@team-agent/installer 0.5.120 → 0.5.122

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 (29) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/intent/runtime.rs +46 -10
  4. package/crates/team-agent/src/cli/intent/runtime_tests.rs +114 -0
  5. package/crates/team-agent/src/cli/mod.rs +4 -1
  6. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +56 -0
  7. package/crates/team-agent/src/coordinator/tests/health_sync.rs +27 -8
  8. package/crates/team-agent/src/db/message_store.rs +65 -0
  9. package/crates/team-agent/src/lifecycle/launch/agent_state.rs +8 -0
  10. package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +111 -4
  11. package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +244 -82
  12. package/crates/team-agent/src/lifecycle/launch.rs +1 -0
  13. package/crates/team-agent/src/lifecycle/restart/agent.rs +3 -0
  14. package/crates/team-agent/src/lifecycle/restart/common.rs +10 -1
  15. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +5 -0
  16. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +16 -3
  17. package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +60 -17
  18. package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +162 -8
  19. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +55 -6
  20. package/crates/team-agent/src/mcp_server/mod.rs +2 -0
  21. package/crates/team-agent/src/mcp_server/readiness.rs +495 -0
  22. package/crates/team-agent/src/mcp_server/readiness_tests.rs +272 -0
  23. package/crates/team-agent/src/mcp_server/wire.rs +96 -11
  24. package/crates/team-agent/src/messaging/delivery.rs +62 -0
  25. package/crates/team-agent/src/messaging/delivery_consumption_tests.rs +12 -1
  26. package/crates/team-agent/src/messaging/tests/dup_inject.rs +89 -5
  27. package/crates/team-agent/src/messaging/tests/spine.rs +45 -14
  28. package/crates/team-agent/src/provider/adapter.rs +7 -0
  29. package/package.json +4 -4
@@ -26,17 +26,122 @@ use std::sync::atomic::{AtomicU64, Ordering};
26
26
 
27
27
  use crate::lifecycle::restart::classify_restart_plan_with_resume_validation;
28
28
  use crate::lifecycle::types::ResumeDecision;
29
- use crate::provider::session_scan::cursor::{
30
- cursor_session_archive_present, cursor_session_dir,
31
- };
29
+ use crate::model::ids::AgentId;
30
+ use crate::provider::session_scan::cursor::{cursor_session_archive_present, cursor_session_dir};
32
31
  use crate::state::persist::{load_runtime_state, save_runtime_state};
33
32
  use serial_test::serial;
34
- use crate::model::ids::AgentId;
35
33
  use team_agent::lifecycle::{
36
- fork_agent_with_transport, quick_start_with_transport_in_workspace, restart_with_transport,
34
+ cursor_mcp_project_dir, fork_agent_with_transport, quick_start_with_transport_in_workspace,
35
+ restart_with_transport,
37
36
  };
38
37
  use team_agent::transport::test_support::OfflineTransport;
39
38
 
39
+ #[test]
40
+ #[serial(env)]
41
+ fn issue345_dynamic_cursor_start_persists_its_generated_mcp_path() {
42
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-dynamic-mcp");
43
+ let (ws, _home, _guard, team) = seed_cursor("dynamic-mcp");
44
+ std::fs::write(
45
+ team.join("agents/cursor_writer.md"),
46
+ "---\nprovider: fake\ndangerously_skip_permissions: true\n---\nControl seat.\n",
47
+ )
48
+ .unwrap();
49
+ let transport = OfflineTransport::new();
50
+ quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
51
+ .unwrap();
52
+ let role = team.join("cursor_dynamic.md");
53
+ std::fs::write(&role, "---\nprovider: cursor_agent\nmodel: grok-4.7-high\nauth_mode: subscription\ndangerously_skip_permissions: true\n---\nDynamic Cursor seat.\n").unwrap();
54
+ crate::lifecycle::add_agent_with_transport(
55
+ &team,
56
+ &AgentId::new("cursor_dynamic"),
57
+ &role,
58
+ false,
59
+ Some("cursortm"),
60
+ &transport,
61
+ )
62
+ .unwrap();
63
+ let state = load_runtime_state(&ws).unwrap();
64
+ let expected = ws.join(".team/runtime/mcp/cursor_dynamic.json");
65
+ assert!(
66
+ expected.is_file(),
67
+ "positive control: spawn generated the real config"
68
+ );
69
+ assert_eq!(
70
+ state["agents"]["cursor_dynamic"]["mcp_config"].as_str(),
71
+ expected.to_str()
72
+ );
73
+ assert_eq!(
74
+ state["teams"]["cursortm"]["agents"]["cursor_dynamic"]["mcp_config"].as_str(),
75
+ expected.to_str()
76
+ );
77
+ let config: serde_json::Value =
78
+ serde_json::from_slice(&std::fs::read(&expected).unwrap()).unwrap();
79
+ assert_eq!(
80
+ config["mcpServers"]["team_orchestrator"]["env"]["TEAM_AGENT_ID"],
81
+ "cursor_dynamic"
82
+ );
83
+ assert_eq!(
84
+ config["mcpServers"]["team_orchestrator"]["env"]["TEAM_AGENT_OWNER_TEAM_ID"],
85
+ "cursortm"
86
+ );
87
+ }
88
+
89
+ #[test]
90
+ #[serial(env)]
91
+ fn issue345_cursor_wakeup_restores_missing_derived_mcp_path() {
92
+ let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-wakeup-mcp");
93
+ let (ws, _home, _guard, team) = seed_cursor("wakeup-mcp");
94
+ let transport = OfflineTransport::new();
95
+ quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
96
+ .unwrap();
97
+ crate::lifecycle::restart::stop_agent_with_transport(
98
+ &ws,
99
+ &AgentId::new("cursor_writer"),
100
+ Some("cursortm"),
101
+ &transport,
102
+ )
103
+ .unwrap();
104
+ let mut state = load_runtime_state(&ws).unwrap();
105
+ state["agents"]["cursor_writer"]
106
+ .as_object_mut()
107
+ .unwrap()
108
+ .remove("mcp_config");
109
+ state["teams"]["cursortm"]["agents"]["cursor_writer"]
110
+ .as_object_mut()
111
+ .unwrap()
112
+ .remove("mcp_config");
113
+ // Plant an actual legacy row; ordinary state saves intentionally backfill fields.
114
+ std::fs::write(
115
+ crate::state::persist::runtime_state_path(&ws),
116
+ serde_json::to_vec(&state).unwrap(),
117
+ )
118
+ .unwrap();
119
+ assert!(load_runtime_state(&ws).unwrap()["agents"]["cursor_writer"]
120
+ .get("mcp_config")
121
+ .is_none());
122
+ crate::lifecycle::restart::start_agent_with_transport(
123
+ &ws,
124
+ &AgentId::new("cursor_writer"),
125
+ false,
126
+ false,
127
+ true,
128
+ Some("cursortm"),
129
+ &transport,
130
+ )
131
+ .unwrap();
132
+ let after = load_runtime_state(&ws).unwrap();
133
+ let expected = ws.join(".team/runtime/mcp/cursor_writer.json");
134
+ assert!(expected.is_file());
135
+ assert_eq!(
136
+ after["agents"]["cursor_writer"]["mcp_config"].as_str(),
137
+ expected.to_str()
138
+ );
139
+ assert_eq!(
140
+ after["teams"]["cursortm"]["agents"]["cursor_writer"]["mcp_config"].as_str(),
141
+ expected.to_str()
142
+ );
143
+ }
144
+
40
145
  #[test]
41
146
  #[serial(env)]
42
147
  fn cursor_fresh_spawn_does_not_invent_session_id() {
@@ -86,11 +191,16 @@ fn cursor_restart_uses_resume_when_session_captured_and_archive_present() {
86
191
  quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &first)
87
192
  .expect("initial cursor start");
88
193
  let sid = "502896a1-72ba-4c53-9a86-b2da28780806";
194
+ let primary = cursor_mcp_project_dir(&ws, "cursor_writer").unwrap();
195
+ let primary_before = std::fs::canonicalize(&primary).unwrap();
89
196
  let dir = {
90
197
  let path = home
91
198
  .join(".cursor")
92
199
  .join("chats")
93
- .join("00a437742b92089861da7821a62f232a")
200
+ .join(format!(
201
+ "{:x}",
202
+ md5::compute(primary.to_string_lossy().as_bytes())
203
+ ))
94
204
  .join(sid);
95
205
  std::fs::create_dir_all(&path).unwrap();
96
206
  std::fs::write(path.join("store.db"), b"").unwrap();
@@ -100,8 +210,28 @@ fn cursor_restart_uses_resume_when_session_captured_and_archive_present() {
100
210
  assert_eq!(cursor_session_dir(sid).as_deref(), Some(dir.as_path()));
101
211
 
102
212
  let mut state = load_runtime_state(&ws).expect("load");
103
- patch_cursor_writer(&mut state, sid, &dir, &ws);
104
- save_runtime_state(&ws, &state).expect("save captured");
213
+ assert_eq!(
214
+ state["agents"]["cursor_writer"]["spawn_cwd"],
215
+ primary.to_string_lossy().as_ref()
216
+ );
217
+ patch_cursor_writer(&mut state, sid, &dir, &primary);
218
+ state["agents"]["cursor_writer"]
219
+ .as_object_mut()
220
+ .unwrap()
221
+ .remove("mcp_config");
222
+ state["teams"]["cursortm"]["agents"]["cursor_writer"]
223
+ .as_object_mut()
224
+ .unwrap()
225
+ .remove("mcp_config");
226
+ // Preserve the real captured SID while removing only the derived config path.
227
+ std::fs::write(
228
+ crate::state::persist::runtime_state_path(&ws),
229
+ serde_json::to_vec(&state).unwrap(),
230
+ )
231
+ .unwrap();
232
+ assert!(load_runtime_state(&ws).unwrap()["agents"]["cursor_writer"]
233
+ .get("mcp_config")
234
+ .is_none());
105
235
 
106
236
  seed_healthy_coordinator(&ws);
107
237
 
@@ -121,6 +251,30 @@ fn cursor_restart_uses_resume_when_session_captured_and_archive_present() {
121
251
  !argv.iter().any(|a| a == "--continue"),
122
252
  "resume argv must not use --continue; argv={argv:?}"
123
253
  );
254
+ let after = load_runtime_state(&ws).unwrap();
255
+ assert_eq!(
256
+ after["agents"]["cursor_writer"]["spawn_cwd"],
257
+ primary.to_string_lossy().as_ref()
258
+ );
259
+ assert_eq!(after["agents"]["cursor_writer"]["session_id"], sid);
260
+ let expected_config = ws.join(".team/runtime/mcp/cursor_writer.json");
261
+ assert_eq!(
262
+ after["agents"]["cursor_writer"]["mcp_config"].as_str(),
263
+ expected_config.to_str()
264
+ );
265
+ assert_eq!(
266
+ after["teams"]["cursortm"]["agents"]["cursor_writer"]["mcp_config"].as_str(),
267
+ expected_config.to_str()
268
+ );
269
+ assert_eq!(
270
+ cursor_mcp_project_dir(&ws, "cursor_writer").unwrap(),
271
+ primary_before
272
+ );
273
+ assert_eq!(argv_flag(&argv, "--workspace").as_deref(), primary.to_str());
274
+ assert!(argv_contains_adjacent(
275
+ &argv,
276
+ &["--add-dir", ws.to_str().unwrap()]
277
+ ));
124
278
  }
125
279
 
126
280
  #[test]
@@ -651,10 +651,10 @@ fn contract_b_delivery_gate_trusts_startup_handled_state_over_pane_scrollback()
651
651
  "SUB step2 probe: handled startup trust must deliver despite scrollback residue.",
652
652
  None,
653
653
  true,
654
- None,
654
+ Some("subprep-scrollback"),
655
655
  )
656
656
  .unwrap();
657
- let handled_state = json!({
657
+ let mut handled_state = json!({
658
658
  "session_name": "team-ta-subroot-SUB-PROBE-2-send-turn-idle",
659
659
  "agents": {
660
660
  "w1": {
@@ -670,9 +670,15 @@ fn contract_b_delivery_gate_trusts_startup_handled_state_over_pane_scrollback()
670
670
  }
671
671
  }
672
672
  });
673
+ #[cfg(unix)]
674
+ let pane = crate::mcp_server::readiness::test_ready_worker(
675
+ &handled_ws, &mut handled_state, "subprep-scrollback", "w1", "%7",
676
+ );
673
677
  team_agent::state::persist::save_runtime_state(&handled_ws, &handled_state).unwrap();
674
678
  let handled_transport =
675
679
  RecordingTransport::new(vec![SUBROOT_FULL_TRUST_PANE_AFTER_QUICKSTART.to_string()]);
680
+ #[cfg(unix)]
681
+ { handled_transport.state.lock().unwrap().carrier = pane.pane_pid; }
676
682
  let handled_log = EventLog::new(&handled_ws);
677
683
  let handled = deliver_pending_message(
678
684
  &handled_ws,
@@ -720,8 +726,12 @@ fn contract_b_step2_retry_delivers_queued_message_after_startup_trust_is_handled
720
726
  let ws = tmp_dir("b-step2-trust-retry");
721
727
  let mcp_config = ws.join(".team").join("runtime").join("mcp").join("w1.json");
722
728
  std::fs::create_dir_all(mcp_config.parent().unwrap()).unwrap();
723
- std::fs::write(&mcp_config, "{}").unwrap();
724
- let state = json!({
729
+ // This worker genuinely depends on MCP. Bind the controlled receipt to the
730
+ // live test carrier rather than bypassing readiness with an empty config.
731
+ let owner = "subprep-trust-retry";
732
+ let mut state = json!({
733
+ "team_key": owner,
734
+ "active_team_key": owner,
725
735
  "session_name": "team-ta-subroot-SUB-PROBE-2-send-turn-idle",
726
736
  "team_dir": ws.join("teamdir").to_string_lossy().to_string(),
727
737
  "agents": {
@@ -736,6 +746,21 @@ fn contract_b_step2_retry_delivers_queued_message_after_startup_trust_is_handled
736
746
  }
737
747
  }
738
748
  });
749
+ #[cfg(unix)]
750
+ let carrier = {
751
+ let carrier = crate::mcp_server::readiness::test_carrier_pid().unwrap();
752
+ state["agents"]["w1"]["pane_pid"] = json!(carrier);
753
+ state["agents"]["w1"]["spawned_at"] =
754
+ json!((chrono::Utc::now() - chrono::Duration::seconds(10)).to_rfc3339());
755
+ carrier
756
+ };
757
+ state["teams"] = json!({owner: state.clone()});
758
+ std::fs::write(&mcp_config, serde_json::to_vec(&json!({
759
+ "mcpServers": {"team_orchestrator": {
760
+ "command": std::env::current_exe().unwrap().canonicalize().unwrap(),
761
+ "env": {"TEAM_AGENT_WORKSPACE": ws.to_path_buf(), "TEAM_AGENT_ID": "w1", "TEAM_AGENT_OWNER_TEAM_ID": owner}
762
+ }}
763
+ })).unwrap()).unwrap();
739
764
  team_agent::state::persist::save_runtime_state(&ws, &state).unwrap();
740
765
 
741
766
  let store = MessageStore::open(&ws).unwrap();
@@ -748,7 +773,7 @@ fn contract_b_step2_retry_delivers_queued_message_after_startup_trust_is_handled
748
773
  "SUB step2 probe: reply with one sentence and call report_result exactly once.",
749
774
  None,
750
775
  true,
751
- None,
776
+ Some(owner),
752
777
  )
753
778
  .unwrap();
754
779
  let transport = RecordingTransport::new(vec![
@@ -759,6 +784,10 @@ fn contract_b_step2_retry_delivers_queued_message_after_startup_trust_is_handled
759
784
  ])
760
785
  .with_session_present(true)
761
786
  .with_windows(vec![WindowName::new("w1")]);
787
+ #[cfg(unix)]
788
+ {
789
+ transport.state.lock().unwrap().carrier = Some(carrier);
790
+ }
762
791
  let event_log = EventLog::new(&ws);
763
792
 
764
793
  let deferred =
@@ -787,6 +816,13 @@ fn contract_b_step2_retry_delivers_queued_message_after_startup_trust_is_handled
787
816
  "fixture sanity: delivery defer must be observable as delivery.deferred_startup_prompt"
788
817
  );
789
818
 
819
+ // No receipt existed during the actionable trust prompt. Once startup
820
+ // control can progress, model the successful live helper handshake before
821
+ // the coordinator retries this SAME queued row.
822
+ #[cfg(unix)]
823
+ {
824
+ crate::mcp_server::readiness::publish(&ws, owner, "w1", chrono::Utc::now()).unwrap();
825
+ }
790
826
  let coord = Coordinator::new(
791
827
  WorkspacePath::new(ws.to_path_buf()),
792
828
  Box::new(RealAdapterRegistry),
@@ -1045,6 +1081,7 @@ struct TransportState {
1045
1081
  session_present: bool,
1046
1082
  windows: Vec<WindowName>,
1047
1083
  spawn_argv: Vec<Vec<String>>,
1084
+ carrier: Option<u32>,
1048
1085
  }
1049
1086
 
1050
1087
  #[derive(Debug, Clone)]
@@ -1189,7 +1226,19 @@ impl Transport for RecordingTransport {
1189
1226
  }
1190
1227
 
1191
1228
  fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
1192
- Ok(Vec::new())
1229
+ Ok(self.state.lock().unwrap().carrier.map(|carrier| PaneInfo {
1230
+ pane_id: PaneId::new("%7"),
1231
+ session: SessionName::new("team-ta-subroot-SUB-PROBE-2-send-turn-idle"),
1232
+ window_index: None,
1233
+ window_name: Some(WindowName::new("w1")),
1234
+ pane_index: None,
1235
+ tty: None,
1236
+ current_command: None,
1237
+ current_path: None,
1238
+ active: true,
1239
+ pane_pid: Some(carrier),
1240
+ leader_env: BTreeMap::new(),
1241
+ }).into_iter().collect())
1193
1242
  }
1194
1243
 
1195
1244
  fn has_session(&self, _session: &SessionName) -> Result<bool, TransportError> {
@@ -89,6 +89,8 @@ use crate::messaging::{self, DeliveryOutcome, MessageTarget, SendOptions, Truste
89
89
  pub mod helpers;
90
90
  pub(crate) mod lifecycle_tools;
91
91
  pub mod normalize;
92
+ #[cfg(unix)]
93
+ pub(crate) mod readiness;
92
94
  pub mod tools;
93
95
  pub mod types;
94
96
  pub mod wire;