@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.
- package/Cargo.lock +1 -1
- package/Cargo.toml +1 -1
- package/crates/team-agent/src/cli/intent/runtime.rs +46 -10
- package/crates/team-agent/src/cli/intent/runtime_tests.rs +114 -0
- package/crates/team-agent/src/cli/mod.rs +4 -1
- package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +56 -0
- package/crates/team-agent/src/coordinator/tests/health_sync.rs +27 -8
- package/crates/team-agent/src/db/message_store.rs +65 -0
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +8 -0
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +111 -4
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp_iso.rs +244 -82
- package/crates/team-agent/src/lifecycle/launch.rs +1 -0
- package/crates/team-agent/src/lifecycle/restart/agent.rs +3 -0
- package/crates/team-agent/src/lifecycle/restart/common.rs +10 -1
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +5 -0
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +16 -3
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +60 -17
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +162 -8
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +55 -6
- package/crates/team-agent/src/mcp_server/mod.rs +2 -0
- package/crates/team-agent/src/mcp_server/readiness.rs +495 -0
- package/crates/team-agent/src/mcp_server/readiness_tests.rs +272 -0
- package/crates/team-agent/src/mcp_server/wire.rs +96 -11
- package/crates/team-agent/src/messaging/delivery.rs +62 -0
- package/crates/team-agent/src/messaging/delivery_consumption_tests.rs +12 -1
- package/crates/team-agent/src/messaging/tests/dup_inject.rs +89 -5
- package/crates/team-agent/src/messaging/tests/spine.rs +45 -14
- package/crates/team-agent/src/provider/adapter.rs +7 -0
- 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::
|
|
30
|
-
|
|
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,
|
|
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(
|
|
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
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
724
|
-
|
|
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
|
-
|
|
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(
|
|
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> {
|