@team-agent/installer 0.5.49 → 0.5.51

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 (90) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +6 -4
  4. package/crates/team-agent/src/cli/emit.rs +91 -39
  5. package/crates/team-agent/src/cli/mod.rs +33 -15
  6. package/crates/team-agent/src/cli/named_address.rs +82 -53
  7. package/crates/team-agent/src/cli/send/coordinator.rs +163 -0
  8. package/crates/team-agent/src/cli/send/mailbox.rs +99 -0
  9. package/crates/team-agent/src/cli/send/persist.rs +154 -0
  10. package/crates/team-agent/src/cli/send/presentation.rs +333 -0
  11. package/crates/team-agent/src/cli/send/resolve.rs +361 -0
  12. package/crates/team-agent/src/cli/send.rs +103 -1308
  13. package/crates/team-agent/src/cli/spec.rs +2 -2
  14. package/crates/team-agent/src/cli/status_port/agents.rs +358 -0
  15. package/crates/team-agent/src/cli/status_port/approvals.rs +79 -0
  16. package/crates/team-agent/src/cli/status_port/compact.rs +207 -0
  17. package/crates/team-agent/src/cli/status_port/format.rs +145 -0
  18. package/crates/team-agent/src/cli/status_port/inbox.rs +36 -0
  19. package/crates/team-agent/src/cli/status_port/runtime.rs +195 -0
  20. package/crates/team-agent/src/cli/status_port/snapshot.rs +181 -0
  21. package/crates/team-agent/src/cli/status_port/store.rs +412 -0
  22. package/crates/team-agent/src/cli/status_port/tests.rs +54 -0
  23. package/crates/team-agent/src/cli/status_port.rs +47 -1548
  24. package/crates/team-agent/src/cli/tests/leader_watch.rs +1 -1
  25. package/crates/team-agent/src/cli/tests/named_address.rs +9 -7
  26. package/crates/team-agent/src/cli/tests/run_delegation.rs +2 -3
  27. package/crates/team-agent/src/cli/tests/status_send.rs +17 -33
  28. package/crates/team-agent/src/cli/types.rs +5 -8
  29. package/crates/team-agent/src/coordinator/conpty_shim.rs +34 -30
  30. package/crates/team-agent/src/coordinator/steps/abnormal.rs +135 -13
  31. package/crates/team-agent/src/coordinator/tick.rs +37 -0
  32. package/crates/team-agent/src/db/agent_health_capture.rs +18 -13
  33. package/crates/team-agent/src/db/message_store.rs +154 -44
  34. package/crates/team-agent/src/event_log.rs +73 -0
  35. package/crates/team-agent/src/leader/start.rs +28 -4
  36. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +424 -0
  37. package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +297 -0
  38. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +160 -0
  39. package/crates/team-agent/src/lifecycle/launch/approval.rs +134 -0
  40. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +492 -0
  41. package/crates/team-agent/src/lifecycle/launch/fork_state.rs +297 -0
  42. package/crates/team-agent/src/lifecycle/launch/identity.rs +372 -0
  43. package/crates/team-agent/src/lifecycle/launch/layout.rs +313 -0
  44. package/crates/team-agent/src/lifecycle/launch/leader_context.rs +478 -0
  45. package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +201 -0
  46. package/crates/team-agent/src/lifecycle/launch/ownership.rs +66 -0
  47. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +477 -0
  48. package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +278 -0
  49. package/crates/team-agent/src/lifecycle/launch/readiness.rs +123 -0
  50. package/crates/team-agent/src/lifecycle/launch/spawn.rs +377 -0
  51. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +434 -0
  52. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +499 -0
  53. package/crates/team-agent/src/lifecycle/launch/worker_env.rs +438 -0
  54. package/crates/team-agent/src/lifecycle/launch.rs +119 -5351
  55. package/crates/team-agent/src/lifecycle/restart/agent.rs +44 -26
  56. package/crates/team-agent/src/lifecycle/restart/common.rs +53 -27
  57. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +67 -26
  58. package/crates/team-agent/src/lifecycle/restart/remove.rs +435 -72
  59. package/crates/team-agent/src/lifecycle/restart.rs +1 -1
  60. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +575 -17
  61. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +55 -2
  62. package/crates/team-agent/src/lifecycle/tests/lifecycle_lock.rs +24 -1
  63. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +1 -1
  64. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +30 -7
  65. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +8 -4
  66. package/crates/team-agent/src/mcp_server/mod.rs +2 -2
  67. package/crates/team-agent/src/mcp_server/tests/send.rs +22 -15
  68. package/crates/team-agent/src/mcp_server/tests/wire.rs +6 -0
  69. package/crates/team-agent/src/mcp_server/tools.rs +26 -15
  70. package/crates/team-agent/src/mcp_server/wire.rs +2 -18
  71. package/crates/team-agent/src/messaging/activity.rs +4 -2
  72. package/crates/team-agent/src/messaging/address.rs +86 -0
  73. package/crates/team-agent/src/messaging/delivery.rs +165 -39
  74. package/crates/team-agent/src/messaging/helpers.rs +17 -13
  75. package/crates/team-agent/src/messaging/leader_receiver.rs +60 -35
  76. package/crates/team-agent/src/messaging/mod.rs +11 -2
  77. package/crates/team-agent/src/messaging/persist.rs +309 -0
  78. package/crates/team-agent/src/messaging/results.rs +16 -24
  79. package/crates/team-agent/src/messaging/scheduler.rs +4 -2
  80. package/crates/team-agent/src/messaging/selftest.rs +19 -12
  81. package/crates/team-agent/src/messaging/send.rs +133 -58
  82. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +305 -0
  83. package/crates/team-agent/src/messaging/tests/mod.rs +1 -0
  84. package/crates/team-agent/src/messaging/tests/runtime.rs +38 -17
  85. package/crates/team-agent/src/messaging/watchers.rs +13 -3
  86. package/crates/team-agent/src/redaction.rs +72 -2
  87. package/crates/team-agent/src/state/persist.rs +2 -1
  88. package/crates/team-agent/src/state/repository/tests.rs +47 -0
  89. package/crates/team-agent/src/state/repository.rs +59 -16
  90. package/package.json +4 -4
@@ -884,26 +884,38 @@ pub(super) fn stop_agent_at_paths(
884
884
  transport: &dyn crate::transport::Transport,
885
885
  ) -> Result<StopAgentReport, LifecycleError> {
886
886
  // golden operations.py:64-66: resolve_team_scoped_state -> owner gate, BEFORE the unknown-worker raise.
887
- let mut state = resolve_team_scoped_state_or_refuse(workspace, team)?;
888
- crate::lifecycle::launch::ensure_owner_allowed_for_state(&state, Some(agent_id))?;
889
- let spec = load_team_spec(spec_workspace)?;
890
- let agent = find_spec_agent(&spec, agent_id).ok_or_else(|| unknown_worker(agent_id))?;
891
- let session_name = state_session_name_from_spec(&state, &spec);
892
- let window = state
893
- .get("agents")
894
- .and_then(|v| v.get(agent_id.as_str()))
895
- .and_then(|v| v.get("window"))
896
- .and_then(|v| v.as_str())
897
- .filter(|s| !s.is_empty())
898
- .unwrap_or_else(|| agent_id.as_str())
899
- .to_string();
900
- let pane_id = state
901
- .get("agents")
902
- .and_then(|v| v.get(agent_id.as_str()))
903
- .and_then(|v| v.get("pane_id"))
904
- .and_then(|v| v.as_str())
905
- .filter(|pane| !pane.is_empty())
906
- .map(crate::transport::PaneId::new);
887
+ let seat = super::remove::resolve_seat(workspace, spec_workspace, agent_id, team, transport)?;
888
+ if seat.consistency == super::remove::SeatConsistency::Absent {
889
+ return Err(unknown_worker(agent_id));
890
+ }
891
+ let mut state = seat.state;
892
+ let spec = seat.spec;
893
+ let state_agent = state.get("agents").and_then(|v| v.get(agent_id.as_str()));
894
+ // Persisted-only seats must remain stoppable. Build the minimal provider
895
+ // projection consumed by mark_agent_stopped instead of requiring a spec
896
+ // row that is precisely what recovery is repairing.
897
+ let fallback_agent = YamlValue::Map(vec![(
898
+ "provider".to_string(),
899
+ YamlValue::Str(
900
+ state_agent
901
+ .and_then(|agent| agent.get("provider"))
902
+ .and_then(|value| value.as_str())
903
+ .unwrap_or("codex")
904
+ .to_string(),
905
+ ),
906
+ )]);
907
+ let agent = find_spec_agent(&spec, agent_id).unwrap_or(&fallback_agent);
908
+ let session_name = seat.session;
909
+ let window = seat.window;
910
+ let pane_id = seat.physical.map(|pane| pane.pane_id).or_else(|| {
911
+ state
912
+ .get("agents")
913
+ .and_then(|agents| agents.get(agent_id.as_str()))
914
+ .and_then(|agent| agent.get("pane_id"))
915
+ .and_then(serde_json::Value::as_str)
916
+ .filter(|pane| !pane.is_empty())
917
+ .map(crate::transport::PaneId::new)
918
+ });
907
919
  let target_str = pane_id
908
920
  .as_ref()
909
921
  .map(|pane| pane.as_str().to_string())
@@ -995,12 +1007,15 @@ pub(super) fn stop_agent_at_paths(
995
1007
  }
996
1008
  close_agent_display(&mut state, agent_id);
997
1009
  mark_agent_stopped(&mut state, agent_id, agent, &window)?;
1010
+ let team_key = restart_projection_team_key(&state, team);
998
1011
  // golden operations.py:95: save_team_scoped_state (team projection) — NOT a raw save, so a
999
1012
  // multi-team workspace keeps the other teams' persisted runtime state instead of being clobbered.
1000
- crate::state::projection::save_team_scoped_state_with_lifecycle_topology_authority(
1001
- workspace,
1013
+ crate::state::repository::StateRepository::new(workspace).save(
1014
+ crate::state::repository::StateWriteIntent::StopAgent {
1015
+ team_key: &team_key,
1016
+ agent_id: agent_id.as_str(),
1017
+ },
1002
1018
  &state,
1003
- &[agent_id.as_str()],
1004
1019
  )
1005
1020
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
1006
1021
  // golden operations.py:96-99: snapshot (side-effect), then state_file = write_team_state path.
@@ -1447,10 +1462,13 @@ fn reset_agent_at_paths(
1447
1462
  sync_restart_team_projections(&mut state, &team_key);
1448
1463
  // golden operations.py (reset): save_team_scoped_state on the team projection — same multi-team
1449
1464
  // preservation as stop, not a raw save_runtime_state.
1450
- crate::state::projection::save_team_scoped_state_with_tombstone_lifecycle_topology_authority(
1451
- workspace,
1465
+ crate::state::repository::StateRepository::new(workspace).save(
1466
+ crate::state::repository::StateWriteIntent::ResetAgent {
1467
+ team_key: &team_key,
1468
+ agent_id: agent_id.as_str(),
1469
+ discard_session,
1470
+ },
1452
1471
  &state,
1453
- &[agent_id.as_str()],
1454
1472
  )
1455
1473
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
1456
1474
  // golden operations.py:125: write_team_state after the discard-save (the intermediate stopped snapshot).
@@ -373,8 +373,8 @@ pub(super) fn spawn_agent_window(
373
373
  let tmux_endpoint = transport.tmux_endpoint();
374
374
  let event_log = crate::event_log::EventLog::new(workspace);
375
375
  let _ = event_log.write(
376
- "provider.worker.spawn_argv",
377
- serde_json::json!({
376
+ crate::event_log::PROVIDER_WORKER_SPAWN_ARGV,
377
+ crate::event_log::provider_worker_spawn_argv_fields(serde_json::json!({
378
378
  "agent_id": agent_id.as_str(),
379
379
  "provider": provider,
380
380
  "argv": plan.argv,
@@ -388,7 +388,7 @@ pub(super) fn spawn_agent_window(
388
388
  "source": "restart",
389
389
  "tmux_endpoint": tmux_endpoint,
390
390
  "tmux_endpoint_source": tmux_endpoint_source.unwrap_or("transport"),
391
- }),
391
+ })),
392
392
  );
393
393
  }
394
394
 
@@ -817,11 +817,13 @@ pub(super) fn save_restart_projected_state_with_capture_backfill_skip(
817
817
  topology_authority_agent_ids: &[&str],
818
818
  ) -> Result<(), LifecycleError> {
819
819
  sync_restart_team_projections(state, team_key);
820
- crate::state::projection::save_team_scoped_state_with_lifecycle_topology_authority_and_capture_backfill_skip(
821
- workspace,
820
+ crate::state::repository::StateRepository::new(workspace).save(
821
+ crate::state::repository::StateWriteIntent::RestartTeam {
822
+ team_key,
823
+ topology_authority_agent_ids,
824
+ skip_capture_backfill_agent_ids,
825
+ },
822
826
  state,
823
- skip_capture_backfill_agent_ids,
824
- topology_authority_agent_ids,
825
827
  )
826
828
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))
827
829
  }
@@ -1316,21 +1318,16 @@ pub(crate) fn converge_missing_provider_sessions(
1316
1318
  poll_interval,
1317
1319
  restart_required_missing_session_agent_ids,
1318
1320
  |progress| {
1319
- let pending_agent_ids = progress.pending_agent_ids.clone();
1320
1321
  write_session_convergence_progress_event(
1321
1322
  workspace,
1322
1323
  serde_json::json!({
1323
- "ts": chrono::Utc::now().to_rfc3339(),
1324
- "event": "provider.session.converging",
1325
1324
  "iteration": progress.iteration,
1326
1325
  "elapsed_ms": progress.elapsed_ms,
1327
1326
  "deadline_ms": progress.deadline_ms,
1328
1327
  "changed": progress.changed,
1329
1328
  "assigned": progress.assigned,
1330
1329
  "missing": progress.missing,
1331
- "required_missing": progress.required_missing_agent_ids.clone(),
1332
1330
  "required_missing_agent_ids": progress.required_missing_agent_ids,
1333
- "pending": pending_agent_ids,
1334
1331
  "pending_agent_ids": progress.pending_agent_ids,
1335
1332
  "candidate_count_by_agent": progress.candidate_count_by_agent,
1336
1333
  "remaining_ms": progress.remaining_ms,
@@ -1344,22 +1341,11 @@ pub(crate) fn converge_missing_provider_sessions(
1344
1341
 
1345
1342
  fn write_session_convergence_progress_event(
1346
1343
  workspace: &Path,
1347
- event: serde_json::Value,
1344
+ fields: serde_json::Value,
1348
1345
  ) -> Result<(), String> {
1349
- use std::io::Write as _;
1350
-
1351
- let path = workspace.join(".team").join("logs").join("events.jsonl");
1352
- if let Some(parent) = path.parent() {
1353
- std::fs::create_dir_all(parent).map_err(|e| e.to_string())?;
1354
- }
1355
- let line = serde_json::to_string(&event).map_err(|e| e.to_string())?;
1356
- let mut file = std::fs::OpenOptions::new()
1357
- .create(true)
1358
- .append(true)
1359
- .open(path)
1360
- .map_err(|e| e.to_string())?;
1361
- file.write_all(line.as_bytes())
1362
- .and_then(|_| file.write_all(b"\n"))
1346
+ crate::event_log::EventLog::new(workspace)
1347
+ .write(crate::event_log::PROVIDER_SESSION_CONVERGING, fields)
1348
+ .map(|_| ())
1363
1349
  .map_err(|e| e.to_string())
1364
1350
  }
1365
1351
 
@@ -1837,6 +1823,46 @@ mod e36_transcript_backing_tests {
1837
1823
  }
1838
1824
  }
1839
1825
 
1826
+ #[test]
1827
+ fn convergence_event_uses_central_scrub_and_preserves_required_failure() {
1828
+ let scratch = ScratchDir::new("event-log-central");
1829
+ let marker = "synthetic-convergence-marker";
1830
+ write_session_convergence_progress_event(
1831
+ scratch.path(),
1832
+ serde_json::json!({
1833
+ "diagnostic": format!("mcp_servers.demo.env.OPENAI_API_KEY=\"{marker}\""),
1834
+ "required_missing_agent_ids": ["worker"],
1835
+ "pending_agent_ids": ["worker"],
1836
+ }),
1837
+ )
1838
+ .expect("central EventLog write");
1839
+ let bytes = std::fs::read_to_string(
1840
+ scratch
1841
+ .path()
1842
+ .join(".team")
1843
+ .join("logs")
1844
+ .join("events.jsonl"),
1845
+ )
1846
+ .expect("events");
1847
+ assert!(!bytes.contains(marker));
1848
+ assert!(bytes.contains("[REDACTED]"));
1849
+
1850
+ let blocked = ScratchDir::new("event-log-required");
1851
+ std::fs::create_dir_all(
1852
+ blocked
1853
+ .path()
1854
+ .join(".team")
1855
+ .join("logs")
1856
+ .join("events.jsonl"),
1857
+ )
1858
+ .expect("occupy event path with directory");
1859
+ assert!(write_session_convergence_progress_event(
1860
+ blocked.path(),
1861
+ serde_json::json!({"pending_agent_ids": ["worker"]}),
1862
+ )
1863
+ .is_err());
1864
+ }
1865
+
1840
1866
  // E36 fix-B RED→GREEN: a real Claude worker that sent a message has its session
1841
1867
  // transcript landed at <projects_root>/<slug>/<session_id>.jsonl, but neither
1842
1868
  // rollout_path was persisted to state nor a session.captured event was logged.
@@ -1841,11 +1841,13 @@ fn save_restart_state_with_lifecycle_topology_authority_and_capture_backfill_ski
1841
1841
  .map(String::as_str)
1842
1842
  .collect::<Vec<_>>();
1843
1843
  sync_restart_team_projections(state, team_key);
1844
- crate::state::projection::save_team_scoped_state_with_lifecycle_topology_authority_and_capture_backfill_skip(
1845
- workspace,
1844
+ crate::state::repository::StateRepository::new(workspace).save(
1845
+ crate::state::repository::StateWriteIntent::RestartTeam {
1846
+ team_key,
1847
+ topology_authority_agent_ids: &topology_agent_ids,
1848
+ skip_capture_backfill_agent_ids: &skip_capture_backfill_agent_ids,
1849
+ },
1846
1850
  state,
1847
- &skip_capture_backfill_agent_ids,
1848
- &topology_agent_ids,
1849
1851
  )
1850
1852
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))
1851
1853
  }
@@ -1858,7 +1860,11 @@ fn save_restart_session_repairs(
1858
1860
  ) -> Result<(), LifecycleError> {
1859
1861
  let repairs = collect_session_repair_fields(state, agent_ids);
1860
1862
  sync_restart_team_projections(state, team_key);
1861
- match crate::state::projection::save_team_scoped_state(workspace, state) {
1863
+ let repository = crate::state::repository::StateRepository::new(workspace);
1864
+ match repository.save(
1865
+ crate::state::repository::StateWriteIntent::RestartSessionRepair { team_key },
1866
+ state,
1867
+ ) {
1862
1868
  Ok(()) => Ok(()),
1863
1869
  Err(crate::state::StateError::SaveConflict(_)) => {
1864
1870
  let mut latest =
@@ -1868,7 +1874,11 @@ fn save_restart_session_repairs(
1868
1874
  apply_session_repair_fields(&mut latest, agent_id, repair);
1869
1875
  }
1870
1876
  sync_restart_team_projections(&mut latest, team_key);
1871
- crate::state::projection::save_team_scoped_state(workspace, &latest)
1877
+ repository
1878
+ .save(
1879
+ crate::state::repository::StateWriteIntent::RestartSessionRepair { team_key },
1880
+ &latest,
1881
+ )
1872
1882
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
1873
1883
  *state = latest;
1874
1884
  Ok(())
@@ -2266,8 +2276,8 @@ fn write_fake_harness_spawn_argv_event(
2266
2276
  tmux_endpoint_source: Option<&str>,
2267
2277
  ) {
2268
2278
  let _ = crate::event_log::EventLog::new(workspace).write(
2269
- "provider.worker.spawn_argv",
2270
- serde_json::json!({
2279
+ crate::event_log::PROVIDER_WORKER_SPAWN_ARGV,
2280
+ crate::event_log::provider_worker_spawn_argv_fields(serde_json::json!({
2271
2281
  "agent_id": decision.agent_id.as_str(),
2272
2282
  "provider": agent_provider(agent),
2273
2283
  "argv": [],
@@ -2277,7 +2287,7 @@ fn write_fake_harness_spawn_argv_event(
2277
2287
  "source": "restart",
2278
2288
  "tmux_endpoint": transport.tmux_endpoint(),
2279
2289
  "tmux_endpoint_source": tmux_endpoint_source.unwrap_or("transport"),
2280
- }),
2290
+ })),
2281
2291
  );
2282
2292
  }
2283
2293
 
@@ -2924,15 +2934,7 @@ fn write_restart_resume_decision_event(
2924
2934
  forced_fresh_convergence: Option<&crate::session_capture::SessionConvergence>,
2925
2935
  unresumable: Option<&crate::lifecycle::types::UnresumableWorker>,
2926
2936
  ) -> Result<(), LifecycleError> {
2927
- use std::io::Write as _;
2928
-
2929
- let path = workspace.join(".team").join("logs").join("events.jsonl");
2930
- if let Some(parent) = path.parent() {
2931
- std::fs::create_dir_all(parent).map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
2932
- }
2933
2937
  let mut event = serde_json::json!({
2934
- "ts": chrono::Utc::now().to_rfc3339(),
2935
- "event": crate::lifecycle::types::event_names::RESTART_RESUME_DECISION,
2936
2938
  "worker_id": worker_id,
2937
2939
  "has_first_send_at": first_send_at.is_some(),
2938
2940
  "has_session_id": session_id.is_some(),
@@ -3039,15 +3041,12 @@ fn write_restart_resume_decision_event(
3039
3041
  }
3040
3042
  }
3041
3043
  }
3042
- let line =
3043
- serde_json::to_string(&event).map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
3044
- let mut file = std::fs::OpenOptions::new()
3045
- .create(true)
3046
- .append(true)
3047
- .open(&path)
3048
- .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
3049
- file.write_all(line.as_bytes())
3050
- .and_then(|_| file.write_all(b"\n"))
3044
+ crate::event_log::EventLog::new(workspace)
3045
+ .write(
3046
+ crate::lifecycle::types::event_names::RESTART_RESUME_DECISION,
3047
+ event,
3048
+ )
3049
+ .map(|_| ())
3051
3050
  .map_err(|e| LifecycleError::StatePersist(e.to_string()))
3052
3051
  }
3053
3052
 
@@ -3419,6 +3418,48 @@ mod tests {
3419
3418
  TransportError,
3420
3419
  };
3421
3420
 
3421
+ #[test]
3422
+ fn resume_decision_event_uses_central_scrub_and_preserves_required_failure() {
3423
+ let marker = "synthetic-resume-decision-marker";
3424
+ let workspace =
3425
+ std::env::temp_dir().join(format!("ta-resume-decision-event-{}", std::process::id()));
3426
+ let _ = std::fs::remove_dir_all(&workspace);
3427
+ write_restart_resume_decision_event(
3428
+ &workspace,
3429
+ "worker",
3430
+ Some(format!("mcp_servers.demo.env.OPENAI_API_KEY=\"{marker}\"")),
3431
+ None,
3432
+ false,
3433
+ "fresh_start",
3434
+ false,
3435
+ None,
3436
+ None,
3437
+ )
3438
+ .expect("central EventLog write");
3439
+ let bytes =
3440
+ std::fs::read_to_string(workspace.join(".team").join("logs").join("events.jsonl"))
3441
+ .expect("events");
3442
+ assert!(!bytes.contains(marker));
3443
+ assert!(bytes.contains("[REDACTED]"));
3444
+ let _ = std::fs::remove_dir_all(&workspace);
3445
+
3446
+ std::fs::create_dir_all(workspace.join(".team").join("logs").join("events.jsonl"))
3447
+ .expect("occupy event path with directory");
3448
+ let result = write_restart_resume_decision_event(
3449
+ &workspace,
3450
+ "worker",
3451
+ None,
3452
+ None,
3453
+ false,
3454
+ "fresh_start",
3455
+ false,
3456
+ None,
3457
+ None,
3458
+ );
3459
+ assert!(matches!(result, Err(LifecycleError::StatePersist(_))));
3460
+ let _ = std::fs::remove_dir_all(workspace);
3461
+ }
3462
+
3422
3463
  struct RespawnEpochTransport;
3423
3464
 
3424
3465
  impl Transport for RespawnEpochTransport {