@team-agent/installer 0.5.98 → 0.5.100
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/adapters.rs +1 -163
- package/crates/team-agent/src/cli/diagnose.rs +3 -19
- package/crates/team-agent/src/cli/emit.rs +20 -204
- package/crates/team-agent/src/cli/mod.rs +138 -205
- package/crates/team-agent/src/cli/send/coordinator.rs +2 -2
- package/crates/team-agent/src/cli/spec.rs +41 -67
- package/crates/team-agent/src/cli/tests/main_preserved.rs +2 -186
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +9 -10
- package/crates/team-agent/src/cli/tests/run_delegation.rs +0 -3
- package/crates/team-agent/src/cli/tests/verb_validate.rs +0 -1
- package/crates/team-agent/src/cli/triage.rs +1 -1
- package/crates/team-agent/src/cli/types.rs +0 -55
- package/crates/team-agent/src/compiler/tests.rs +5 -8
- package/crates/team-agent/src/compiler.rs +6 -14
- package/crates/team-agent/src/leader/lease.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/agent_state.rs +5 -25
- package/crates/team-agent/src/lifecycle/launch/identity.rs +2 -18
- package/crates/team-agent/src/lifecycle/launch/layout.rs +7 -29
- package/crates/team-agent/src/lifecycle/launch/leader_context.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/ownership.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +7 -7
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +12 -49
- package/crates/team-agent/src/lifecycle/launch/quick_start_transport.rs +3 -9
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -61
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +2 -15
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +3 -7
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch.rs +3 -4
- package/crates/team-agent/src/lifecycle/mod.rs +0 -8
- package/crates/team-agent/src/lifecycle/restart/agent.rs +62 -8
- package/crates/team-agent/src/lifecycle/restart/common.rs +0 -32
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +6 -15
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +1 -2
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +0 -1
- package/crates/team-agent/src/lifecycle/tests/core.rs +0 -213
- package/crates/team-agent/src/lifecycle/tests/issue238_codex_effort_contract.rs +96 -0
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +1 -0
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +31 -77
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +4 -8
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +16 -9
- package/crates/team-agent/src/lifecycle/tests/pi_executable_mcp_red.rs +1 -1
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +0 -60
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +3 -4
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests.rs +1 -2
- package/crates/team-agent/src/lifecycle/types.rs +3 -133
- package/crates/team-agent/src/messaging/send.rs +2 -2
- package/crates/team-agent/src/model/enums.rs +21 -71
- package/crates/team-agent/src/model/spec.rs +5 -20
- package/crates/team-agent/src/provider/adapters/codex.rs +2 -2
- package/crates/team-agent/src/provider/types.rs +7 -6
- package/crates/team-agent/src/state/repository.rs +0 -5
- package/examples/team.spec.yaml +0 -1
- package/package.json +4 -4
- package/schemas/team.schema.json +2 -2
- package/skills/team-agent/SKILL.md +1 -1
- package/skills/team-agent/references/recovery-runbook.md +2 -2
- package/skills/team-agent/references/team-agent-operator.md +3 -10
- package/skills/team-agent/references/team-in-team.md +0 -6
- package/crates/team-agent/src/lifecycle/display.rs +0 -550
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +0 -884
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +0 -481
|
@@ -1,481 +0,0 @@
|
|
|
1
|
-
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
2
|
-
|
|
3
|
-
#[path = "../../../tests/support/hermetic.rs"]
|
|
4
|
-
mod hermetic_guard;
|
|
5
|
-
#[allow(dead_code)]
|
|
6
|
-
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
7
|
-
|
|
8
|
-
use std::path::PathBuf;
|
|
9
|
-
use std::process::Command;
|
|
10
|
-
use std::sync::atomic::{AtomicU64, Ordering};
|
|
11
|
-
|
|
12
|
-
use serde_json::{json, Value};
|
|
13
|
-
use serial_test::file_serial;
|
|
14
|
-
use team_agent::lifecycle::{
|
|
15
|
-
close_team_display_backends, open_worker_displays, probe_display_capabilities,
|
|
16
|
-
AdaptiveBlockReason, CapsFlags, DisplayBackend, DisplayProbe, DisplayStatus, WorkerDisplay,
|
|
17
|
-
};
|
|
18
|
-
use team_agent::state::persist::{load_runtime_state, save_runtime_state};
|
|
19
|
-
use team_agent::transport::{PaneId, SessionName};
|
|
20
|
-
|
|
21
|
-
#[test]
|
|
22
|
-
#[ignore = "real-machine: adaptive open creates tmux panes"]
|
|
23
|
-
#[file_serial(tmux)]
|
|
24
|
-
fn adaptive_open_creates_real_tmux_pane_records_instead_of_placeholder_state() {
|
|
25
|
-
let fixture = DisplayFixture::new("open-records");
|
|
26
|
-
fixture.seed_running_workers(["alpha", "bravo"]);
|
|
27
|
-
let probe = DisplayProbe {
|
|
28
|
-
in_tmux: true,
|
|
29
|
-
platform: std::env::consts::OS.to_string(),
|
|
30
|
-
leader_session: Some(SessionName::new("leader-live")),
|
|
31
|
-
leader_pane: Some(PaneId::new("%leader")),
|
|
32
|
-
caps: CapsFlags {
|
|
33
|
-
tmux_append_windows: true,
|
|
34
|
-
adaptive_display: true,
|
|
35
|
-
},
|
|
36
|
-
adaptive_status: DisplayStatus::Opened,
|
|
37
|
-
reason: None,
|
|
38
|
-
};
|
|
39
|
-
|
|
40
|
-
let report = open_worker_displays(
|
|
41
|
-
&fixture.workspace,
|
|
42
|
-
&SessionName::new("team-adaptive"),
|
|
43
|
-
DisplayBackend::Adaptive,
|
|
44
|
-
&probe,
|
|
45
|
-
)
|
|
46
|
-
.expect("adaptive display opening should return a typed report");
|
|
47
|
-
|
|
48
|
-
assert_eq!(report.backend, DisplayBackend::Adaptive);
|
|
49
|
-
assert_eq!(
|
|
50
|
-
report.displays.len(),
|
|
51
|
-
2,
|
|
52
|
-
"fixture has two worker panes; report={report:?}"
|
|
53
|
-
);
|
|
54
|
-
for (agent_id, display) in &report.displays {
|
|
55
|
-
let value = serde_json::to_value(display).unwrap();
|
|
56
|
-
let pane_id = value.get("pane_id").and_then(Value::as_str);
|
|
57
|
-
assert!(
|
|
58
|
-
pane_id.is_some_and(|pane| pane.starts_with('%')),
|
|
59
|
-
"adaptive display must create a real tmux overview/split pane and persist pane_id; agent={agent_id} display={value}"
|
|
60
|
-
);
|
|
61
|
-
for field in [
|
|
62
|
-
"leader_session",
|
|
63
|
-
"display_session",
|
|
64
|
-
"linked_session",
|
|
65
|
-
"pane_title",
|
|
66
|
-
"target_worker_session",
|
|
67
|
-
] {
|
|
68
|
-
assert!(
|
|
69
|
-
value.get(field).and_then(Value::as_str).is_some_and(|s| !s.is_empty()),
|
|
70
|
-
"adaptive display metadata must include cleanup/status field `{field}`; agent={agent_id} display={value}"
|
|
71
|
-
);
|
|
72
|
-
}
|
|
73
|
-
assert_eq!(
|
|
74
|
-
value.get("fallback").and_then(Value::as_str),
|
|
75
|
-
Some("tmux_headless"),
|
|
76
|
-
"adaptive display must record the tmux fallback mode used for cleanup/status; agent={agent_id} display={value}"
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
#[test]
|
|
82
|
-
fn adaptive_close_uses_recorded_display_metadata_to_close_windows_and_linked_sessions() {
|
|
83
|
-
let fixture = DisplayFixture::new("close-records");
|
|
84
|
-
fixture.seed_state_with_open_adaptive_display();
|
|
85
|
-
|
|
86
|
-
let report =
|
|
87
|
-
close_team_display_backends(&fixture.workspace, &SessionName::new("team-adaptive"))
|
|
88
|
-
.expect("adaptive close should return concrete cleanup targets");
|
|
89
|
-
|
|
90
|
-
for expected in [
|
|
91
|
-
"leader-live:team-agent:team-adaptive:overview",
|
|
92
|
-
"team-agent:team-adaptive:alpha:linked",
|
|
93
|
-
"%11",
|
|
94
|
-
] {
|
|
95
|
-
assert!(
|
|
96
|
-
report.closed.iter().any(|closed| closed == expected),
|
|
97
|
-
"adaptive close must include every recorded cleanup target `{expected}`, not only the first identifier per agent; report={report:?}"
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
assert!(
|
|
101
|
-
report.orphans_cleaned.iter().any(|closed| closed.contains("team-agent:team-adaptive:overview")),
|
|
102
|
-
"adaptive close must scan and clean stale tagged overview windows/sessions; report={report:?}"
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
#[test]
|
|
107
|
-
#[ignore = "real-machine: needs real tmux workspace socket and tagged adaptive overview cleanup"]
|
|
108
|
-
#[file_serial(tmux)]
|
|
109
|
-
fn real_tmux_adaptive_display_creates_tiled_overview_and_close_removes_only_tagged_windows() {
|
|
110
|
-
let fixture = DisplayFixture::new("real-tmux");
|
|
111
|
-
let team_dir = fixture.write_fake_team(["alpha", "bravo"]);
|
|
112
|
-
let socket = format!("ta-display-{}-{}", std::process::id(), fixture.id);
|
|
113
|
-
let leader_session = format!("leader-display-{}", fixture.id);
|
|
114
|
-
let team_session = "team-adaptive";
|
|
115
|
-
let cleanup = RealTmuxCleanup {
|
|
116
|
-
socket: socket.clone(),
|
|
117
|
-
sessions: vec![leader_session.clone(), team_session.to_string()],
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
run_tmux(
|
|
121
|
-
&socket,
|
|
122
|
-
["new-session", "-d", "-s", &leader_session, "-n", "leader"],
|
|
123
|
-
)
|
|
124
|
-
.unwrap();
|
|
125
|
-
run_tmux(
|
|
126
|
-
&socket,
|
|
127
|
-
["new-session", "-d", "-s", team_session, "-n", "alpha"],
|
|
128
|
-
)
|
|
129
|
-
.unwrap();
|
|
130
|
-
run_tmux(
|
|
131
|
-
&socket,
|
|
132
|
-
["new-window", "-t", team_session, "-n", "bravo", "sleep 60"],
|
|
133
|
-
)
|
|
134
|
-
.unwrap();
|
|
135
|
-
let socket_path = run_tmux(
|
|
136
|
-
&socket,
|
|
137
|
-
[
|
|
138
|
-
"display-message",
|
|
139
|
-
"-p",
|
|
140
|
-
"-t",
|
|
141
|
-
&leader_session,
|
|
142
|
-
"-F",
|
|
143
|
-
"#{socket_path}",
|
|
144
|
-
],
|
|
145
|
-
)
|
|
146
|
-
.unwrap()
|
|
147
|
-
.trim()
|
|
148
|
-
.to_string();
|
|
149
|
-
let leader_pane = run_tmux(
|
|
150
|
-
&socket,
|
|
151
|
-
["list-panes", "-t", &leader_session, "-F", "#{pane_id}"],
|
|
152
|
-
)
|
|
153
|
-
.unwrap()
|
|
154
|
-
.lines()
|
|
155
|
-
.next()
|
|
156
|
-
.unwrap_or("%0")
|
|
157
|
-
.to_string();
|
|
158
|
-
let tmux_env = format!("{socket_path},{},0", std::process::id());
|
|
159
|
-
let _env = EnvGuard::set_many([
|
|
160
|
-
("TMUX", tmux_env.as_str()),
|
|
161
|
-
("TMUX_PANE", leader_pane.as_str()),
|
|
162
|
-
]);
|
|
163
|
-
|
|
164
|
-
let output = Command::new(team_agent::lifecycle::tests::test_binary_path())
|
|
165
|
-
.args([
|
|
166
|
-
"quick-start",
|
|
167
|
-
team_dir.to_str().unwrap(),
|
|
168
|
-
"--workspace",
|
|
169
|
-
fixture.workspace.to_str().unwrap(),
|
|
170
|
-
"--team-id",
|
|
171
|
-
"adaptive",
|
|
172
|
-
"--name",
|
|
173
|
-
"adaptive",
|
|
174
|
-
"--yes",
|
|
175
|
-
"--json",
|
|
176
|
-
])
|
|
177
|
-
.env("TMUX", &tmux_env)
|
|
178
|
-
.env("TMUX_PANE", &leader_pane)
|
|
179
|
-
.output()
|
|
180
|
-
.expect("run team-agent quick-start adaptive display");
|
|
181
|
-
let combined = format!(
|
|
182
|
-
"{}\n{}",
|
|
183
|
-
String::from_utf8_lossy(&output.stdout),
|
|
184
|
-
String::from_utf8_lossy(&output.stderr)
|
|
185
|
-
);
|
|
186
|
-
assert!(
|
|
187
|
-
output.status.success(),
|
|
188
|
-
"quick-start should run before checking real tmux display; output={combined}"
|
|
189
|
-
);
|
|
190
|
-
|
|
191
|
-
let probe = probe_display_capabilities(&fixture.workspace)
|
|
192
|
-
.expect("real tmux fixture should produce a typed adaptive display probe");
|
|
193
|
-
assert_eq!(
|
|
194
|
-
probe.leader_session.as_ref().map(SessionName::as_str),
|
|
195
|
-
Some(leader_session.as_str()),
|
|
196
|
-
"fixture must pass the real leader tmux session to the display probe; probe={probe:?} output={combined}"
|
|
197
|
-
);
|
|
198
|
-
let opened = open_worker_displays(
|
|
199
|
-
&fixture.workspace,
|
|
200
|
-
&SessionName::new(team_session),
|
|
201
|
-
DisplayBackend::Adaptive,
|
|
202
|
-
&probe,
|
|
203
|
-
)
|
|
204
|
-
.expect("real adaptive display open should return a typed report");
|
|
205
|
-
assert_real_display_records(&opened.displays);
|
|
206
|
-
fixture.persist_display_records(&opened.displays);
|
|
207
|
-
|
|
208
|
-
let windows = run_tmux(
|
|
209
|
-
&socket,
|
|
210
|
-
[
|
|
211
|
-
"list-windows",
|
|
212
|
-
"-t",
|
|
213
|
-
&leader_session,
|
|
214
|
-
"-F",
|
|
215
|
-
"#{window_name}:#{window_panes}",
|
|
216
|
-
],
|
|
217
|
-
)
|
|
218
|
-
.unwrap();
|
|
219
|
-
assert!(
|
|
220
|
-
windows.contains("team-agent:team-adaptive:overview") && windows.contains(":2"),
|
|
221
|
-
"adaptive display must create a tiled overview window in the leader tmux socket; windows={windows} output={combined}"
|
|
222
|
-
);
|
|
223
|
-
let closed = close_team_display_backends(&fixture.workspace, &SessionName::new(team_session))
|
|
224
|
-
.expect("real adaptive display close should return cleanup evidence");
|
|
225
|
-
assert!(
|
|
226
|
-
closed
|
|
227
|
-
.closed
|
|
228
|
-
.iter()
|
|
229
|
-
.any(|target| target.contains("team-agent:team-adaptive:overview")),
|
|
230
|
-
"adaptive close must clean the tagged overview window; closed={closed:?}"
|
|
231
|
-
);
|
|
232
|
-
let after_close = run_tmux(
|
|
233
|
-
&socket,
|
|
234
|
-
[
|
|
235
|
-
"list-windows",
|
|
236
|
-
"-t",
|
|
237
|
-
&leader_session,
|
|
238
|
-
"-F",
|
|
239
|
-
"#{window_name}",
|
|
240
|
-
],
|
|
241
|
-
)
|
|
242
|
-
.unwrap();
|
|
243
|
-
assert!(
|
|
244
|
-
!after_close.contains("team-agent:team-adaptive:overview"),
|
|
245
|
-
"adaptive close must remove tagged overview windows while leaving leader session alive; after_close={after_close}"
|
|
246
|
-
);
|
|
247
|
-
drop(cleanup);
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
struct DisplayFixture {
|
|
251
|
-
id: u64,
|
|
252
|
-
workspace: PathBuf,
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
impl DisplayFixture {
|
|
256
|
-
fn new(tag: &str) -> Self {
|
|
257
|
-
static N: AtomicU64 = AtomicU64::new(0);
|
|
258
|
-
let id = N.fetch_add(1, Ordering::Relaxed);
|
|
259
|
-
let workspace = std::env::temp_dir().join(format!(
|
|
260
|
-
"ta-rs-display-adaptive-{tag}-{}-{id}",
|
|
261
|
-
std::process::id(),
|
|
262
|
-
));
|
|
263
|
-
let _ = std::fs::remove_dir_all(&workspace);
|
|
264
|
-
std::fs::create_dir_all(&workspace).unwrap();
|
|
265
|
-
Self {
|
|
266
|
-
id,
|
|
267
|
-
workspace: std::fs::canonicalize(workspace).unwrap(),
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
fn seed_running_workers<const N: usize>(&self, workers: [&str; N]) {
|
|
272
|
-
let agents = workers
|
|
273
|
-
.into_iter()
|
|
274
|
-
.map(|agent| {
|
|
275
|
-
(
|
|
276
|
-
agent.to_string(),
|
|
277
|
-
json!({
|
|
278
|
-
"status": "running",
|
|
279
|
-
"provider": "fake",
|
|
280
|
-
"window": agent,
|
|
281
|
-
"pane_id": format!("%{agent}"),
|
|
282
|
-
"owner_team_id": "adaptive",
|
|
283
|
-
}),
|
|
284
|
-
)
|
|
285
|
-
})
|
|
286
|
-
.collect::<serde_json::Map<_, _>>();
|
|
287
|
-
save_runtime_state(
|
|
288
|
-
&self.workspace,
|
|
289
|
-
&json!({
|
|
290
|
-
"active_team_key": "adaptive",
|
|
291
|
-
"session_name": "team-adaptive",
|
|
292
|
-
"agents": agents,
|
|
293
|
-
}),
|
|
294
|
-
)
|
|
295
|
-
.unwrap();
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
fn seed_state_with_open_adaptive_display(&self) {
|
|
299
|
-
save_runtime_state(
|
|
300
|
-
&self.workspace,
|
|
301
|
-
&json!({
|
|
302
|
-
"active_team_key": "adaptive",
|
|
303
|
-
"session_name": "team-adaptive",
|
|
304
|
-
"agents": {
|
|
305
|
-
"alpha": {
|
|
306
|
-
"status": "running",
|
|
307
|
-
"provider": "fake",
|
|
308
|
-
"window": "alpha",
|
|
309
|
-
"display": {
|
|
310
|
-
"backend": "adaptive",
|
|
311
|
-
"status": "opened",
|
|
312
|
-
"leader_session": "leader-live",
|
|
313
|
-
"display_session": "leader-live:team-agent:team-adaptive:overview",
|
|
314
|
-
"linked_session": "team-agent:team-adaptive:alpha:linked",
|
|
315
|
-
"pane_id": "%11",
|
|
316
|
-
"target": "team-adaptive:alpha",
|
|
317
|
-
"pane_title": "team-agent:alpha:Worker"
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}),
|
|
322
|
-
)
|
|
323
|
-
.unwrap();
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
fn persist_display_records(
|
|
327
|
-
&self,
|
|
328
|
-
displays: &std::collections::BTreeMap<String, WorkerDisplay>,
|
|
329
|
-
) {
|
|
330
|
-
let mut state = load_runtime_state(&self.workspace).unwrap();
|
|
331
|
-
let state_snapshot = state.to_string();
|
|
332
|
-
let agents = state
|
|
333
|
-
.get_mut("agents")
|
|
334
|
-
.and_then(Value::as_object_mut)
|
|
335
|
-
.unwrap_or_else(|| panic!("quick-start fixture must persist agents before display open; state={state_snapshot}"));
|
|
336
|
-
for (agent_id, display) in displays {
|
|
337
|
-
let agent = agents
|
|
338
|
-
.get_mut(agent_id)
|
|
339
|
-
.and_then(Value::as_object_mut)
|
|
340
|
-
.unwrap_or_else(|| panic!("display report referenced unknown agent {agent_id}; state={state_snapshot}"));
|
|
341
|
-
agent.insert(
|
|
342
|
-
"display".to_string(),
|
|
343
|
-
serde_json::to_value(display).unwrap(),
|
|
344
|
-
);
|
|
345
|
-
}
|
|
346
|
-
save_runtime_state(&self.workspace, &state).unwrap();
|
|
347
|
-
}
|
|
348
|
-
|
|
349
|
-
fn write_fake_team<const N: usize>(&self, workers: [&str; N]) -> PathBuf {
|
|
350
|
-
let team = self.workspace.join("adaptive-team");
|
|
351
|
-
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
352
|
-
std::fs::write(
|
|
353
|
-
team.join("TEAM.md"),
|
|
354
|
-
"---\nname: adaptive\nobjective: Adaptive display real tmux fixture.\nprovider: fake\ndisplay_backend: adaptive\n---\n\nTeam.\n",
|
|
355
|
-
)
|
|
356
|
-
.unwrap();
|
|
357
|
-
for worker in workers {
|
|
358
|
-
std::fs::write(
|
|
359
|
-
team.join("agents").join(format!("{worker}.md")),
|
|
360
|
-
format!(
|
|
361
|
-
"---\nname: {worker}\nrole: Adaptive fake worker {worker}\nprovider: fake\nmodel: fake\nauth_mode: subscription\ndangerously_skip_permissions: false\ntools:\n - mcp_team\n---\n\nWorker.\n"
|
|
362
|
-
),
|
|
363
|
-
)
|
|
364
|
-
.unwrap();
|
|
365
|
-
}
|
|
366
|
-
team
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
impl Drop for DisplayFixture {
|
|
371
|
-
fn drop(&mut self) {
|
|
372
|
-
let _ = std::fs::remove_dir_all(&self.workspace);
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
struct RealTmuxCleanup {
|
|
377
|
-
socket: String,
|
|
378
|
-
sessions: Vec<String>,
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
impl Drop for RealTmuxCleanup {
|
|
382
|
-
fn drop(&mut self) {
|
|
383
|
-
for session in &self.sessions {
|
|
384
|
-
let _ = run_tmux(&self.socket, ["kill-session", "-t", session]);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
struct EnvGuard {
|
|
390
|
-
old: Vec<(&'static str, Option<String>)>,
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
impl EnvGuard {
|
|
394
|
-
fn set_many<const N: usize>(pairs: [(&'static str, &str); N]) -> Self {
|
|
395
|
-
let old = pairs
|
|
396
|
-
.iter()
|
|
397
|
-
.map(|(key, _)| (*key, std::env::var(key).ok()))
|
|
398
|
-
.collect::<Vec<_>>();
|
|
399
|
-
for (key, value) in pairs {
|
|
400
|
-
std::env::set_var(key, value);
|
|
401
|
-
}
|
|
402
|
-
Self { old }
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
impl Drop for EnvGuard {
|
|
407
|
-
fn drop(&mut self) {
|
|
408
|
-
for (key, old) in self.old.drain(..) {
|
|
409
|
-
match old {
|
|
410
|
-
Some(value) => std::env::set_var(key, value),
|
|
411
|
-
None => std::env::remove_var(key),
|
|
412
|
-
}
|
|
413
|
-
}
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
fn run_tmux<const N: usize>(socket: &str, args: [&str; N]) -> std::io::Result<String> {
|
|
418
|
-
let output = Command::new("tmux")
|
|
419
|
-
.arg("-L")
|
|
420
|
-
.arg(socket)
|
|
421
|
-
.args(args)
|
|
422
|
-
.output()?;
|
|
423
|
-
Ok(String::from_utf8_lossy(&output.stdout).to_string())
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
fn assert_real_display_records(displays: &std::collections::BTreeMap<String, WorkerDisplay>) {
|
|
427
|
-
assert_eq!(
|
|
428
|
-
displays.len(),
|
|
429
|
-
2,
|
|
430
|
-
"real fixture has two fake workers; displays={displays:?}"
|
|
431
|
-
);
|
|
432
|
-
for (agent_id, display) in displays {
|
|
433
|
-
let value = serde_json::to_value(display).unwrap();
|
|
434
|
-
assert_eq!(
|
|
435
|
-
value.get("backend").and_then(Value::as_str),
|
|
436
|
-
Some("adaptive"),
|
|
437
|
-
"agent={agent_id} display={value}"
|
|
438
|
-
);
|
|
439
|
-
assert_eq!(
|
|
440
|
-
value.get("status").and_then(Value::as_str),
|
|
441
|
-
Some("opened"),
|
|
442
|
-
"agent={agent_id} display={value}"
|
|
443
|
-
);
|
|
444
|
-
assert!(
|
|
445
|
-
value
|
|
446
|
-
.get("pane_id")
|
|
447
|
-
.and_then(Value::as_str)
|
|
448
|
-
.is_some_and(|pane| pane.starts_with('%')),
|
|
449
|
-
"real adaptive display must record concrete pane_id; agent={agent_id} display={value}"
|
|
450
|
-
);
|
|
451
|
-
for field in [
|
|
452
|
-
"leader_session",
|
|
453
|
-
"display_session",
|
|
454
|
-
"linked_session",
|
|
455
|
-
"pane_title",
|
|
456
|
-
"target_worker_session",
|
|
457
|
-
] {
|
|
458
|
-
assert!(
|
|
459
|
-
value.get(field).and_then(Value::as_str).is_some_and(|s| !s.is_empty()),
|
|
460
|
-
"real adaptive display must persist `{field}` for cleanup/status; agent={agent_id} display={value}"
|
|
461
|
-
);
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
#[allow(dead_code)]
|
|
467
|
-
fn assert_not_ghostty(display: &WorkerDisplay) {
|
|
468
|
-
assert!(
|
|
469
|
-
!matches!(
|
|
470
|
-
display,
|
|
471
|
-
WorkerDisplay::GhosttyWindow { .. } | WorkerDisplay::GhosttyWorkspace { .. }
|
|
472
|
-
),
|
|
473
|
-
"Ghostty backends are explicitly out of scope for this adaptive-only contract"
|
|
474
|
-
);
|
|
475
|
-
assert!(!matches!(
|
|
476
|
-
display,
|
|
477
|
-
WorkerDisplay::Blocked {
|
|
478
|
-
reason: AdaptiveBlockReason::NotImplementedThisPlatform
|
|
479
|
-
}
|
|
480
|
-
));
|
|
481
|
-
}
|