@team-agent/installer 0.5.119 → 0.5.120

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 (59) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/README.md +46 -1
  4. package/README.zh.md +46 -1
  5. package/crates/team-agent/src/cli/adapters.rs +11 -0
  6. package/crates/team-agent/src/cli/arguments.rs +401 -0
  7. package/crates/team-agent/src/cli/emit.rs +241 -142
  8. package/crates/team-agent/src/cli/intent/runtime.rs +1139 -0
  9. package/crates/team-agent/src/cli/intent/runtime_tests.rs +584 -0
  10. package/crates/team-agent/src/cli/intent/tests.rs +654 -0
  11. package/crates/team-agent/src/cli/intent.rs +642 -0
  12. package/crates/team-agent/src/cli/mod.rs +49 -5
  13. package/crates/team-agent/src/cli/named_address.rs +10 -1
  14. package/crates/team-agent/src/cli/spec.rs +22 -10
  15. package/crates/team-agent/src/cli/status_port/brief.rs +117 -16
  16. package/crates/team-agent/src/cli/tests/base.rs +6 -1
  17. package/crates/team-agent/src/cli/tests/parser_safety.rs +383 -0
  18. package/crates/team-agent/src/cli/tests/run_delegation.rs +63 -19
  19. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +3 -2
  20. package/crates/team-agent/src/cli/tests/verb_profile.rs +3 -1
  21. package/crates/team-agent/src/contract_runtime/backend.rs +1 -1
  22. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +166 -77
  23. package/crates/team-agent/src/lifecycle/launch.rs +28 -0
  24. package/crates/team-agent/src/lifecycle/restart.rs +17 -0
  25. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -3
  26. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +5 -15
  27. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -3
  28. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +2 -3
  29. package/crates/team-agent/src/lifecycle/tests/cli_argv_routing_red.rs +10 -2
  30. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +8 -1
  31. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +3 -2
  32. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +6 -9
  33. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +7 -5
  34. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +4 -2
  35. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +5 -2
  36. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +4 -3
  37. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +2 -4
  38. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +3 -4
  39. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +5 -4
  40. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +2 -4
  41. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +78 -36
  42. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +8 -4
  43. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +6 -12
  44. package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +4 -0
  45. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1 -3
  46. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +7 -0
  47. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -3
  48. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -1
  49. package/crates/team-agent/src/lifecycle/types.rs +10 -0
  50. package/crates/team-agent/src/tmux_backend/tests.rs +37 -0
  51. package/crates/team-agent/src/tmux_backend.rs +8 -1
  52. package/package.json +4 -4
  53. package/skills/team-agent/SKILL.md +71 -54
  54. package/skills/team-agent/command-coverage.json +283 -13
  55. package/skills/team-agent/docs/reference/team-agent-operator.md +220 -0
  56. package/skills/team-agent/references/bug-as-artifact-flow.md +1 -1
  57. package/skills/team-agent/references/recovery-runbook.md +61 -212
  58. package/skills/team-agent/references/team-agent-operator.md +181 -48
  59. package/skills/team-agent/references/team-in-team.md +47 -80
@@ -32,7 +32,9 @@ use team_agent::transport::test_support::OfflineTransport;
32
32
  #[test]
33
33
  #[serial(env)]
34
34
  fn cursor_role_missing_model_uses_provider_default() {
35
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-no-model");
35
36
  let ws = tmp_dir("cursor-no-model");
37
+ super::launch_spawn::seed_healthy_coordinator(&ws);
36
38
  let team = write_role_team(&ws, "cursortm", "cursor_writer", None, None);
37
39
  let transport = OfflineTransport::new();
38
40
  quick_start_with_transport_in_workspace(
@@ -54,8 +56,9 @@ fn cursor_role_missing_model_uses_provider_default() {
54
56
  #[test]
55
57
  #[serial(env)]
56
58
  fn cursor_role_explicit_model_reaches_argv() {
57
- let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-with-model");
59
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-with-model");
58
60
  let ws = tmp_dir("cursor-with-model");
61
+ super::launch_spawn::seed_healthy_coordinator(&ws);
59
62
  let team = write_role_team(
60
63
  &ws,
61
64
  "cursortm",
@@ -116,7 +119,7 @@ fn cursor_role_with_effort_refuses_to_start() {
116
119
  #[test]
117
120
  #[serial(env)]
118
121
  fn cursor_second_seat_in_same_workspace_is_refused() {
119
- let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-two");
122
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-two");
120
123
  let ws = tmp_dir("cursor-two");
121
124
  let team = ws.join("cursortm");
122
125
  std::fs::create_dir_all(team.join("agents")).unwrap();
@@ -40,7 +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
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-fresh");
44
44
  let (ws, _home, _guard, team) = seed_cursor("pending");
45
45
  let transport = OfflineTransport::new();
46
46
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
@@ -80,7 +80,7 @@ fn cursor_fresh_spawn_does_not_invent_session_id() {
80
80
  #[test]
81
81
  #[serial(env)]
82
82
  fn cursor_restart_uses_resume_when_session_captured_and_archive_present() {
83
- let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-resume");
83
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-resume");
84
84
  let (ws, home, _guard, team) = seed_cursor("resume");
85
85
  let first = OfflineTransport::new();
86
86
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &first)
@@ -182,7 +182,7 @@ fn cursor_classify_resumes_when_archive_present_and_refuses_when_missing() {
182
182
  #[test]
183
183
  #[serial(env)]
184
184
  fn cursor_fork_agent_refuses_as_unsupported_not_unverified() {
185
- let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-fork");
185
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-fork");
186
186
  let (ws, _home, _guard, team) = seed_cursor("fork");
187
187
  let transport = OfflineTransport::new();
188
188
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
@@ -280,6 +280,7 @@ fn seed_healthy_coordinator(workspace: &Path) {
280
280
 
281
281
  fn seed_cursor(tag: &str) -> (PathBuf, PathBuf, HomeGuard, PathBuf) {
282
282
  let ws = tmp_dir(tag);
283
+ super::launch_spawn::seed_healthy_coordinator(&ws);
283
284
  let home = tmp_dir(&format!("{tag}-home"));
284
285
  let guard = HomeGuard::set(&home);
285
286
  let team = write_cursor_role(&ws);
@@ -22,10 +22,7 @@ mod hermetic_guard;
22
22
  fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
23
23
 
24
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
25
+ hermetic_guard::HermeticTestEnv::enter_with_leader(tag)
29
26
  }
30
27
 
31
28
  use std::path::{Path, PathBuf};
@@ -159,6 +156,7 @@ fn seed_healthy_coordinator(workspace: &Path) {
159
156
 
160
157
  fn seed_grok(tag: &str, effort: Option<&str>) -> (PathBuf, PathBuf, HomeGuard, PathBuf) {
161
158
  let ws = tmp_dir(tag);
159
+ super::launch_spawn::seed_healthy_coordinator(&ws);
162
160
  let home = tmp_dir(&format!("{tag}-home"));
163
161
  seed_grok_home(&home, Some(&ws));
164
162
  let guard = HomeGuard::set(&home);
@@ -15,10 +15,7 @@ mod hermetic_guard;
15
15
  fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
16
16
 
17
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
18
+ hermetic_guard::HermeticTestEnv::enter_with_leader(tag)
22
19
  }
23
20
 
24
21
  use std::path::{Path, PathBuf};
@@ -218,6 +215,7 @@ fn two_grok_seats_coexist_when_toml_has_no_per_seat_keys() {
218
215
  fn leftover_per_seat_key_is_cleared_before_start() {
219
216
  let _hermetic = enter_hermetic("grok-overlay-leftover-seat");
220
217
  let ws = tmp_dir("grok-cwd-per-seat");
218
+ super::launch_spawn::seed_healthy_coordinator(&ws);
221
219
  let home = tmp_dir("grok-cwd-per-seat-home");
222
220
  seed_grok_home(&home, Some(&ws));
223
221
  let _guard = HomeGuard::set(&home);
@@ -345,6 +343,7 @@ fn grok_missing_login_refuses_to_start() {
345
343
  fn grok_spawn_writes_resolved_team_agent_into_project_grok_config() {
346
344
  let _hermetic = enter_hermetic("grok-overlay-spawn");
347
345
  let ws = tmp_dir("grok-mcp-overlay");
346
+ super::launch_spawn::seed_healthy_coordinator(&ws);
348
347
  let home = tmp_dir("grok-mcp-overlay-home");
349
348
  seed_grok_home(&home, Some(&ws));
350
349
  let _guard = HomeGuard::set(&home);
@@ -14,10 +14,7 @@ mod hermetic_guard;
14
14
  fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
15
15
 
16
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
17
+ hermetic_guard::HermeticTestEnv::enter_with_leader(tag)
21
18
  }
22
19
 
23
20
  use std::path::{Path, PathBuf};
@@ -31,7 +28,9 @@ use team_agent::transport::test_support::OfflineTransport;
31
28
  #[test]
32
29
  #[serial(env)]
33
30
  fn grok_role_missing_model_uses_provider_default() {
31
+ let _hermetic = enter_hermetic("grok-no-model");
34
32
  let ws = tmp_dir("grok-no-model");
33
+ super::launch_spawn::seed_healthy_coordinator(&ws);
35
34
  let home = tmp_dir("grok-no-model-home");
36
35
  seed_grok_home(&home, Some(&ws));
37
36
  let _guard = HomeGuard::set(&home);
@@ -67,6 +66,7 @@ fn grok_role_missing_model_uses_provider_default() {
67
66
  fn grok_role_explicit_model_reaches_argv() {
68
67
  let _hermetic = enter_hermetic("grok-explicit-model");
69
68
  let ws = tmp_dir("grok-with-model");
69
+ super::launch_spawn::seed_healthy_coordinator(&ws);
70
70
  let home = tmp_dir("grok-with-model-home");
71
71
  seed_grok_home(&home, Some(&ws));
72
72
  let _guard = HomeGuard::set(&home);
@@ -103,6 +103,7 @@ fn grok_role_explicit_model_reaches_argv() {
103
103
  fn claude_role_missing_model_still_starts() {
104
104
  let _hermetic = enter_hermetic("grok-claude-missing-model");
105
105
  let ws = tmp_dir("claude-no-model");
106
+ super::launch_spawn::seed_healthy_coordinator(&ws);
106
107
  let team = write_role_team(
107
108
  &ws,
108
109
  "claudeteam",
@@ -22,10 +22,7 @@ mod hermetic_guard;
22
22
  fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
23
23
 
24
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
25
+ hermetic_guard::HermeticTestEnv::enter_with_leader(tag)
29
26
  }
30
27
 
31
28
  use std::path::{Path, PathBuf};
@@ -212,6 +209,7 @@ fn seed_healthy_coordinator(workspace: &Path) {
212
209
 
213
210
  fn seed_grok(tag: &str) -> (PathBuf, PathBuf, HomeGuard, PathBuf) {
214
211
  let ws = tmp_dir(tag);
212
+ super::launch_spawn::seed_healthy_coordinator(&ws);
215
213
  let home = tmp_dir(&format!("{tag}-home"));
216
214
  seed_grok_home(&home, Some(&ws));
217
215
  let guard = HomeGuard::set(&home);
@@ -174,8 +174,9 @@ fn unowned_running_ws() -> PathBuf {
174
174
  #[test]
175
175
  #[serial(env)]
176
176
  fn quick_start_compiles_real_spec_to_team_spec_yaml() {
177
- let _hermetic = enter_hermetic("launch-spawn-compile-spec");
177
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-compile-spec");
178
178
  let team = quick_start_team_dir(QS_VALID_ROLE);
179
+ seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
179
180
  let transport = OfflineTransport::new();
180
181
  let result = quick_start_with_transport(&team, None, true, None, &transport);
181
182
 
@@ -209,9 +210,10 @@ fn quick_start_compiles_real_spec_to_team_spec_yaml() {
209
210
  #[test]
210
211
  #[serial(env)]
211
212
  fn quick_start_without_team_id_uses_compiled_team_name_as_canonical_key() {
212
- let _hermetic = enter_hermetic("launch-spawn-canonical-team");
213
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-canonical-team");
213
214
  let team = quick_start_team_dir(QS_VALID_ROLE);
214
215
  let workspace = crate::model::paths::team_workspace(&team).unwrap();
216
+ seed_healthy_coordinator(&workspace);
215
217
  let transport = OfflineTransport::new();
216
218
 
217
219
  let result = quick_start_with_transport(&team, None, true, None, &transport);
@@ -231,8 +233,9 @@ fn quick_start_without_team_id_uses_compiled_team_name_as_canonical_key() {
231
233
  #[test]
232
234
  #[serial(env)]
233
235
  fn existing_runtime_producer_scopes_canonical_team() {
234
- let _hermetic = enter_hermetic("launch-spawn-existing-producer");
236
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-existing-producer");
235
237
  let team = quick_start_team_dir(QS_VALID_ROLE);
238
+ seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
236
239
  let transport = OfflineTransport::new();
237
240
  let first = quick_start_with_transport(&team, Some("from-name"), true, None, &transport)
238
241
  .expect("first quick-start");
@@ -342,8 +345,9 @@ fn existing_runtime_empty_top_level_agents_still_emits_bob() {
342
345
  #[test]
343
346
  #[serial(env)]
344
347
  fn quick_start_teamdir_under_dot_team_uses_project_workspace_for_status_and_collect() {
345
- let _hermetic = enter_hermetic("launch-spawn-dot-team-workspace");
348
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-dot-team-workspace");
346
349
  let workspace = temp_ws();
350
+ seed_healthy_coordinator(&workspace);
347
351
  let team = workspace.join(".team").join("current");
348
352
  std::fs::create_dir_all(team.join("agents")).unwrap();
349
353
  std::fs::write(team.join("TEAM.md"), QS_TEAM_MD).unwrap();
@@ -417,7 +421,7 @@ fn quick_start_teamdir_under_dot_team_uses_project_workspace_for_status_and_coll
417
421
  #[test]
418
422
  #[serial(env)]
419
423
  fn quick_start_default_workspace_compiled_spec_uses_project_root() {
420
- let _hermetic = enter_hermetic("launch-spawn-default-workspace");
424
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-default-workspace");
421
425
  let workspace = temp_ws();
422
426
  std::fs::create_dir_all(workspace.join("agents")).unwrap();
423
427
  std::fs::write(workspace.join("TEAM.md"), QS_TEAM_MD).unwrap();
@@ -724,8 +728,9 @@ fn unowned_running_ws_all_paused() -> PathBuf {
724
728
  #[test]
725
729
  #[serial(env)]
726
730
  fn spine_quick_start_seeds_state_under_team_workspace_not_team_dir() {
727
- let _hermetic = enter_hermetic("launch-spawn-spine-workspace");
731
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-spine-workspace");
728
732
  let team = quick_start_team_dir(QS_VALID_ROLE); // <base>/teamdir
733
+ seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
729
734
  let transport = OfflineTransport::new();
730
735
  let _ = quick_start_with_transport(&team, None, true, None, &transport);
731
736
  let workspace = team.parent().unwrap(); // team_workspace(<base>/teamdir) = <base>
@@ -1302,21 +1307,32 @@ fn start_agent_session_generation_inherits_birth_or_stamps_recreated_session() {
1302
1307
  /// Seed a HEALTHY coordinator at `workspace` (this process's pid + matching metadata + db schema) so
1303
1308
  /// quick_start's internal start_coordinator returns AlreadyRunning — NO real daemon subprocess spawn.
1304
1309
  pub(super) fn seed_healthy_coordinator(workspace: &std::path::Path) {
1305
- let wp = crate::coordinator::WorkspacePath::new(workspace.to_path_buf());
1306
- std::fs::create_dir_all(crate::model::paths::runtime_dir(workspace)).unwrap();
1307
- let _ = crate::message_store::MessageStore::open(workspace).unwrap(); // create db schema (schema.ok)
1308
- let me = crate::coordinator::Pid::new(std::process::id());
1309
- crate::coordinator::write_coordinator_metadata(
1310
- &wp,
1311
- me,
1312
- crate::coordinator::MetadataSource::Boot,
1313
- )
1314
- .unwrap();
1315
- std::fs::write(
1316
- crate::coordinator::coordinator_pid_path(&wp),
1317
- me.to_string(),
1318
- )
1319
- .unwrap();
1310
+ hermetic_guard::seed_in_process_coordinator(workspace);
1311
+ }
1312
+
1313
+ #[test]
1314
+ #[cfg(unix)]
1315
+ #[serial(env)]
1316
+ fn issue337_owned_fixture_caller_admits_fresh_launch_without_guard_bypass() {
1317
+ let env = hermetic_guard::HermeticTestEnv::enter_with_leader("issue337-fixture-caller");
1318
+ let workspace = env.workspace("ws");
1319
+ let team = workspace.join("roles");
1320
+ std::fs::create_dir_all(team.join("agents")).unwrap();
1321
+ std::fs::write(team.join("TEAM.md"), QS_TEAM_MD).unwrap();
1322
+ std::fs::write(team.join("agents/implementer.md"), QS_VALID_ROLE).unwrap();
1323
+ seed_healthy_coordinator(&workspace);
1324
+ let transport = OfflineTransport::new();
1325
+ let report = quick_start_with_transport_in_workspace(
1326
+ &workspace, &team, None, true, Some("fixture-team"), &transport,
1327
+ ).unwrap();
1328
+ let QuickStartReport::Ready { launch, .. } = report else { panic!("owned caller must admit launch: {report:?}") };
1329
+ assert!(launch.leader_receiver_attached);
1330
+ assert_eq!(launch.started.len(), 1);
1331
+ assert_eq!(transport.spawn_records().len(), 1);
1332
+ let socket = std::env::var("TMUX").unwrap().split(',').next().unwrap().to_string();
1333
+ assert!(std::path::Path::new(&socket).exists());
1334
+ drop(env);
1335
+ assert!(!std::path::Path::new(&socket).exists());
1320
1336
  }
1321
1337
 
1322
1338
  fn seed_canonical_team_identity(workspace: &std::path::Path, team_key: &str) {
@@ -1333,7 +1349,7 @@ fn seed_canonical_team_identity(workspace: &std::path::Path, team_key: &str) {
1333
1349
  #[test]
1334
1350
  #[serial(env)]
1335
1351
  fn quick_start_with_transport_spawns_workers_not_dry_run() {
1336
- let _hermetic = enter_hermetic("launch-spawn-spawn-path");
1352
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-spawn-path");
1337
1353
  let team = quick_start_team_dir(QS_VALID_ROLE); // one agent: implementer / provider codex
1338
1354
  let workspace = team.parent().expect("team_workspace(team_dir) = parent"); // where start_coordinator runs
1339
1355
  seed_healthy_coordinator(workspace);
@@ -1422,7 +1438,7 @@ fn adaptive_layout_plan_8_workers_is_3_3_2() {
1422
1438
  #[test]
1423
1439
  #[serial(env)]
1424
1440
  fn quick_start_default_adaptive_groups_workers_into_layout_panes() {
1425
- let _hermetic = enter_hermetic("launch-spawn-adaptive-layout");
1441
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-adaptive-layout");
1426
1442
  let roles = (1..=4)
1427
1443
  .map(|i| {
1428
1444
  let id = format!("w{i}");
@@ -1507,7 +1523,7 @@ fn quick_start_tmux_backend_uses_workspace_private_endpoint() {
1507
1523
  #[test]
1508
1524
  #[serial(env)]
1509
1525
  fn quick_start_persists_selected_tmux_endpoint_and_attach_commands() {
1510
- let _hermetic = enter_hermetic("launch-spawn-endpoint-attach");
1526
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-endpoint-attach");
1511
1527
  let team = quick_start_team_dir(QS_VALID_ROLE);
1512
1528
  let workspace = team.parent().expect("team_workspace(team_dir) = parent");
1513
1529
  seed_healthy_coordinator(workspace);
@@ -1568,23 +1584,27 @@ fn annotate_runtime_tmux_endpoint_persists_workspace_socket_as_full_path() {
1568
1584
  #[test]
1569
1585
  #[serial(env)]
1570
1586
  fn quick_start_preserves_managed_leader_topology_and_emits_leader_attach_command() {
1571
- let _hermetic = enter_hermetic("launch-spawn-managed-leader");
1572
- let team = quick_start_team_dir(QS_VALID_ROLE);
1573
- let workspace = team.parent().expect("team_workspace(team_dir) = parent");
1587
+ let hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-managed-leader");
1588
+ let workspace = hermetic.root();
1589
+ let team = workspace.join("teamdir");
1590
+ std::fs::create_dir_all(team.join("agents")).unwrap();
1591
+ std::fs::write(team.join("TEAM.md"), QS_TEAM_MD).unwrap();
1592
+ std::fs::write(team.join("agents/implementer.md"), QS_VALID_ROLE).unwrap();
1574
1593
  seed_healthy_coordinator(workspace);
1594
+ let endpoint = std::env::var("TMUX").unwrap().split(',').next().unwrap().to_string();
1575
1595
  crate::state::persist::save_runtime_state(
1576
1596
  workspace,
1577
1597
  &json!({
1578
1598
  "active_team_key": "teamdir",
1579
- "session_name": "team-teamdir",
1599
+ "session_name": null,
1580
1600
  "is_external_leader": false,
1581
1601
  "leader_client": {"diagnostic_only": true, "attach_mode": "attach-session"},
1582
- "leader_receiver": {"mode": "direct_tmux", "status": "attached", "pane_id": "%42"},
1583
- "team_owner": {"pane_id": "%42", "owner_epoch": 1},
1602
+ "tmux_socket": endpoint,
1603
+ "tmux_endpoint": endpoint,
1584
1604
  "agents": {},
1585
1605
  "teams": {
1586
1606
  "teamdir": {
1587
- "session_name": "team-teamdir",
1607
+ "session_name": null,
1588
1608
  "is_external_leader": false,
1589
1609
  "leader_client": {"diagnostic_only": true, "attach_mode": "attach-session"},
1590
1610
  "agents": {}
@@ -1593,9 +1613,25 @@ fn quick_start_preserves_managed_leader_topology_and_emits_leader_attach_command
1593
1613
  }),
1594
1614
  )
1595
1615
  .expect("seed managed leader state");
1616
+ let attached = crate::cli::cmd_attach_leader(&crate::cli::AttachLeaderArgs {
1617
+ workspace: workspace.to_path_buf(), team: Some("teamdir".into()), pane: None,
1618
+ provider: crate::model::enums::Provider::Fake, confirm: false, json: true,
1619
+ }).expect("bind the real owned caller through the public lease port");
1620
+ assert_eq!(attached.exit, crate::cli::ExitCode::Ok, "{attached:?}");
1621
+ let mut state = crate::state::persist::load_runtime_state(workspace).unwrap();
1622
+ assert!(state["session_name"].is_null(), "leader-only bootstrap has no worker session");
1623
+ assert!(state["teams"]["teamdir"]["session_name"].is_null());
1624
+ assert!(state["teams"]["teamdir"]["agents"].as_object().unwrap().is_empty());
1625
+ // The owned fake caller models the already-managed launcher topology;
1626
+ // retain the real binding while setting the presentation fixture metadata.
1627
+ state["is_external_leader"] = json!(false);
1628
+ state["leader_client"] = json!({"diagnostic_only": true, "attach_mode": "attach-session"});
1629
+ state["teams"]["teamdir"]["is_external_leader"] = json!(false);
1630
+ state["teams"]["teamdir"]["leader_client"] = state["leader_client"].clone();
1631
+ crate::state::persist::save_runtime_state(workspace, &state).unwrap();
1596
1632
  let transport = OfflineTransport::new();
1597
1633
 
1598
- let report = quick_start_with_transport(&team, None, true, None, &transport)
1634
+ let report = quick_start_with_transport(&team, None, true, Some("teamdir"), &transport)
1599
1635
  .expect("quick_start_with_transport must reach Ready");
1600
1636
  let attach_commands = match report {
1601
1637
  QuickStartReport::Ready {
@@ -2872,8 +2908,9 @@ fn stop_agent_adaptive_kills_target_pane_not_shared_layout_window() {
2872
2908
  #[test]
2873
2909
  #[serial(env)]
2874
2910
  fn quick_start_seeds_tasks_key_from_compiled_spec() {
2875
- let _hermetic = enter_hermetic("launch-spawn-tasks-key");
2911
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-tasks-key");
2876
2912
  let team = quick_start_team_dir(QS_VALID_ROLE);
2913
+ seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
2877
2914
  let transport = OfflineTransport::new();
2878
2915
  let _ = quick_start_with_transport(&team, None, true, None, &transport);
2879
2916
  let workspace = team
@@ -2921,7 +2958,7 @@ fn quick_start_seeds_tasks_key_from_compiled_spec() {
2921
2958
  // suffix (active_team_key, teams).
2922
2959
  #[test]
2923
2960
  #[serial(env)]
2924
- fn quick_start_state_seeds_spec_path_workspace_leader() {
2961
+ fn quick_start_unbound_state_producer_seeds_spec_path_workspace_leader() {
2925
2962
  let _hermetic = enter_hermetic("launch-spawn-state-seeds");
2926
2963
  // Bug 2 owner team-scope: top-level owner triple is dropped when the seeded
2927
2964
  // pane is empty; an ambient TMUX_PANE (tests run inside the dev's tmux session)
@@ -2932,10 +2969,14 @@ fn quick_start_state_seeds_spec_path_workspace_leader() {
2932
2969
  let _tmux = EnvVarGuard::unset("TMUX");
2933
2970
  let team = quick_start_team_dir(QS_VALID_ROLE);
2934
2971
  let transport = OfflineTransport::new();
2935
- let _ = quick_start_with_transport(&team, None, true, None, &transport);
2936
2972
  let workspace = team
2937
2973
  .parent()
2938
2974
  .expect("team_workspace(<base>/teamdir) = <base>");
2975
+ // Test the real unbound state producer plus recording-only worker stage.
2976
+ // Public start correctly refuses this absent caller (separate regressions).
2977
+ crate::lifecycle::launch::materialize_unbound_state_for_test(
2978
+ workspace, &team, "quickteam", &transport,
2979
+ ).expect("materialize the unbound producer fixture without public start");
2939
2980
  // E5: spec_path in state now points to .team/runtime/<team_key>/, team_dir stays the user role dir.
2940
2981
  let spec_path = quick_start_runtime_spec_path(&team);
2941
2982
  let (raw, state) = raw_runtime_state(workspace);
@@ -3018,10 +3059,11 @@ fn quick_start_state_seeds_spec_path_workspace_leader() {
3018
3059
  #[test]
3019
3060
  #[serial(env)]
3020
3061
  fn quick_start_running_agent_state_shape_after_spawn_is_golden() {
3021
- let _hermetic = enter_hermetic("launch-spawn-running-state");
3062
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-running-state");
3022
3063
  const FIXED_SPAWNED_AT: &str = "2026-06-04T00:00:00+00:00";
3023
3064
  let _clock_guard = EnvVarGuard::set("TEAM_AGENT_TEST_FIXED_SPAWNED_AT", FIXED_SPAWNED_AT);
3024
3065
  let team = quick_start_team_dir(QS_VALID_ROLE);
3066
+ seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
3025
3067
  let transport = OfflineTransport::new();
3026
3068
  let _ = quick_start_with_transport(&team, None, true, None, &transport);
3027
3069
  let workspace = team
@@ -32,7 +32,7 @@ fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
32
32
  #[test]
33
33
  #[serial_test::serial(env)]
34
34
  fn concurrent_reset_discard_session_serializes() {
35
- let _hermetic = enter_hermetic("phase-b-concurrent-reset");
35
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-concurrent-reset");
36
36
  let ids = (1..=6).map(|n| format!("w{n}")).collect::<Vec<_>>();
37
37
  let role_docs = ids
38
38
  .iter()
@@ -139,7 +139,7 @@ fn concurrent_reset_discard_session_serializes() {
139
139
  #[test]
140
140
  #[serial_test::serial(env)]
141
141
  fn add_fork_remove_share_lifecycle_lock_behavior() {
142
- let _hermetic = enter_hermetic("phase-b-fork");
142
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-fork");
143
143
  let _home = LaneHomeGuard::enter("phase-b-fork");
144
144
  let (add_team, add_workspace, add_role, add_transport) = add_fixture();
145
145
  let add_agent = aid("w2");
@@ -324,7 +324,7 @@ fn add_fork_remove_share_lifecycle_lock_behavior() {
324
324
  #[test]
325
325
  #[serial_test::serial(env)]
326
326
  fn breal_spawn_ownership_mismatch_fails_without_state_pollution() {
327
- let _hermetic = enter_hermetic("phase-b-spawn-ownership");
327
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-spawn-ownership");
328
328
  let (workspace, _team) = breal_workspace();
329
329
  let transport = BRealTransport::misowned();
330
330
 
@@ -366,7 +366,9 @@ fn breal_spawn_ownership_mismatch_fails_without_state_pollution() {
366
366
  }
367
367
 
368
368
  #[test]
369
+ #[serial_test::serial(env)]
369
370
  fn breal_provider_missing_window_disappeared_fails_closed() {
371
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-missing-window");
370
372
  let (workspace, _team) = breal_workspace();
371
373
  let transport = BRealTransport::dead_after_spawn();
372
374
 
@@ -404,7 +406,9 @@ fn breal_provider_missing_window_disappeared_fails_closed() {
404
406
  }
405
407
 
406
408
  #[test]
409
+ #[serial_test::serial(env)]
407
410
  fn breal_reset_rehydrates_role_context_from_compiled_spec() {
411
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-reset-context");
408
412
  let (workspace, _team) = breal_workspace();
409
413
  strip_runtime_command_context(&workspace);
410
414
  let transport = BRealTransport::owned();
@@ -438,7 +442,7 @@ fn breal_reset_rehydrates_role_context_from_compiled_spec() {
438
442
  #[test]
439
443
  #[serial_test::serial(env)]
440
444
  fn breal_restart_rehydrates_role_context_from_compiled_spec() {
441
- let _hermetic = enter_hermetic("phase-b-restart-role-context");
445
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-restart-role-context");
442
446
  let (workspace, _team) = breal_one_worker_workspace();
443
447
  strip_runtime_command_context(&workspace);
444
448
  let transport = BRealTransport::owned();
@@ -94,8 +94,8 @@ fn hermetic_caller_envs() -> &'static [&'static str] {
94
94
 
95
95
  #[test]
96
96
  #[serial_test::serial(env)]
97
- fn phase_golden_unbound_not_ready_requires_claim_leader() {
98
- let diagnose = run_diagnose_after_quick_start();
97
+ fn phase_golden_missing_registry_requires_index_publication_not_claim() {
98
+ let diagnose = run_diagnose_after_unbound_state_fixture();
99
99
  let issues = diagnose
100
100
  .get("issues")
101
101
  .and_then(Value::as_array)
@@ -177,22 +177,16 @@ fn cmd_value(result: Result<CmdResult, crate::cli::CliError>) -> Value {
177
177
  }
178
178
  }
179
179
 
180
- fn run_diagnose_after_quick_start() -> Value {
180
+ fn run_diagnose_after_unbound_state_fixture() -> Value {
181
181
  let hermetic = HermeticTestEnv::enter("tombstone-claim-leader");
182
182
  let _permission_mode = EnvVarGuard::set(ANCESTRY_ENV, "[]");
183
183
  let team = two_worker_team_dir(&hermetic);
184
184
  let workspace = team.parent().expect("workspace").to_path_buf();
185
185
  seed_healthy_coordinator(&workspace);
186
186
  let launch_transport = codex_ready_transport();
187
- quick_start_with_transport_in_workspace(
188
- &workspace,
189
- &team,
190
- None,
191
- true,
192
- None,
193
- &launch_transport,
194
- )
195
- .expect("tombstone fixture must quick-start");
187
+ crate::lifecycle::launch::materialize_unbound_state_for_test(
188
+ &workspace, &team, "teamdir", &launch_transport,
189
+ ).expect("materialize the recording-only tombstone fixture, not a callerless public start");
196
190
  let mut state = crate::state::persist::load_runtime_state(&workspace)
197
191
  .expect("tombstone fixture must persist runtime state");
198
192
  crate::lifecycle::launch::seed_launched_owner_from_caller_with_provider_lookup(
@@ -90,6 +90,7 @@ impl Drop for PathEnvGuard {
90
90
  struct Fixture {
91
91
  #[cfg(unix)]
92
92
  _path_guard: PathEnvGuard,
93
+ _caller: hermetic_guard::HermeticTestEnv,
93
94
  root: PathBuf,
94
95
  team_dir: PathBuf,
95
96
  run_workspace: PathBuf,
@@ -103,6 +104,7 @@ struct Fixture {
103
104
 
104
105
  impl Fixture {
105
106
  fn new() -> Self {
107
+ let caller = hermetic_guard::HermeticTestEnv::enter_with_leader("pi-fork-owned-caller");
106
108
  let root = super::temp_ws();
107
109
  #[cfg(unix)]
108
110
  let path_guard = PathEnvGuard::with_fake_pi(&root);
@@ -122,6 +124,7 @@ impl Fixture {
122
124
  )
123
125
  .expect("write referenced profile");
124
126
 
127
+ super::launch_spawn::seed_healthy_coordinator(&root);
125
128
  let setup = team_agent::lifecycle::launch::quick_start_with_transport(
126
129
  &team_dir,
127
130
  None,
@@ -198,6 +201,7 @@ impl Fixture {
198
201
  let fixture = Self {
199
202
  #[cfg(unix)]
200
203
  _path_guard: path_guard,
204
+ _caller: caller,
201
205
  root,
202
206
  team_dir,
203
207
  run_workspace,
@@ -468,9 +468,7 @@ struct LaunchLatencyCase {
468
468
 
469
469
  impl LaunchLatencyCase {
470
470
  fn new(tag: &str, workers: usize) -> Self {
471
- let env = hermetic_guard::HermeticTestEnv::enter(tag);
472
- env.scrub_tmux();
473
- env.assert_no_real_tmux();
471
+ let env = hermetic_guard::HermeticTestEnv::enter_with_leader(tag);
474
472
  let workspace = env.workspace(tag);
475
473
  let team_dir = workspace.join("teamdir");
476
474
  fs::create_dir_all(&team_dir).expect("create launch team dir");
@@ -355,6 +355,13 @@ fn red6_cli_restart_reports_and_top_level_error_logs_are_redacted() {
355
355
  combined.push_str(&String::from_utf8_lossy(&output.stdout));
356
356
  combined.push_str(&String::from_utf8_lossy(&output.stderr));
357
357
  }
358
+ assert!(read_cli_error_logs(&workspace).is_empty(),
359
+ "invalid profile syntax must not create a runtime diagnostic log");
360
+ // Keep the original physical-log redaction assertion on the same actual
361
+ // runtime-error emitter, separately from the new zero-log Usage boundary.
362
+ assert_eq!(crate::cli::emit::__test_runtime_error_emitter(
363
+ "restart", &workspace, &crate::cli::CliError::Runtime(diagnostic.clone())
364
+ ), crate::cli::ExitCode::Error);
358
365
  let logs = read_cli_error_logs(&workspace);
359
366
  let output_safe = !combined.contains(&marker) && combined.contains(REDACTED);
360
367
  let logs_safe = !logs.contains(&marker) && logs.contains(REDACTED);
@@ -35,10 +35,8 @@ use team_agent::transport::{
35
35
  #[test]
36
36
  #[serial(env)]
37
37
  fn quick_start_team_id_persists_requested_team_key_for_lifecycle_selection() {
38
- let hermetic = hermetic_guard::HermeticTestEnv::enter("team-in-team-requested-key");
39
- hermetic.scrub_tmux();
40
- hermetic.assert_no_real_tmux();
41
38
  let _env = EnvGuard::unset();
39
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("team-in-team-requested-key");
42
40
  let root = tmp_dir("team-id-key");
43
41
  seed_healthy_coordinator(&root);
44
42
  let team_dir = write_team_dir(&root, "teamdir", "w1");
@@ -46,8 +46,9 @@ const CLAUDE_ROLE: &str = "---\nname: clauder\nrole: Claude Worker\nprovider: cl
46
46
  #[test]
47
47
  #[serial(env)]
48
48
  fn worker_mcp_owner_team_scope_must_match_runtime_team_key_not_spec_name() {
49
- let hermetic = HermeticTestEnv::enter("rs031-mcp-owner-scope");
49
+ let hermetic = HermeticTestEnv::enter_with_leader("rs031-mcp-owner-scope");
50
50
  let team = mixed_provider_team_dir(&hermetic, "mcp-owner-scope");
51
+ super::launch_spawn::seed_healthy_coordinator(team.parent().unwrap());
51
52
  let transport = RecordingTransport::new()
52
53
  .with_windows(vec![WindowName::new("clauder"), WindowName::new("codexer")]);
53
54
  let launch = ready_launch(
@@ -512,6 +512,16 @@ pub enum QuickStartReport {
512
512
  worker_readiness: QuickStartReadiness,
513
513
  team: String,
514
514
  },
515
+ /// Workers may exist, but post-spawn admission/coordinator completion failed.
516
+ /// Preserve confirmed effects; never disguise this as a preflight refusal.
517
+ Partial {
518
+ workspace: PathBuf,
519
+ session_name: SessionName,
520
+ launch: Box<LaunchReport>,
521
+ team: String,
522
+ error: String,
523
+ coordinator_state: String,
524
+ },
515
525
  /// 已有 runtime state → quick-start 拒绝并引导用 restart。
516
526
  ExistingRuntime {
517
527
  team: Option<String>,