@team-agent/installer 0.5.77 → 0.5.80
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 +161 -29
- package/crates/team-agent/src/cli/diagnose.rs +333 -68
- package/crates/team-agent/src/cli/emit.rs +24 -8
- package/crates/team-agent/src/cli/mod.rs +543 -98
- package/crates/team-agent/src/cli/models.rs +393 -32
- package/crates/team-agent/src/cli/named_address.rs +19 -5
- package/crates/team-agent/src/cli/profile.rs +35 -4
- package/crates/team-agent/src/cli/spec.rs +7 -2
- package/crates/team-agent/src/cli/status_port/compact.rs +12 -8
- package/crates/team-agent/src/cli/status_port/runtime.rs +3 -1
- package/crates/team-agent/src/cli/status_port/snapshot.rs +15 -12
- package/crates/team-agent/src/cli/status_port/store.rs +1 -1
- package/crates/team-agent/src/cli/status_port/tests.rs +37 -1
- package/crates/team-agent/src/cli/tests/missing_subcommands.rs +62 -8
- package/crates/team-agent/src/cli/tests/named_address.rs +70 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +81 -6
- package/crates/team-agent/src/cli/types.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +122 -2
- package/crates/team-agent/src/leader/lease.rs +492 -33
- package/crates/team-agent/src/leader/owner_bind.rs +40 -12
- package/crates/team-agent/src/leader/rediscover/tests.rs +65 -0
- package/crates/team-agent/src/leader/rediscover.rs +36 -6
- package/crates/team-agent/src/leader/start.rs +197 -88
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +15 -9
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +28 -227
- package/crates/team-agent/src/lifecycle/launch/add_agent_state.rs +0 -18
- package/crates/team-agent/src/lifecycle/launch/cursor_mcp.rs +94 -227
- package/crates/team-agent/src/lifecycle/launch/mcp_config.rs +59 -10
- package/crates/team-agent/src/lifecycle/launch/pi_mcp.rs +86 -23
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +1449 -282
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +349 -4
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +11 -11
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +35 -3
- package/crates/team-agent/src/lifecycle/launch/worker_env.rs +16 -0
- package/crates/team-agent/src/lifecycle/launch.rs +30 -7
- package/crates/team-agent/src/lifecycle/profile_launch.rs +37 -3
- package/crates/team-agent/src/lifecycle/restart/agent.rs +112 -100
- package/crates/team-agent/src/lifecycle/restart/common.rs +82 -52
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +996 -66
- package/crates/team-agent/src/lifecycle/restart.rs +2 -2
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +15 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +5 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +11 -13
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +119 -4
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +12 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +22 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +12 -0
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +265 -198
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +14 -30
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/fixtures/pi_list_models_spaced_ids.stdout.txt +9 -0
- package/crates/team-agent/src/lifecycle/tests/g1_silent_faces.rs +2 -5
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +10 -0
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +58 -35
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +10 -0
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +9 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +0 -168
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +145 -212
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +16 -2
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +54 -9
- package/crates/team-agent/src/lifecycle/tests/pi_catalog_red.rs +71 -0
- package/crates/team-agent/src/lifecycle/tests/pi_dual_entry_red.rs +78 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +3 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +51 -10
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +50 -7
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +137 -0
- package/crates/team-agent/src/lifecycle/types.rs +8 -0
- package/crates/team-agent/src/mcp_server/wire.rs +140 -11
- package/crates/team-agent/src/messaging/delivery.rs +221 -80
- package/crates/team-agent/src/messaging/leader_channel.rs +56 -0
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +127 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +10 -2
- package/crates/team-agent/src/provider/adapters/cursor_agent.rs +2 -5
- package/crates/team-agent/src/provider/session_scan/common/tests.rs +204 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +24 -2
- package/crates/team-agent/src/state/abnormal_watch/tests/mod.rs +248 -0
- package/crates/team-agent/src/state/abnormal_watch.rs +144 -0
- package/crates/team-agent/src/state/mod.rs +1 -0
- package/crates/team-agent/src/state/persist.rs +102 -4
- package/crates/team-agent/src/state/projection.rs +7 -1
- package/crates/team-agent/src/state/repository.rs +29 -0
- package/crates/team-agent/src/test_support/models_timeout_fixture.rs +43 -21
- package/crates/team-agent/src/tmux_backend/tests.rs +149 -4
- package/crates/team-agent/src/tmux_backend.rs +239 -127
- package/crates/team-agent/src/transport.rs +78 -0
- package/crates/team-agent/src/transport_factory.rs +53 -0
- package/package.json +4 -5
- package/skills/team-agent/SKILL.md +45 -17
- package/skills/team-agent/command-coverage.json +4 -29
- package/npm/check-skill-links.mjs +0 -88
- package/skills/team-agent/references/team-agent-operator.md +0 -672
|
@@ -40,6 +40,7 @@ use team_agent::transport::test_support::OfflineTransport;
|
|
|
40
40
|
#[test]
|
|
41
41
|
#[serial(env)]
|
|
42
42
|
fn cursor_fresh_spawn_does_not_invent_session_id() {
|
|
43
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-fresh");
|
|
43
44
|
let (ws, _home, _guard, team) = seed_cursor("pending");
|
|
44
45
|
let transport = OfflineTransport::new();
|
|
45
46
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
|
|
@@ -79,6 +80,7 @@ fn cursor_fresh_spawn_does_not_invent_session_id() {
|
|
|
79
80
|
#[test]
|
|
80
81
|
#[serial(env)]
|
|
81
82
|
fn cursor_restart_uses_resume_when_session_captured_and_archive_present() {
|
|
83
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-resume");
|
|
82
84
|
let (ws, home, _guard, team) = seed_cursor("resume");
|
|
83
85
|
let first = OfflineTransport::new();
|
|
84
86
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &first)
|
|
@@ -180,6 +182,7 @@ fn cursor_classify_resumes_when_archive_present_and_refuses_when_missing() {
|
|
|
180
182
|
#[test]
|
|
181
183
|
#[serial(env)]
|
|
182
184
|
fn cursor_fork_agent_refuses_as_unsupported_not_unverified() {
|
|
185
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter("cursor-fork");
|
|
183
186
|
let (ws, _home, _guard, team) = seed_cursor("fork");
|
|
184
187
|
let transport = OfflineTransport::new();
|
|
185
188
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
provider model context max-out thinking images
|
|
2
|
+
cursor claude-opus-4-7@1m:fast 1M 16.4K yes yes
|
|
3
|
+
cursor claude-opus-4-7@1m:slow 1M 16.4K yes yes
|
|
4
|
+
openai-codex gpt-5.6-luna 272K 128K yes yes
|
|
5
|
+
team-agent Claude Opus 4.6 (Thinking) 200K 32.8K yes yes
|
|
6
|
+
team-agent Claude Sonnet 4.6 (Thinking) 200K 32.8K yes yes
|
|
7
|
+
team-agent Gemini 3.8 Flash 200K 32.8K yes yes
|
|
8
|
+
team-agent gpt-5.6-luna 200K 32.8K yes yes
|
|
9
|
+
team-agent qwen3.8-27b 262.1K 32.8K yes yes
|
|
@@ -518,11 +518,8 @@ fn assert_provider_mcp_table(case: &Case, provider: Provider, clone_id: &str) {
|
|
|
518
518
|
}
|
|
519
519
|
Provider::CursorAgent => {
|
|
520
520
|
apply_cursor_mcp_overlay(&overlay_root, &cfg).expect("cursor overlay");
|
|
521
|
-
let text = std::fs::read_to_string(
|
|
522
|
-
|
|
523
|
-
.expect("iso path"),
|
|
524
|
-
)
|
|
525
|
-
.expect("cursor mcp.json");
|
|
521
|
+
let text = std::fs::read_to_string(overlay_root.join(".cursor/mcp.json"))
|
|
522
|
+
.expect("cursor mcp.json");
|
|
526
523
|
assert!(
|
|
527
524
|
text.contains("\"team_orchestrator\""),
|
|
528
525
|
"cursor MCP injection must keep team_orchestrator; text={text}"
|
|
@@ -21,6 +21,13 @@ mod hermetic_guard;
|
|
|
21
21
|
#[allow(dead_code)]
|
|
22
22
|
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
23
23
|
|
|
24
|
+
fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
|
|
25
|
+
let env = hermetic_guard::HermeticTestEnv::enter(tag);
|
|
26
|
+
env.scrub_tmux();
|
|
27
|
+
env.assert_no_real_tmux();
|
|
28
|
+
env
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
use std::path::{Path, PathBuf};
|
|
25
32
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
26
33
|
|
|
@@ -31,6 +38,7 @@ use team_agent::transport::test_support::OfflineTransport;
|
|
|
31
38
|
#[test]
|
|
32
39
|
#[serial(env)]
|
|
33
40
|
fn grok_role_effort_medium_reaches_argv() {
|
|
41
|
+
let _hermetic = enter_hermetic("grok-effort-medium");
|
|
34
42
|
let (ws, _home, _guard, team) = seed_grok("effort-medium", Some("medium"));
|
|
35
43
|
let transport = OfflineTransport::new();
|
|
36
44
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &transport)
|
|
@@ -45,6 +53,7 @@ fn grok_role_effort_medium_reaches_argv() {
|
|
|
45
53
|
#[test]
|
|
46
54
|
#[serial(env)]
|
|
47
55
|
fn grok_role_without_effort_emits_zero_effort_flags() {
|
|
56
|
+
let _hermetic = enter_hermetic("grok-effort-absent");
|
|
48
57
|
let (ws, _home, _guard, team) = seed_grok("effort-absent", None);
|
|
49
58
|
let transport = OfflineTransport::new();
|
|
50
59
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &transport)
|
|
@@ -60,6 +69,7 @@ fn grok_role_without_effort_emits_zero_effort_flags() {
|
|
|
60
69
|
#[test]
|
|
61
70
|
#[serial(env)]
|
|
62
71
|
fn grok_restart_keeps_declared_effort_medium() {
|
|
72
|
+
let _hermetic = enter_hermetic("grok-effort-restart");
|
|
63
73
|
let (ws, _home, _guard, team) = seed_grok("effort-restart", Some("medium"));
|
|
64
74
|
let first = OfflineTransport::new();
|
|
65
75
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &first)
|
|
@@ -14,6 +14,13 @@ mod hermetic_guard;
|
|
|
14
14
|
#[allow(dead_code)]
|
|
15
15
|
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
16
16
|
|
|
17
|
+
fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
|
|
18
|
+
let env = hermetic_guard::HermeticTestEnv::enter(tag);
|
|
19
|
+
env.scrub_tmux();
|
|
20
|
+
env.assert_no_real_tmux();
|
|
21
|
+
env
|
|
22
|
+
}
|
|
23
|
+
|
|
17
24
|
use std::path::{Path, PathBuf};
|
|
18
25
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
19
26
|
|
|
@@ -183,7 +190,7 @@ fn sample_mcp_config(agent_id: &str, workspace: &str) -> McpConfig {
|
|
|
183
190
|
fn two_grok_seats_coexist_when_toml_has_no_per_seat_keys() {
|
|
184
191
|
let ws = tmp_dir("grok-cwd-coexist");
|
|
185
192
|
let home = tmp_dir("grok-cwd-coexist-home");
|
|
186
|
-
seed_grok_home(&home);
|
|
193
|
+
seed_grok_home(&home, Some(&ws));
|
|
187
194
|
let _guard = HomeGuard::set(&home);
|
|
188
195
|
let team = write_grok_team_agents(&ws, "grokteam", &["g1", "g2"]);
|
|
189
196
|
|
|
@@ -209,9 +216,10 @@ fn two_grok_seats_coexist_when_toml_has_no_per_seat_keys() {
|
|
|
209
216
|
#[test]
|
|
210
217
|
#[serial(env)]
|
|
211
218
|
fn leftover_per_seat_key_is_cleared_before_start() {
|
|
219
|
+
let _hermetic = enter_hermetic("grok-overlay-leftover-seat");
|
|
212
220
|
let ws = tmp_dir("grok-cwd-per-seat");
|
|
213
221
|
let home = tmp_dir("grok-cwd-per-seat-home");
|
|
214
|
-
seed_grok_home(&home);
|
|
222
|
+
seed_grok_home(&home, Some(&ws));
|
|
215
223
|
let _guard = HomeGuard::set(&home);
|
|
216
224
|
let grok = ws.join(".grok");
|
|
217
225
|
std::fs::create_dir_all(&grok).unwrap();
|
|
@@ -279,12 +287,46 @@ TEAM_AGENT_WORKSPACE = "/stale-ws"
|
|
|
279
287
|
);
|
|
280
288
|
}
|
|
281
289
|
|
|
290
|
+
#[test]
|
|
291
|
+
#[serial(env)]
|
|
292
|
+
fn grok_untrusted_folder_refuses_to_start() {
|
|
293
|
+
let _hermetic = enter_hermetic("grok-overlay-untrusted");
|
|
294
|
+
let ws = tmp_dir("grok-untrusted");
|
|
295
|
+
let home = tmp_dir("grok-untrusted-home");
|
|
296
|
+
seed_grok_home(&home, None);
|
|
297
|
+
let _guard = HomeGuard::set(&home);
|
|
298
|
+
let team = write_grok_team(&ws, "grokteam", "grok_writer");
|
|
299
|
+
let err = quick_start_with_transport_in_workspace(
|
|
300
|
+
&ws,
|
|
301
|
+
&team,
|
|
302
|
+
None,
|
|
303
|
+
true,
|
|
304
|
+
Some("grokteam"),
|
|
305
|
+
&OfflineTransport::new(),
|
|
306
|
+
)
|
|
307
|
+
.expect_err("untrusted folder must not start a grok seat");
|
|
308
|
+
let text = err.to_string();
|
|
309
|
+
assert!(
|
|
310
|
+
text.contains("not trusted") && text.contains("action:"),
|
|
311
|
+
"untrusted-folder error must be actionable; err={text}"
|
|
312
|
+
);
|
|
313
|
+
assert!(
|
|
314
|
+
text.contains("grok --trust") || text.contains("/hooks-trust"),
|
|
315
|
+
"next step must name grok --trust or /hooks-trust; err={text}"
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
|
|
282
319
|
#[test]
|
|
283
320
|
#[serial(env)]
|
|
284
321
|
fn grok_missing_login_refuses_to_start() {
|
|
285
322
|
let ws = tmp_dir("grok-nologin");
|
|
286
323
|
let home = tmp_dir("grok-nologin-home");
|
|
287
324
|
std::fs::create_dir_all(home.join(".grok")).unwrap();
|
|
325
|
+
std::fs::write(
|
|
326
|
+
home.join(".grok").join("trusted_folders.toml"),
|
|
327
|
+
format!("[folders.\"{}\"]\ntrusted = true\n", ws.display()),
|
|
328
|
+
)
|
|
329
|
+
.unwrap();
|
|
288
330
|
let _guard = HomeGuard::set(&home);
|
|
289
331
|
let err = ensure_grok_login_and_folder_trust(&ws).expect_err("missing auth.json");
|
|
290
332
|
match err {
|
|
@@ -298,29 +340,13 @@ fn grok_missing_login_refuses_to_start() {
|
|
|
298
340
|
}
|
|
299
341
|
}
|
|
300
342
|
|
|
301
|
-
#[test]
|
|
302
|
-
#[serial(env)]
|
|
303
|
-
fn grok_login_present_allows_start_without_inspect_or_toml_trust() {
|
|
304
|
-
let ws = tmp_dir("grok-login-only");
|
|
305
|
-
let home = tmp_dir("grok-login-only-home");
|
|
306
|
-
seed_grok_home(&home);
|
|
307
|
-
let _guard = HomeGuard::set(&home);
|
|
308
|
-
|
|
309
|
-
assert!(!home.join("bin/grok").exists(), "no inspect shim may exist");
|
|
310
|
-
assert!(
|
|
311
|
-
!home.join(".grok/trusted_folders.toml").exists(),
|
|
312
|
-
"no trusted TOML may be required"
|
|
313
|
-
);
|
|
314
|
-
ensure_grok_login_and_folder_trust(&ws)
|
|
315
|
-
.expect("valid login alone must allow Grok TUI to own folder consent");
|
|
316
|
-
}
|
|
317
|
-
|
|
318
343
|
#[test]
|
|
319
344
|
#[serial(env)]
|
|
320
345
|
fn grok_spawn_writes_resolved_team_agent_into_project_grok_config() {
|
|
346
|
+
let _hermetic = enter_hermetic("grok-overlay-spawn");
|
|
321
347
|
let ws = tmp_dir("grok-mcp-overlay");
|
|
322
348
|
let home = tmp_dir("grok-mcp-overlay-home");
|
|
323
|
-
seed_grok_home(&home);
|
|
349
|
+
seed_grok_home(&home, Some(&ws));
|
|
324
350
|
let _guard = HomeGuard::set(&home);
|
|
325
351
|
let team = write_grok_team(&ws, "grokteam", "grok_writer");
|
|
326
352
|
let config_path = ws.join(".grok").join("config.toml");
|
|
@@ -435,40 +461,37 @@ fn write_grok_team_agents(ws: &Path, team_key: &str, agent_ids: &[&str]) -> Path
|
|
|
435
461
|
team
|
|
436
462
|
}
|
|
437
463
|
|
|
438
|
-
fn seed_grok_home(home: &Path) {
|
|
464
|
+
fn seed_grok_home(home: &Path, trusted_cwd: Option<&Path>) {
|
|
439
465
|
let grok = home.join(".grok");
|
|
440
466
|
std::fs::create_dir_all(&grok).unwrap();
|
|
441
467
|
std::fs::write(grok.join("auth.json"), r#"{"test":"ok"}"#).unwrap();
|
|
468
|
+
if let Some(cwd) = trusted_cwd {
|
|
469
|
+
std::fs::write(
|
|
470
|
+
grok.join("trusted_folders.toml"),
|
|
471
|
+
format!("[folders.\"{}\"]\ntrusted = true\n", cwd.display()),
|
|
472
|
+
)
|
|
473
|
+
.unwrap();
|
|
474
|
+
}
|
|
442
475
|
}
|
|
443
476
|
|
|
444
477
|
struct HomeGuard {
|
|
445
|
-
|
|
446
|
-
prev_path: Option<String>,
|
|
478
|
+
prev: Option<String>,
|
|
447
479
|
}
|
|
448
480
|
|
|
449
481
|
impl HomeGuard {
|
|
450
482
|
fn set(home: &Path) -> Self {
|
|
451
|
-
let
|
|
452
|
-
let prev_path = std::env::var("PATH").ok();
|
|
483
|
+
let prev = std::env::var("HOME").ok();
|
|
453
484
|
std::env::set_var("HOME", home);
|
|
454
|
-
|
|
455
|
-
Self {
|
|
456
|
-
prev_home,
|
|
457
|
-
prev_path,
|
|
458
|
-
}
|
|
485
|
+
Self { prev }
|
|
459
486
|
}
|
|
460
487
|
}
|
|
461
488
|
|
|
462
489
|
impl Drop for HomeGuard {
|
|
463
490
|
fn drop(&mut self) {
|
|
464
|
-
match &self.
|
|
491
|
+
match &self.prev {
|
|
465
492
|
Some(value) => std::env::set_var("HOME", value),
|
|
466
493
|
None => std::env::remove_var("HOME"),
|
|
467
494
|
}
|
|
468
|
-
match &self.prev_path {
|
|
469
|
-
Some(value) => std::env::set_var("PATH", value),
|
|
470
|
-
None => std::env::remove_var("PATH"),
|
|
471
|
-
}
|
|
472
495
|
}
|
|
473
496
|
}
|
|
474
497
|
|
|
@@ -13,6 +13,13 @@ mod hermetic_guard;
|
|
|
13
13
|
#[allow(dead_code)]
|
|
14
14
|
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
15
15
|
|
|
16
|
+
fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
|
|
17
|
+
let env = hermetic_guard::HermeticTestEnv::enter(tag);
|
|
18
|
+
env.scrub_tmux();
|
|
19
|
+
env.assert_no_real_tmux();
|
|
20
|
+
env
|
|
21
|
+
}
|
|
22
|
+
|
|
16
23
|
use std::path::{Path, PathBuf};
|
|
17
24
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
18
25
|
|
|
@@ -91,6 +98,7 @@ fn grok_role_missing_model_refuses_to_start() {
|
|
|
91
98
|
#[test]
|
|
92
99
|
#[serial(env)]
|
|
93
100
|
fn grok_role_explicit_model_reaches_argv() {
|
|
101
|
+
let _hermetic = enter_hermetic("grok-explicit-model");
|
|
94
102
|
let ws = tmp_dir("grok-with-model");
|
|
95
103
|
let home = tmp_dir("grok-with-model-home");
|
|
96
104
|
seed_grok_home(&home, Some(&ws));
|
|
@@ -124,7 +132,9 @@ fn grok_role_explicit_model_reaches_argv() {
|
|
|
124
132
|
|
|
125
133
|
/// 3. claude 角色缺 model ⇒ 行为不变(仍能启动,不被 grok 的硬性 model 要求误伤)。
|
|
126
134
|
#[test]
|
|
135
|
+
#[serial(env)]
|
|
127
136
|
fn claude_role_missing_model_still_starts() {
|
|
137
|
+
let _hermetic = enter_hermetic("grok-claude-missing-model");
|
|
128
138
|
let ws = tmp_dir("claude-no-model");
|
|
129
139
|
let team = write_role_team(
|
|
130
140
|
&ws,
|
|
@@ -21,6 +21,13 @@ mod hermetic_guard;
|
|
|
21
21
|
#[allow(dead_code)]
|
|
22
22
|
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
23
23
|
|
|
24
|
+
fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
|
|
25
|
+
let env = hermetic_guard::HermeticTestEnv::enter(tag);
|
|
26
|
+
env.scrub_tmux();
|
|
27
|
+
env.assert_no_real_tmux();
|
|
28
|
+
env
|
|
29
|
+
}
|
|
30
|
+
|
|
24
31
|
use std::path::{Path, PathBuf};
|
|
25
32
|
use std::sync::atomic::{AtomicU64, Ordering};
|
|
26
33
|
|
|
@@ -35,6 +42,7 @@ use team_agent::transport::test_support::OfflineTransport;
|
|
|
35
42
|
#[test]
|
|
36
43
|
#[serial(env)]
|
|
37
44
|
fn grok_fresh_spawn_binds_pending_session_id_to_argv_session_id() {
|
|
45
|
+
let _hermetic = enter_hermetic("grok-resume-pending");
|
|
38
46
|
let (ws, _home, _guard, team) = seed_grok("pending");
|
|
39
47
|
let transport = OfflineTransport::new();
|
|
40
48
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &transport)
|
|
@@ -68,6 +76,7 @@ fn grok_fresh_spawn_binds_pending_session_id_to_argv_session_id() {
|
|
|
68
76
|
#[test]
|
|
69
77
|
#[serial(env)]
|
|
70
78
|
fn grok_restart_uses_resume_when_session_captured_and_archive_present() {
|
|
79
|
+
let _hermetic = enter_hermetic("grok-resume-captured");
|
|
71
80
|
let (ws, home, _guard, team) = seed_grok("resume");
|
|
72
81
|
let first = OfflineTransport::new();
|
|
73
82
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("grokteam"), &first)
|
|
@@ -229,7 +229,7 @@ got {status:?}"
|
|
|
229
229
|
#[test]
|
|
230
230
|
#[serial(t3_event)]
|
|
231
231
|
fn t3_unknown_business_status_is_preserved_without_normalized_event() {
|
|
232
|
-
let harness = McpSimHarness::
|
|
232
|
+
let harness = McpSimHarness::new();
|
|
233
233
|
let mut worker = harness.spawn_mcp_client("worker_a", "teamA");
|
|
234
234
|
let status = "garbage-status-xyz";
|
|
235
235
|
let call = worker.call_tool(
|
|
@@ -423,7 +423,8 @@ check behind it = false green); it must reflect an actual doctor result"
|
|
|
423
423
|
/// unparseable / unsuccessful, the hint must not default to Present (auth misreported
|
|
424
424
|
/// as available). PATH-shims a `claude` that prints garbage and exits non-zero.
|
|
425
425
|
/// Mutates the process-global PATH/HOME — must not race host_cotenant's ps/tmux
|
|
426
|
-
/// shims (P0 pid-probe isolation), hence
|
|
426
|
+
/// shims (P0 pid-probe isolation) or other env-mutating tests, hence
|
|
427
|
+
/// `#[serial(env, pid_probe)]`.
|
|
427
428
|
#[test]
|
|
428
429
|
#[serial(env, pid_probe)]
|
|
429
430
|
fn t3_claude_auth_hint_parse_failure_is_not_present() {
|
|
@@ -1846,171 +1846,3 @@ fn lanea_fork_rollback_complete_on_post_spawn_failure() {
|
|
|
1846
1846
|
// Golden contract (operations.py:384-394): a post-spawn failure kills the spawned window, restores
|
|
1847
1847
|
// the old spec text + prior state, and runs cleanup_mcp before re-raising.
|
|
1848
1848
|
}
|
|
1849
|
-
|
|
1850
|
-
#[test]
|
|
1851
|
-
fn schema_invalid_pi_role_is_rejected_before_normal_and_force_mutation() {
|
|
1852
|
-
let ws = lanea_ws_agents(json!({
|
|
1853
|
-
"alpha": { "status": "stopped", "provider": "codex", "window": "alpha" },
|
|
1854
|
-
"bravo": { "status": "running", "provider": "codex", "window": "bravo" }
|
|
1855
|
-
}));
|
|
1856
|
-
let tx = LaneTransport::new("team-laneateam", &[]);
|
|
1857
|
-
seed_healthy_coordinator(&ws);
|
|
1858
|
-
let role_dir = ws.join("role-masters");
|
|
1859
|
-
std::fs::create_dir_all(&role_dir).unwrap();
|
|
1860
|
-
let role = role_dir.join("invalid-pi.md");
|
|
1861
|
-
std::fs::write(
|
|
1862
|
-
&role,
|
|
1863
|
-
CHARLIE_ROLE
|
|
1864
|
-
.replace(
|
|
1865
|
-
"provider: codex\nmodel: gpt-5.5",
|
|
1866
|
-
"provider: pi\nmodel: openai-codex/gpt-5.6-sol",
|
|
1867
|
-
)
|
|
1868
|
-
.replace(" - mcp_team", " - fs_read"),
|
|
1869
|
-
)
|
|
1870
|
-
.unwrap();
|
|
1871
|
-
|
|
1872
|
-
let spec_path = selected_spec_path(&ws);
|
|
1873
|
-
let state_path = crate::state::persist::runtime_state_path(&ws);
|
|
1874
|
-
let event_path = ws.join(".team/logs/events.jsonl");
|
|
1875
|
-
let copied_role = ws.join("agents/charlie.md");
|
|
1876
|
-
let team_state_path = ws.join("team_state.md");
|
|
1877
|
-
let charlie_paths = crate::lifecycle::launch::pi_mcp::pi_seat_paths(
|
|
1878
|
-
&ws,
|
|
1879
|
-
"laneateam",
|
|
1880
|
-
"charlie",
|
|
1881
|
-
);
|
|
1882
|
-
let alpha_paths =
|
|
1883
|
-
crate::lifecycle::launch::pi_mcp::pi_seat_paths(&ws, "laneateam", "alpha");
|
|
1884
|
-
let spec_before = std::fs::read(&spec_path).unwrap();
|
|
1885
|
-
let state_before = std::fs::read(&state_path).unwrap();
|
|
1886
|
-
let role_before = std::fs::read(&role).unwrap();
|
|
1887
|
-
let alpha_before = serde_json::from_slice::<serde_json::Value>(&state_before).unwrap()
|
|
1888
|
-
["agents"]["alpha"]
|
|
1889
|
-
.clone();
|
|
1890
|
-
for path in [
|
|
1891
|
-
&event_path,
|
|
1892
|
-
&copied_role,
|
|
1893
|
-
&team_state_path,
|
|
1894
|
-
&charlie_paths.wrapper,
|
|
1895
|
-
&charlie_paths.sessions,
|
|
1896
|
-
&alpha_paths.wrapper,
|
|
1897
|
-
&alpha_paths.sessions,
|
|
1898
|
-
] {
|
|
1899
|
-
assert!(!path.exists(), "precondition: {} must be absent", path.display());
|
|
1900
|
-
}
|
|
1901
|
-
|
|
1902
|
-
let assert_schema_error = |error: crate::lifecycle::LifecycleError| match error {
|
|
1903
|
-
crate::lifecycle::LifecycleError::Compile(message) => {
|
|
1904
|
-
assert!(message.contains("Pi roles require mcp_team"), "{message}");
|
|
1905
|
-
}
|
|
1906
|
-
other => panic!("expected Pi role schema error, got {other:?}"),
|
|
1907
|
-
};
|
|
1908
|
-
let assert_unchanged = |expected_role: &[u8]| {
|
|
1909
|
-
assert_eq!(spec_before, std::fs::read(&spec_path).unwrap());
|
|
1910
|
-
assert_eq!(state_before, std::fs::read(&state_path).unwrap());
|
|
1911
|
-
assert_eq!(expected_role, std::fs::read(&role).unwrap());
|
|
1912
|
-
for path in [
|
|
1913
|
-
&event_path,
|
|
1914
|
-
&copied_role,
|
|
1915
|
-
&team_state_path,
|
|
1916
|
-
&charlie_paths.wrapper,
|
|
1917
|
-
&charlie_paths.sessions,
|
|
1918
|
-
&alpha_paths.wrapper,
|
|
1919
|
-
&alpha_paths.sessions,
|
|
1920
|
-
] {
|
|
1921
|
-
assert!(!path.exists(), "preflight created {}", path.display());
|
|
1922
|
-
}
|
|
1923
|
-
let state = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
1924
|
-
assert_eq!(state["agents"]["alpha"], alpha_before);
|
|
1925
|
-
assert!(state["agents"].get("charlie").is_none(), "no reservation row");
|
|
1926
|
-
assert!(tx.spawns().is_empty());
|
|
1927
|
-
assert!(tx.killed().is_empty());
|
|
1928
|
-
};
|
|
1929
|
-
|
|
1930
|
-
let mut normal_calls = 0;
|
|
1931
|
-
let mut normal_discover = |_requested: &str| {
|
|
1932
|
-
normal_calls += 1;
|
|
1933
|
-
Ok(Vec::new())
|
|
1934
|
-
};
|
|
1935
|
-
let normal = crate::lifecycle::add_agent_with_transport_pi_preflight(
|
|
1936
|
-
&ws,
|
|
1937
|
-
&aid("charlie"),
|
|
1938
|
-
&role,
|
|
1939
|
-
false,
|
|
1940
|
-
None,
|
|
1941
|
-
&tx,
|
|
1942
|
-
&mut normal_discover,
|
|
1943
|
-
)
|
|
1944
|
-
.expect_err("invalid Pi schema must fail before reservation");
|
|
1945
|
-
drop(normal_discover);
|
|
1946
|
-
assert_eq!(normal_calls, 0);
|
|
1947
|
-
assert_schema_error(normal);
|
|
1948
|
-
assert_unchanged(&role_before);
|
|
1949
|
-
|
|
1950
|
-
let mut force_calls = 0;
|
|
1951
|
-
let mut force_discover = |_requested: &str| {
|
|
1952
|
-
force_calls += 1;
|
|
1953
|
-
Ok(Vec::new())
|
|
1954
|
-
};
|
|
1955
|
-
let force = crate::lifecycle::add_agent_with_transport_force_pi_preflight(
|
|
1956
|
-
&ws,
|
|
1957
|
-
&aid("alpha"),
|
|
1958
|
-
&role,
|
|
1959
|
-
false,
|
|
1960
|
-
None,
|
|
1961
|
-
true,
|
|
1962
|
-
&tx,
|
|
1963
|
-
&mut force_discover,
|
|
1964
|
-
)
|
|
1965
|
-
.expect_err("invalid Pi replacement schema must fail before old-seat consumption");
|
|
1966
|
-
drop(force_discover);
|
|
1967
|
-
assert_eq!(force_calls, 0);
|
|
1968
|
-
assert_schema_error(force);
|
|
1969
|
-
assert_unchanged(&role_before);
|
|
1970
|
-
|
|
1971
|
-
std::fs::write(
|
|
1972
|
-
&role,
|
|
1973
|
-
CHARLIE_ROLE.replace(
|
|
1974
|
-
"provider: codex\nmodel: gpt-5.5",
|
|
1975
|
-
"provider: pi\nmodel: gpt-5.6-sol",
|
|
1976
|
-
),
|
|
1977
|
-
)
|
|
1978
|
-
.unwrap();
|
|
1979
|
-
let model_role_before = std::fs::read(&role).unwrap();
|
|
1980
|
-
let assert_model_diagnostic = |error: crate::lifecycle::LifecycleError| match error {
|
|
1981
|
-
crate::lifecycle::LifecycleError::PiModelPreflight {
|
|
1982
|
-
requested,
|
|
1983
|
-
candidates,
|
|
1984
|
-
not_ready,
|
|
1985
|
-
..
|
|
1986
|
-
} => {
|
|
1987
|
-
assert_eq!(requested, "gpt-5.6-sol");
|
|
1988
|
-
assert_eq!(candidates, vec!["openai-codex/gpt-5.6-sol"]);
|
|
1989
|
-
assert!(!not_ready);
|
|
1990
|
-
}
|
|
1991
|
-
other => panic!("expected typed Pi preflight error, got {other:?}"),
|
|
1992
|
-
};
|
|
1993
|
-
for (force, agent_id) in [(false, "charlie"), (true, "alpha")] {
|
|
1994
|
-
let mut calls = 0;
|
|
1995
|
-
let mut discover = |requested: &str| {
|
|
1996
|
-
calls += 1;
|
|
1997
|
-
assert_eq!(requested, "gpt-5.6-sol");
|
|
1998
|
-
Ok(vec!["openai-codex/gpt-5.6-sol".into()])
|
|
1999
|
-
};
|
|
2000
|
-
let error = crate::lifecycle::add_agent_with_transport_force_pi_preflight(
|
|
2001
|
-
&ws,
|
|
2002
|
-
&aid(agent_id),
|
|
2003
|
-
&role,
|
|
2004
|
-
false,
|
|
2005
|
-
None,
|
|
2006
|
-
force,
|
|
2007
|
-
&tx,
|
|
2008
|
-
&mut discover,
|
|
2009
|
-
)
|
|
2010
|
-
.expect_err("unqualified Pi model must fail before mutation");
|
|
2011
|
-
drop(discover);
|
|
2012
|
-
assert_eq!(calls, 1);
|
|
2013
|
-
assert_model_diagnostic(error);
|
|
2014
|
-
assert_unchanged(&model_role_before);
|
|
2015
|
-
}
|
|
2016
|
-
}
|