@team-agent/installer 0.5.66 → 0.5.68

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 (164) hide show
  1. package/Cargo.lock +8 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/Cargo.toml +1 -0
  4. package/crates/team-agent/src/cli/adapters.rs +5 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +82 -0
  6. package/crates/team-agent/src/cli/emit.rs +3 -3
  7. package/crates/team-agent/src/cli/grok_slot.rs +299 -0
  8. package/crates/team-agent/src/cli/leader.rs +21 -7
  9. package/crates/team-agent/src/cli/leaders.rs +125 -1
  10. package/crates/team-agent/src/cli/mod.rs +20 -0
  11. package/crates/team-agent/src/cli/send/presentation.rs +7 -1
  12. package/crates/team-agent/src/cli/spec.rs +2 -0
  13. package/crates/team-agent/src/cli/status_port/compact.rs +28 -0
  14. package/crates/team-agent/src/cli/status_port/snapshot.rs +25 -1
  15. package/crates/team-agent/src/cli/tests/base.rs +26 -3
  16. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +129 -19
  17. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +387 -6
  18. package/crates/team-agent/src/cli/tests/status_send.rs +79 -10
  19. package/crates/team-agent/src/communication_mode/mod.rs +6 -4
  20. package/crates/team-agent/src/compiler.rs +59 -0
  21. package/crates/team-agent/src/coordinator/backoff.rs +55 -0
  22. package/crates/team-agent/src/coordinator/conpty_shim.rs +82 -0
  23. package/crates/team-agent/src/coordinator/health.rs +173 -0
  24. package/crates/team-agent/src/coordinator/mod.rs +31 -0
  25. package/crates/team-agent/src/coordinator/orphan.rs +31 -0
  26. package/crates/team-agent/src/coordinator/runtime_detectors.rs +30 -0
  27. package/crates/team-agent/src/coordinator/runtime_observation.rs +25 -0
  28. package/crates/team-agent/src/coordinator/steps/abnormal.rs +59 -0
  29. package/crates/team-agent/src/coordinator/steps/delivery.rs +10 -0
  30. package/crates/team-agent/src/coordinator/steps/health_sync.rs +10 -0
  31. package/crates/team-agent/src/coordinator/steps/mod.rs +22 -0
  32. package/crates/team-agent/src/coordinator/steps/persist.rs +10 -0
  33. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +10 -0
  34. package/crates/team-agent/src/coordinator/steps/session_gate.rs +10 -0
  35. package/crates/team-agent/src/coordinator/tick.rs +133 -22
  36. package/crates/team-agent/src/coordinator/types.rs +49 -0
  37. package/crates/team-agent/src/db/message_store.rs +39 -5
  38. package/crates/team-agent/src/layout/worker_env.rs +20 -3
  39. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  40. package/crates/team-agent/src/leader/provider_attribution.rs +53 -0
  41. package/crates/team-agent/src/leader/registry.rs +65 -0
  42. package/crates/team-agent/src/leader/start.rs +920 -63
  43. package/crates/team-agent/src/lifecycle/display.rs +56 -0
  44. package/crates/team-agent/src/lifecycle/helpers.rs +57 -0
  45. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +332 -12
  46. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +62 -0
  47. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +59 -0
  48. package/crates/team-agent/src/lifecycle/launch/clone_agent.rs +101 -11
  49. package/crates/team-agent/src/lifecycle/launch/cursor_create_chat.rs +229 -0
  50. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +332 -0
  51. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +204 -456
  52. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +24 -0
  53. package/crates/team-agent/src/lifecycle/launch/grok_per_seat.rs +260 -0
  54. package/crates/team-agent/src/lifecycle/launch/identity.rs +146 -0
  55. package/crates/team-agent/src/lifecycle/launch/layout.rs +60 -0
  56. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +112 -0
  57. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +530 -0
  58. package/crates/team-agent/src/lifecycle/launch/ownership.rs +40 -0
  59. package/crates/team-agent/src/lifecycle/launch/plan.rs +31 -0
  60. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +66 -0
  61. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +78 -0
  62. package/crates/team-agent/src/lifecycle/launch/readiness.rs +85 -38
  63. package/crates/team-agent/src/lifecycle/launch/role_source.rs +44 -44
  64. package/crates/team-agent/src/lifecycle/launch/spawn.rs +62 -2
  65. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +139 -0
  66. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +109 -0
  67. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +214 -1
  68. package/crates/team-agent/src/lifecycle/launch.rs +99 -29
  69. package/crates/team-agent/src/lifecycle/lock.rs +42 -1
  70. package/crates/team-agent/src/lifecycle/mod.rs +11 -0
  71. package/crates/team-agent/src/lifecycle/pane_input_lock.rs +161 -0
  72. package/crates/team-agent/src/lifecycle/profile_launch.rs +98 -0
  73. package/crates/team-agent/src/lifecycle/profile_smoke.rs +51 -1
  74. package/crates/team-agent/src/lifecycle/restart/agent.rs +124 -0
  75. package/crates/team-agent/src/lifecycle/restart/common.rs +369 -11
  76. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +27 -0
  77. package/crates/team-agent/src/lifecycle/restart/preflight.rs +25 -0
  78. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +106 -0
  79. package/crates/team-agent/src/lifecycle/restart/remove.rs +196 -5
  80. package/crates/team-agent/src/lifecycle/restart/selection.rs +72 -0
  81. package/crates/team-agent/src/lifecycle/restart/team_state.rs +22 -0
  82. package/crates/team-agent/src/lifecycle/restart.rs +29 -0
  83. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +2 -3
  84. package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +309 -0
  85. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +150 -157
  86. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +2 -2
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +4 -0
  88. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +287 -0
  89. package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +229 -0
  90. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +353 -0
  91. package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +784 -0
  92. package/crates/team-agent/src/lifecycle/tests/gate_fixtures.rs +562 -0
  93. package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +239 -0
  94. package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +498 -0
  95. package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +250 -0
  96. package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +293 -0
  97. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +63 -38
  98. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +3 -2
  99. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +16 -8
  100. package/crates/team-agent/src/lifecycle/tests/mcp_tool_name_format_red.rs +69 -0
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +20 -19
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +97 -61
  103. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +2 -0
  104. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +40 -7
  105. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +59 -1
  106. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +69 -26
  107. package/crates/team-agent/src/lifecycle/tests.rs +23 -13
  108. package/crates/team-agent/src/lifecycle/types.rs +61 -0
  109. package/crates/team-agent/src/lifecycle/worker_command_context.rs +85 -11
  110. package/crates/team-agent/src/mcp_server/tests/scoped.rs +100 -1
  111. package/crates/team-agent/src/mcp_server/tests.rs +196 -2
  112. package/crates/team-agent/src/messaging/delivery.rs +405 -40
  113. package/crates/team-agent/src/messaging/helpers.rs +2 -0
  114. package/crates/team-agent/src/messaging/leader_receiver.rs +53 -1
  115. package/crates/team-agent/src/messaging/results.rs +4 -0
  116. package/crates/team-agent/src/messaging/send.rs +34 -0
  117. package/crates/team-agent/src/messaging/tests/dup_inject.rs +406 -0
  118. package/crates/team-agent/src/messaging/tests/e23.rs +9 -0
  119. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +66 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  121. package/crates/team-agent/src/messaging/types.rs +4 -1
  122. package/crates/team-agent/src/model/enums.rs +14 -5
  123. package/crates/team-agent/src/model/permissions.rs +3 -0
  124. package/crates/team-agent/src/os_probe.rs +73 -2
  125. package/crates/team-agent/src/provider/adapter.rs +231 -4
  126. package/crates/team-agent/src/provider/adapters/cursor_agent.rs +86 -0
  127. package/crates/team-agent/src/provider/adapters/grok.rs +157 -0
  128. package/crates/team-agent/src/provider/adapters/mod.rs +2 -0
  129. package/crates/team-agent/src/provider/bypass_flags.rs +46 -2
  130. package/crates/team-agent/src/provider/classify.rs +4 -2
  131. package/crates/team-agent/src/provider/faults.rs +2 -1
  132. package/crates/team-agent/src/provider/mod.rs +11 -0
  133. package/crates/team-agent/src/provider/session/capture.rs +158 -20
  134. package/crates/team-agent/src/provider/session/context_fork/claude.rs +38 -1
  135. package/crates/team-agent/src/provider/session/context_fork/codex.rs +72 -1
  136. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +57 -1
  137. package/crates/team-agent/src/provider/session/context_fork.rs +77 -3
  138. package/crates/team-agent/src/provider/session/mod.rs +18 -0
  139. package/crates/team-agent/src/provider/session/resume.rs +57 -0
  140. package/crates/team-agent/src/provider/session_scan/claude.rs +125 -1
  141. package/crates/team-agent/src/provider/session_scan/codex.rs +94 -1
  142. package/crates/team-agent/src/provider/session_scan/common.rs +204 -2
  143. package/crates/team-agent/src/provider/session_scan/copilot.rs +33 -1
  144. package/crates/team-agent/src/provider/session_scan/cursor.rs +538 -0
  145. package/crates/team-agent/src/provider/session_scan/grok.rs +288 -0
  146. package/crates/team-agent/src/provider/session_scan.rs +8 -0
  147. package/crates/team-agent/src/provider/submit_now.rs +94 -0
  148. package/crates/team-agent/src/provider/tests/adapter.rs +304 -0
  149. package/crates/team-agent/src/provider/types.rs +4 -2
  150. package/crates/team-agent/src/provider/wire.rs +23 -1
  151. package/crates/team-agent/src/state/persist.rs +301 -8
  152. package/crates/team-agent/src/state/repository.rs +5 -0
  153. package/crates/team-agent/src/tmux_backend/tests.rs +1737 -42
  154. package/crates/team-agent/src/tmux_backend.rs +1064 -75
  155. package/crates/team-agent/src/transport/tests/wire.rs +28 -2
  156. package/crates/team-agent/src/transport.rs +248 -1
  157. package/npm/install.mjs +129 -73
  158. package/package.json +4 -4
  159. package/skills/team-agent/SKILL.md +33 -238
  160. package/skills/team-agent/command-coverage.json +31 -0
  161. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +0 -59
  162. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +0 -488
  163. package/crates/team-agent/src/lifecycle/launch/fork_pending.rs +0 -109
  164. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +0 -447
@@ -1,3 +1,18 @@
1
+ //! ---
2
+ //! purpose: restart spawn overlap and identity contracts
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: restart_spawn_overlap_identity
6
+ //! what: controlled transport facts prove overlapping worker spawns and stable persisted identities
7
+ //! depends:
8
+ //! - crate::lifecycle::restart
9
+ //! - hermetic fake transport with spawn trace
10
+ //! boundary:
11
+ //! - overlap/max-in-flight facts replace scheduler-sensitive total-time thresholds
12
+ //! - no production scheduler, pool, or state-machine changes
13
+ //! maturity: wired
14
+ //! ---
15
+ //!
1
16
  //! 0.5.38 RED contract: startup latency instrumentation and bounded worker spawn.
2
17
  //!
3
18
  //! References:
@@ -83,6 +98,10 @@ fn restart_records_parallel_spawn_overlap_and_deterministic_state() {
83
98
  "expected at least two new-window spawns to overlap after the initial session creator; calls={calls:?}"
84
99
  ));
85
100
  }
101
+ assert!(
102
+ transport.max_in_flight() >= 2,
103
+ "expected at least two concurrent spawn calls; calls={calls:?}"
104
+ );
86
105
  let agent_keys = state
87
106
  .pointer("/agents")
88
107
  .and_then(Value::as_object)
@@ -272,7 +291,6 @@ fn eight_worker_restart_with_100ms_spawn_delay_beats_serial_baseline() {
272
291
  let delay = Duration::from_millis(100);
273
292
  let transport = StartupLatencyTransport::new().with_spawn_delay(delay);
274
293
 
275
- let started = Instant::now();
276
294
  let report = restart_with_transport_with_readiness_deadline(
277
295
  &case.workspace,
278
296
  true,
@@ -281,7 +299,6 @@ fn eight_worker_restart_with_100ms_spawn_delay_beats_serial_baseline() {
281
299
  Some(2_000),
282
300
  )
283
301
  .expect("R4 setup: restart should complete against delayed transport");
284
- let elapsed = started.elapsed();
285
302
  assert!(
286
303
  matches!(report, RestartReport::Restarted { .. }),
287
304
  "R4 setup: restart must reach Restarted; report={report:?}"
@@ -293,14 +310,18 @@ fn eight_worker_restart_with_100ms_spawn_delay_beats_serial_baseline() {
293
310
  8,
294
311
  "R4 setup: timing smoke must exercise all 8 workers; calls={calls:?}"
295
312
  );
296
- let serial_baseline = delay * calls.len() as u32;
297
313
  assert!(
298
- serial_baseline >= Duration::from_millis(800),
299
- "R4 setup: serial baseline must represent 8 workers * 100ms; baseline={serial_baseline:?}"
314
+ has_overlap(
315
+ &calls
316
+ .iter()
317
+ .filter(|call| call.kind == "spawn_into")
318
+ .collect::<Vec<_>>(),
319
+ ),
320
+ "R4: bounded-concurrency restart must overlap spawn calls; calls={calls:?}"
300
321
  );
301
322
  assert!(
302
- elapsed < serial_baseline - Duration::from_millis(150),
303
- "R4: bounded-concurrency restart should complete below the serial spawn sum; elapsed={elapsed:?} serial_baseline={serial_baseline:?} calls={calls:?}"
323
+ transport.max_in_flight() >= 2,
324
+ "R4: max in-flight spawn count must prove overlap; calls={calls:?}"
304
325
  );
305
326
  assert_phase_events(&case.events(), "restart.phase", &["spawn_all", "completed"]);
306
327
  }
@@ -583,6 +604,8 @@ struct TransportState {
583
604
  calls: Vec<SpawnCall>,
584
605
  panes: BTreeMap<String, PaneInfo>,
585
606
  next_pane: usize,
607
+ in_flight: usize,
608
+ max_in_flight: usize,
586
609
  }
587
610
 
588
611
  #[derive(Clone)]
@@ -625,6 +648,10 @@ impl StartupLatencyTransport {
625
648
  self.state.lock().unwrap().calls.clone()
626
649
  }
627
650
 
651
+ fn max_in_flight(&self) -> usize {
652
+ self.state.lock().unwrap().max_in_flight
653
+ }
654
+
628
655
  fn pane_snapshot(&self) -> Vec<Value> {
629
656
  self.state
630
657
  .lock()
@@ -648,11 +675,17 @@ impl StartupLatencyTransport {
648
675
  window: &WindowName,
649
676
  ) -> Result<SpawnResult, TransportError> {
650
677
  let start = Instant::now();
678
+ {
679
+ let mut state = self.state.lock().unwrap();
680
+ state.in_flight += 1;
681
+ state.max_in_flight = state.max_in_flight.max(state.in_flight);
682
+ }
651
683
  if !self.spawn_delay.is_zero() {
652
684
  std::thread::sleep(self.spawn_delay);
653
685
  }
654
686
  let end = Instant::now();
655
687
  let mut state = self.state.lock().unwrap();
688
+ state.in_flight -= 1;
656
689
  let pane_id = PaneId::new(format!("%{}", state.next_pane));
657
690
  state.next_pane += 1;
658
691
  state.calls.push(SpawnCall {
@@ -1,3 +1,7 @@
1
+ //! purpose: contracts that tests touching product side effects stay inside one hermetic HOME/registry/socket/env boundary
2
+ //! contract: R1-R6 source guards; R6 needles match as identifiers, not as substrings of longer names
3
+ //! boundary: does not own HermeticTestEnv; does not drop or whitelist R6 needles
4
+ //!
1
5
  //! task#8 RED contract: tests that exercise Team Agent product side effects
2
6
  //! must run inside one HOME/registry/socket/env hermetic boundary.
3
7
  //!
@@ -233,6 +237,40 @@ fn r6_static_guard_rejects_dangerous_tests_without_hermetic_boundary() {
233
237
  );
234
238
  }
235
239
 
240
+ #[test]
241
+ fn r6_mcp_tool_name_literal_is_not_message_plane_send() {
242
+ let source = r#"
243
+ assert!(prompt.contains("mcp__team_orchestrator__send_message"));
244
+ assert!(prompt.contains("team_orchestrator__send_message"));
245
+ "#;
246
+ assert!(
247
+ static_guard_offenders([(
248
+ "synthetic_mcp_tool_name_literal.rs".to_string(),
249
+ source.to_string()
250
+ )])
251
+ .is_empty(),
252
+ "MCP tool-name literals are not the send_message surface"
253
+ );
254
+ }
255
+
256
+ #[test]
257
+ fn r6_bare_send_message_identifier_without_hermetic_is_still_offender() {
258
+ let source = r#"
259
+ fn tap() {
260
+ let _ = send_message("to", "body");
261
+ }
262
+ "#;
263
+ let offenders = static_guard_offenders([(
264
+ "synthetic_bare_send_message.rs".to_string(),
265
+ source.to_string(),
266
+ )]);
267
+ assert!(
268
+ offenders.iter().any(|offender| offender.path == "synthetic_bare_send_message.rs"
269
+ && offender.signals.contains(&"send_message")),
270
+ "bare send_message identifier without HermeticTestEnv must remain an offender; offenders={offenders:?}"
271
+ );
272
+ }
273
+
236
274
  fn assert_contains_all(label: &str, source: &str, needles: &[&str], message: &str) {
237
275
  let missing = needles
238
276
  .iter()
@@ -315,10 +353,30 @@ fn dangerous_signals(source: &str) -> Vec<&'static str> {
315
353
  "attach-leader",
316
354
  ]
317
355
  .into_iter()
318
- .filter(|needle| source.contains(needle))
356
+ .filter(|needle| contains_identifier(source, needle))
319
357
  .collect()
320
358
  }
321
359
 
360
+ fn contains_identifier(source: &str, ident: &str) -> bool {
361
+ let bytes = source.as_bytes();
362
+ let mut start = 0;
363
+ while let Some(rel) = source[start..].find(ident) {
364
+ let idx = start + rel;
365
+ let before_ok = idx == 0 || !is_ident_char(bytes[idx - 1]);
366
+ let after = idx + ident.len();
367
+ let after_ok = after == bytes.len() || !is_ident_char(bytes[after]);
368
+ if before_ok && after_ok {
369
+ return true;
370
+ }
371
+ start = idx + 1;
372
+ }
373
+ false
374
+ }
375
+
376
+ fn is_ident_char(b: u8) -> bool {
377
+ matches!(b, b'0'..=b'9' | b'A'..=b'Z' | b'a'..=b'z' | b'_')
378
+ }
379
+
322
380
  fn dangerous_test_files() -> Vec<(String, String)> {
323
381
  let root = repo_root();
324
382
  let mut files = Vec::new();
@@ -114,7 +114,8 @@ fn worker_spawn_scrubs_cross_provider_process_identity_but_keeps_config_env() {
114
114
  ("NODE_EXTRA_CA_CERTS", "/canary/ca.pem"),
115
115
  ]);
116
116
 
117
- let launch_team = compiled_team_dir("cross-launch", &[("worker_a", "Launch Worker")]);
117
+ let launch_team =
118
+ compiled_team_dir_for_provider("cross-launch", &[("worker_a", "Launch Worker")], "claude");
118
119
  let launch_transport = RecordingTransport::new().with_session_present(false);
119
120
  launch_with_transport(
120
121
  &launch_team.join("team.spec.yaml"),
@@ -125,16 +126,25 @@ fn worker_spawn_scrubs_cross_provider_process_identity_but_keeps_config_env() {
125
126
  )
126
127
  .expect("launch fixture should spawn worker");
127
128
 
128
- let restart_team = compiled_team_dir("cross-restart", &[("worker_a", "Restart Worker")]);
129
- seed_restart_state(&restart_team, "worker_a");
129
+ let restart_team = compiled_team_dir_for_provider(
130
+ "cross-restart",
131
+ &[("worker_a", "Restart Worker")],
132
+ "claude",
133
+ );
134
+ seed_restart_state_as(&restart_team, "worker_a", "claude");
130
135
  let restart_transport = RecordingTransport::new().with_session_present(false);
131
136
  restart_with_transport(&restart_team, true, None, &restart_transport)
132
137
  .expect("restart fixture should spawn worker");
133
138
 
134
- let add_team = compiled_team_dir("cross-add", &[("worker_a", "Existing Worker")]);
135
- seed_running_add_agent_state(&add_team, "worker_a");
139
+ let add_team =
140
+ compiled_team_dir_for_provider("cross-add", &[("worker_a", "Existing Worker")], "claude");
141
+ seed_running_add_agent_state_as(&add_team, "worker_a", "claude");
136
142
  let role_file = add_team.parent().unwrap().join("worker_b.md");
137
- std::fs::write(&role_file, role_doc("worker_b", "Added Worker")).unwrap();
143
+ std::fs::write(
144
+ &role_file,
145
+ role_doc_for_provider("worker_b", "Added Worker", "claude"),
146
+ )
147
+ .unwrap();
138
148
  let add_transport = RecordingTransport::new().with_session_present(true);
139
149
  add_agent_with_transport(
140
150
  &add_team,
@@ -146,25 +156,10 @@ fn worker_spawn_scrubs_cross_provider_process_identity_but_keeps_config_env() {
146
156
  )
147
157
  .expect("add-agent fixture should spawn worker");
148
158
 
149
- let fork_team = compiled_team_dir("cross-fork", &[("worker_a", "Fork Source")]);
150
- seed_forkable_source_state(&fork_team, "worker_a");
151
- let fork_transport = RecordingTransport::new().with_session_present(true);
152
- fork_agent_with_transport(
153
- &fork_team,
154
- &AgentId::new("worker_a"),
155
- &AgentId::new("worker_fork"),
156
- None,
157
- false,
158
- None,
159
- &fork_transport,
160
- )
161
- .expect("fork-agent fixture should spawn worker");
162
-
163
159
  let failures = [
164
160
  ("quick-start launch", launch_transport.single_spawn()),
165
161
  ("restart", restart_transport.single_spawn()),
166
162
  ("add-agent", add_transport.single_spawn()),
167
- ("fork-agent", fork_transport.single_spawn()),
168
163
  ]
169
164
  .into_iter()
170
165
  .flat_map(|(surface, spawn)| cross_provider_identity_failures(surface, &spawn))
@@ -177,6 +172,36 @@ fn worker_spawn_scrubs_cross_provider_process_identity_but_keeps_config_env() {
177
172
  );
178
173
  }
179
174
 
175
+ #[test]
176
+ #[serial(env)]
177
+ fn worker_spawn_codex_in_window_fork_reports_unverified_not_unsupported() {
178
+ // 已废除的行为:旧实现把 codex in-window fork 当成已验证能力(或报「不支持」),此断言证明它确实没了。
179
+ // 本轮裁定:codex 走这条路必须明确报「未验证」,不是「不支持」。
180
+ let _home = hermetic_guard::HermeticTestEnv::enter("codex-fork-unverified");
181
+ let team = compiled_team_dir("codex-fork-tombstone", &[("worker_a", "Fork Source")]);
182
+ seed_forkable_source_state(&team, "worker_a");
183
+ let transport = RecordingTransport::new().with_session_present(true);
184
+ let err = fork_agent_with_transport(
185
+ &team,
186
+ &AgentId::new("worker_a"),
187
+ &AgentId::new("worker_a"),
188
+ None,
189
+ false,
190
+ None,
191
+ &transport,
192
+ )
193
+ .expect_err("codex in-window fork must refuse as unverified");
194
+ let text = err.to_string();
195
+ assert!(
196
+ text.contains("未验证") && text.contains("unverified"),
197
+ "codex in-window fork must report 未验证; err={text}"
198
+ );
199
+ assert!(
200
+ !text.contains("不支持") && !text.contains("does not support"),
201
+ "the ruling is unverified, not unsupported; err={text}"
202
+ );
203
+ }
204
+
180
205
  #[test]
181
206
  #[serial(env)]
182
207
  fn worker_spawn_stays_out_of_coordinator_tick_and_daemon_preserves_parent_env() {
@@ -304,17 +329,23 @@ fn cross_provider_identity_failures(surface: &str, spawn: &RecordedSpawn) -> Vec
304
329
  }
305
330
 
306
331
  fn compiled_team_dir(label: &str, agents: &[(&str, &str)]) -> PathBuf {
332
+ compiled_team_dir_for_provider(label, agents, "codex")
333
+ }
334
+
335
+ fn compiled_team_dir_for_provider(label: &str, agents: &[(&str, &str)], provider: &str) -> PathBuf {
307
336
  let team = tmp_dir(label).join("teamdir");
308
337
  std::fs::create_dir_all(team.join("agents")).unwrap();
309
338
  std::fs::write(
310
339
  team.join("TEAM.md"),
311
- "---\nname: envteam\nobjective: Worker spawn env contract.\nprovider: codex\n---\n\nTeam.\n",
340
+ format!(
341
+ "---\nname: envteam\nobjective: Worker spawn env contract.\nprovider: {provider}\n---\n\nTeam.\n"
342
+ ),
312
343
  )
313
344
  .unwrap();
314
345
  for (name, role) in agents {
315
346
  std::fs::write(
316
347
  team.join("agents").join(format!("{name}.md")),
317
- role_doc(name, role),
348
+ role_doc_for_provider(name, role, provider),
318
349
  )
319
350
  .unwrap();
320
351
  }
@@ -328,12 +359,20 @@ fn compiled_team_dir(label: &str, agents: &[(&str, &str)]) -> PathBuf {
328
359
  }
329
360
 
330
361
  fn role_doc(name: &str, role: &str) -> String {
362
+ role_doc_for_provider(name, role, "codex")
363
+ }
364
+
365
+ fn role_doc_for_provider(name: &str, role: &str, provider: &str) -> String {
331
366
  format!(
332
- "---\nname: {name}\nrole: {role}\nprovider: codex\nmodel: codex-test-model\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{role}.\n"
367
+ "---\nname: {name}\nrole: {role}\nprovider: {provider}\nmodel: {provider}-test-model\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\n{role}.\n"
333
368
  )
334
369
  }
335
370
 
336
371
  fn seed_restart_state(team: &Path, agent_id: &str) {
372
+ seed_restart_state_as(team, agent_id, "codex");
373
+ }
374
+
375
+ fn seed_restart_state_as(team: &Path, agent_id: &str, provider: &str) {
337
376
  team_agent::state::persist::save_runtime_state(
338
377
  team,
339
378
  &json!({
@@ -344,7 +383,7 @@ fn seed_restart_state(team: &Path, agent_id: &str) {
344
383
  "agents": {
345
384
  agent_id: {
346
385
  "status": "running",
347
- "provider": "codex",
386
+ "provider": provider,
348
387
  "role": "Restart Worker",
349
388
  "tools": ["mcp_team"],
350
389
  "window": agent_id,
@@ -360,6 +399,10 @@ fn seed_restart_state(team: &Path, agent_id: &str) {
360
399
  }
361
400
 
362
401
  fn seed_running_add_agent_state(team: &Path, agent_id: &str) {
402
+ seed_running_add_agent_state_as(team, agent_id, "codex");
403
+ }
404
+
405
+ fn seed_running_add_agent_state_as(team: &Path, agent_id: &str, provider: &str) {
363
406
  let workspace = team_agent::model::paths::team_workspace(team).unwrap();
364
407
  team_agent::state::persist::save_runtime_state(
365
408
  &workspace,
@@ -371,7 +414,7 @@ fn seed_running_add_agent_state(team: &Path, agent_id: &str) {
371
414
  "agents": {
372
415
  agent_id: {
373
416
  "status": "running",
374
- "provider": "codex",
417
+ "provider": provider,
375
418
  "role": "Existing Worker",
376
419
  "tools": ["mcp_team"],
377
420
  "window": agent_id,
@@ -45,30 +45,44 @@ pub(crate) fn test_binary_path() -> &'static str {
45
45
  .as_str()
46
46
  }
47
47
 
48
+ mod acceptance_b_batch_red;
48
49
  mod agent_ops;
49
- mod core;
50
- mod lane_ops;
51
- mod launch_spawn;
52
- mod lifecycle_lock;
53
- mod main_preserved;
54
- mod phase_b_contracts;
55
- mod phase_golden;
56
- mod restart;
57
50
  mod b0_legacy_snapshot_nonauthority_contract;
58
51
  mod b0_reader_hideone_audit_contract;
59
52
  mod behavioral_diff_264_red;
53
+ mod claude_compatible_config_red;
60
54
  mod claude_profile_launch_red;
55
+ mod clone_agent_preserves_source_tools;
56
+ mod clone_fork_copilot_perms_red;
61
57
  mod codex_weak_window_attribution_red;
58
+ mod communication_mode_runtime_contract_red;
62
59
  mod copilot_provider_red;
63
- mod display_adaptive_red;
60
+ mod core;
64
61
  mod core_034_real_red;
62
+ mod cursor_mcp_overlay;
63
+ mod cursor_require_explicit_model_red;
64
+ mod cursor_restart_resume_red;
65
+ mod display_adaptive_red;
65
66
  mod f032_startup_prompt_best_effort_red;
67
+ mod g1_silent_faces;
68
+ mod grok_effort_argv_red;
69
+ mod grok_mcp_overlay_red;
70
+ mod grok_require_explicit_model_red;
71
+ mod grok_restart_resume_red;
66
72
  mod harvest2_a_batch_red;
67
73
  mod host_cotenant_death_p0_contract;
74
+ mod lane_ops;
75
+ mod launch_spawn;
76
+ mod lifecycle_lock;
68
77
  mod lifecycle_rollback_red;
78
+ mod main_preserved;
79
+ mod mcp_tool_name_format_red;
80
+ mod phase_b_contracts;
81
+ mod phase_golden;
69
82
  mod quick_start_worker_readiness_red;
70
83
  mod realmachine_clusters_1_6_red;
71
84
  mod realmachine_residual_g1_g4_red;
85
+ mod restart;
72
86
  mod restart_build_before_destroy_0540_contract;
73
87
  mod restart_liveness_red;
74
88
  mod restart_rebind_hotfix_252_red;
@@ -88,7 +102,3 @@ mod test_isolation_escape_contract;
88
102
  mod upgrade_compat_0211_red;
89
103
  mod verify_rs031_window_consistency_red;
90
104
  mod worker_spawn_env_red;
91
- mod acceptance_b_batch_red;
92
- mod claude_compatible_config_red;
93
- mod communication_mode_runtime_contract_red;
94
- mod clone_fork_copilot_perms_red;
@@ -1,3 +1,27 @@
1
+ //! ---
2
+ //! purpose: lifecycle 的数据类型集中定义,newtype、枚举、报告结构、错误与事件名常量
3
+ //! contract:
4
+ //! provides:
5
+ //! - name: PlanId
6
+ //! what: 受正则约束的 plan 标识,构造时拒斜杠空格与路径穿越
7
+ //! - name: PlanCondition
8
+ //! what: plan 推进条件的封闭文法,只认 any 与 report_result 字段相等
9
+ //! - name: LifecycleError
10
+ //! what: 本模块所有对外错误的统一枚举
11
+ //! - name: CoordinatorStartSummary
12
+ //! what: coordinator 启动结果在 lifecycle 报告里的投影
13
+ //! - name: event_names
14
+ //! what: lifecycle/restart/display 稳定事件名常量集
15
+ //! depends:
16
+ //! - crate::model::ids
17
+ //! - crate::provider
18
+ //! - crate::transport
19
+ //! - crate::coordinator
20
+ //! boundary:
21
+ //! - 只定义类型与解析,不做 IO、不 spawn、不落盘
22
+ //! - 事件名只声明常量,实际写事件由各行为文件负责
23
+ //! maturity: wired
24
+ //! ---
1
25
  //! lifecycle 数据类型:newtype / enum / data struct / error / outcome-report 集中定义。
2
26
 
3
27
  use std::collections::BTreeMap;
@@ -40,6 +64,9 @@ pub mod event_names {
40
64
  pub const DISPLAY_ADAPTIVE_BLOCKED: &str = "display.adaptive_blocked";
41
65
  pub const DISPLAY_ADAPTIVE_REBUILT: &str = "display.adaptive_rebuilt";
42
66
  pub const DISPLAY_ADAPTIVE_CLOSED: &str = "display.adaptive_closed";
67
+ /// Directory-scoped grok toml dropped leftover per-seat `TEAM_AGENT_*` keys.
68
+ /// Fields: `path` (file), `keys` (names only — never values).
69
+ pub const GROK_TOML_PER_SEAT_KEYS_CLEARED: &str = "lifecycle.grok_toml.per_seat_keys_cleared";
43
70
  }
44
71
 
45
72
  // ===========================================================================
@@ -54,6 +81,14 @@ pub mod event_names {
54
81
  pub struct PlanId(String);
55
82
 
56
83
  impl PlanId {
84
+ /// ---
85
+ /// purpose: 校验并构造 PlanId
86
+ /// params:
87
+ /// raw: 原始 plan 名,首字符须字母数字,其余只许字母数字与下划线点横线,长度不超过 64
88
+ /// returns: 通过校验的 PlanId
89
+ /// errors: 不满足上述形状时返回 InvalidPlanId
90
+ /// contract_id: lifecycle.types.plan_id_parse
91
+ /// ---
57
92
  /// `sanitize_plan_id`(`orchestrator/state.py:18`):正则校验,失败 → `Err`。
58
93
  pub fn parse(raw: &str) -> Result<Self, LifecycleError> {
59
94
  let mut chars = raw.chars();
@@ -70,6 +105,10 @@ impl PlanId {
70
105
  )))
71
106
  }
72
107
  }
108
+ /// ---
109
+ /// purpose: 取 PlanId 的原始字符串
110
+ /// returns: 构造时保存的原文
111
+ /// ---
73
112
  pub fn as_str(&self) -> &str {
74
113
  &self.0
75
114
  }
@@ -92,6 +131,12 @@ pub struct CoordinatorStartSummary {
92
131
  }
93
132
 
94
133
  impl CoordinatorStartSummary {
134
+ /// ---
135
+ /// purpose: 把 coordinator 的 StartReport 投影成 lifecycle 报告用的摘要
136
+ /// params:
137
+ /// report: coordinator 启动返回的原始报告
138
+ /// returns: 摘要,status 已转成稳定 wire 字符串,binary_identity_relation 原样带出
139
+ /// ---
95
140
  pub fn from_start_report(report: &crate::coordinator::StartReport) -> Self {
96
141
  Self {
97
142
  ok: report.ok,
@@ -117,6 +162,10 @@ fn coordinator_start_status_wire(status: crate::coordinator::StartOutcome) -> &'
117
162
  }
118
163
  }
119
164
 
165
+ /// ---
166
+ /// purpose: 把 coordinator 启动摘要序列化成 JSON 对象
167
+ /// returns: 含 ok、status、pid、binary 路径与版本、rotation_reason、binary_identity_relation 的 JSON
168
+ /// ---
120
169
  pub fn coordinator_start_summary_value(summary: &CoordinatorStartSummary) -> serde_json::Value {
121
170
  serde_json::json!({
122
171
  "ok": summary.ok,
@@ -142,6 +191,10 @@ impl std::fmt::Display for PlanId {
142
191
  pub struct DisplaySessionName(pub String);
143
192
 
144
193
  impl DisplaySessionName {
194
+ /// ---
195
+ /// purpose: 取 ghostty 派生 display session 名的原始字符串
196
+ /// returns: 内部保存的名字
197
+ /// ---
145
198
  pub fn as_str(&self) -> &str {
146
199
  &self.0
147
200
  }
@@ -353,6 +406,14 @@ pub enum PlanCondition {
353
406
  }
354
407
 
355
408
  impl PlanCondition {
409
+ /// ---
410
+ /// purpose: 解析 plan 推进条件的封闭文法
411
+ /// params:
412
+ /// expr: any,或 report_result.<字段> == <值>,值须带单引号或双引号
413
+ /// returns: Any 或 FieldEq
414
+ /// errors: 前缀不对、缺等号、字段名非法或值未带引号时返回 InvalidPlan
415
+ /// contract_id: lifecycle.types.plan_condition_parse
416
+ /// ---
356
417
  /// 解析封闭条件文法。越界 raise `InvalidPlan`(`orchestrator/plan.py:_is_supported_condition`)。
357
418
  pub fn parse(expr: &str) -> Result<Self, LifecycleError> {
358
419
  let trimmed = expr.trim();