@team-agent/installer 0.5.119 → 0.5.120
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/README.md +46 -1
- package/README.zh.md +46 -1
- package/crates/team-agent/src/cli/adapters.rs +11 -0
- package/crates/team-agent/src/cli/arguments.rs +401 -0
- package/crates/team-agent/src/cli/emit.rs +241 -142
- package/crates/team-agent/src/cli/intent/runtime.rs +1139 -0
- package/crates/team-agent/src/cli/intent/runtime_tests.rs +584 -0
- package/crates/team-agent/src/cli/intent/tests.rs +654 -0
- package/crates/team-agent/src/cli/intent.rs +642 -0
- package/crates/team-agent/src/cli/mod.rs +49 -5
- package/crates/team-agent/src/cli/named_address.rs +10 -1
- package/crates/team-agent/src/cli/spec.rs +22 -10
- package/crates/team-agent/src/cli/status_port/brief.rs +117 -16
- package/crates/team-agent/src/cli/tests/base.rs +6 -1
- package/crates/team-agent/src/cli/tests/parser_safety.rs +383 -0
- package/crates/team-agent/src/cli/tests/run_delegation.rs +63 -19
- package/crates/team-agent/src/cli/tests/verb_install_skill.rs +3 -2
- package/crates/team-agent/src/cli/tests/verb_profile.rs +3 -1
- package/crates/team-agent/src/contract_runtime/backend.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +166 -77
- package/crates/team-agent/src/lifecycle/launch.rs +28 -0
- package/crates/team-agent/src/lifecycle/restart.rs +17 -0
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1 -3
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +5 -15
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +2 -3
- package/crates/team-agent/src/lifecycle/tests/cli_argv_routing_red.rs +10 -2
- package/crates/team-agent/src/lifecycle/tests/clone_agent_preserves_source_tools.rs +8 -1
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +3 -2
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +6 -9
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +7 -5
- package/crates/team-agent/src/lifecycle/tests/cursor_mcp_overlay.rs +4 -2
- package/crates/team-agent/src/lifecycle/tests/cursor_require_explicit_model_red.rs +5 -2
- package/crates/team-agent/src/lifecycle/tests/cursor_restart_resume_red.rs +4 -3
- package/crates/team-agent/src/lifecycle/tests/grok_effort_argv_red.rs +2 -4
- package/crates/team-agent/src/lifecycle/tests/grok_mcp_overlay_red.rs +3 -4
- package/crates/team-agent/src/lifecycle/tests/grok_require_explicit_model_red.rs +5 -4
- package/crates/team-agent/src/lifecycle/tests/grok_restart_resume_red.rs +2 -4
- package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +78 -36
- package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +8 -4
- package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +6 -12
- package/crates/team-agent/src/lifecycle/tests/pi_fork_session_inheritance_red.rs +4 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1 -3
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +7 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +1 -3
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +2 -1
- package/crates/team-agent/src/lifecycle/types.rs +10 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +37 -0
- package/crates/team-agent/src/tmux_backend.rs +8 -1
- package/package.json +4 -4
- package/skills/team-agent/SKILL.md +71 -54
- package/skills/team-agent/command-coverage.json +283 -13
- package/skills/team-agent/docs/reference/team-agent-operator.md +220 -0
- package/skills/team-agent/references/bug-as-artifact-flow.md +1 -1
- package/skills/team-agent/references/recovery-runbook.md +61 -212
- package/skills/team-agent/references/team-agent-operator.md +181 -48
- package/skills/team-agent/references/team-in-team.md +47 -80
|
@@ -32,7 +32,9 @@ use team_agent::transport::test_support::OfflineTransport;
|
|
|
32
32
|
#[test]
|
|
33
33
|
#[serial(env)]
|
|
34
34
|
fn cursor_role_missing_model_uses_provider_default() {
|
|
35
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-no-model");
|
|
35
36
|
let ws = tmp_dir("cursor-no-model");
|
|
37
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
36
38
|
let team = write_role_team(&ws, "cursortm", "cursor_writer", None, None);
|
|
37
39
|
let transport = OfflineTransport::new();
|
|
38
40
|
quick_start_with_transport_in_workspace(
|
|
@@ -54,8 +56,9 @@ fn cursor_role_missing_model_uses_provider_default() {
|
|
|
54
56
|
#[test]
|
|
55
57
|
#[serial(env)]
|
|
56
58
|
fn cursor_role_explicit_model_reaches_argv() {
|
|
57
|
-
let _hermetic = hermetic_guard::HermeticTestEnv::
|
|
59
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-with-model");
|
|
58
60
|
let ws = tmp_dir("cursor-with-model");
|
|
61
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
59
62
|
let team = write_role_team(
|
|
60
63
|
&ws,
|
|
61
64
|
"cursortm",
|
|
@@ -116,7 +119,7 @@ fn cursor_role_with_effort_refuses_to_start() {
|
|
|
116
119
|
#[test]
|
|
117
120
|
#[serial(env)]
|
|
118
121
|
fn cursor_second_seat_in_same_workspace_is_refused() {
|
|
119
|
-
let _hermetic = hermetic_guard::HermeticTestEnv::
|
|
122
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-two");
|
|
120
123
|
let ws = tmp_dir("cursor-two");
|
|
121
124
|
let team = ws.join("cursortm");
|
|
122
125
|
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
@@ -40,7 +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::
|
|
43
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-fresh");
|
|
44
44
|
let (ws, _home, _guard, team) = seed_cursor("pending");
|
|
45
45
|
let transport = OfflineTransport::new();
|
|
46
46
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
|
|
@@ -80,7 +80,7 @@ fn cursor_fresh_spawn_does_not_invent_session_id() {
|
|
|
80
80
|
#[test]
|
|
81
81
|
#[serial(env)]
|
|
82
82
|
fn cursor_restart_uses_resume_when_session_captured_and_archive_present() {
|
|
83
|
-
let _hermetic = hermetic_guard::HermeticTestEnv::
|
|
83
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-resume");
|
|
84
84
|
let (ws, home, _guard, team) = seed_cursor("resume");
|
|
85
85
|
let first = OfflineTransport::new();
|
|
86
86
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &first)
|
|
@@ -182,7 +182,7 @@ fn cursor_classify_resumes_when_archive_present_and_refuses_when_missing() {
|
|
|
182
182
|
#[test]
|
|
183
183
|
#[serial(env)]
|
|
184
184
|
fn cursor_fork_agent_refuses_as_unsupported_not_unverified() {
|
|
185
|
-
let _hermetic = hermetic_guard::HermeticTestEnv::
|
|
185
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("cursor-fork");
|
|
186
186
|
let (ws, _home, _guard, team) = seed_cursor("fork");
|
|
187
187
|
let transport = OfflineTransport::new();
|
|
188
188
|
quick_start_with_transport_in_workspace(&ws, &team, None, true, Some("cursortm"), &transport)
|
|
@@ -280,6 +280,7 @@ fn seed_healthy_coordinator(workspace: &Path) {
|
|
|
280
280
|
|
|
281
281
|
fn seed_cursor(tag: &str) -> (PathBuf, PathBuf, HomeGuard, PathBuf) {
|
|
282
282
|
let ws = tmp_dir(tag);
|
|
283
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
283
284
|
let home = tmp_dir(&format!("{tag}-home"));
|
|
284
285
|
let guard = HomeGuard::set(&home);
|
|
285
286
|
let team = write_cursor_role(&ws);
|
|
@@ -22,10 +22,7 @@ mod hermetic_guard;
|
|
|
22
22
|
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
23
23
|
|
|
24
24
|
fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
|
|
25
|
-
|
|
26
|
-
env.scrub_tmux();
|
|
27
|
-
env.assert_no_real_tmux();
|
|
28
|
-
env
|
|
25
|
+
hermetic_guard::HermeticTestEnv::enter_with_leader(tag)
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
use std::path::{Path, PathBuf};
|
|
@@ -159,6 +156,7 @@ fn seed_healthy_coordinator(workspace: &Path) {
|
|
|
159
156
|
|
|
160
157
|
fn seed_grok(tag: &str, effort: Option<&str>) -> (PathBuf, PathBuf, HomeGuard, PathBuf) {
|
|
161
158
|
let ws = tmp_dir(tag);
|
|
159
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
162
160
|
let home = tmp_dir(&format!("{tag}-home"));
|
|
163
161
|
seed_grok_home(&home, Some(&ws));
|
|
164
162
|
let guard = HomeGuard::set(&home);
|
|
@@ -15,10 +15,7 @@ mod hermetic_guard;
|
|
|
15
15
|
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
16
16
|
|
|
17
17
|
fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
|
|
18
|
-
|
|
19
|
-
env.scrub_tmux();
|
|
20
|
-
env.assert_no_real_tmux();
|
|
21
|
-
env
|
|
18
|
+
hermetic_guard::HermeticTestEnv::enter_with_leader(tag)
|
|
22
19
|
}
|
|
23
20
|
|
|
24
21
|
use std::path::{Path, PathBuf};
|
|
@@ -218,6 +215,7 @@ fn two_grok_seats_coexist_when_toml_has_no_per_seat_keys() {
|
|
|
218
215
|
fn leftover_per_seat_key_is_cleared_before_start() {
|
|
219
216
|
let _hermetic = enter_hermetic("grok-overlay-leftover-seat");
|
|
220
217
|
let ws = tmp_dir("grok-cwd-per-seat");
|
|
218
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
221
219
|
let home = tmp_dir("grok-cwd-per-seat-home");
|
|
222
220
|
seed_grok_home(&home, Some(&ws));
|
|
223
221
|
let _guard = HomeGuard::set(&home);
|
|
@@ -345,6 +343,7 @@ fn grok_missing_login_refuses_to_start() {
|
|
|
345
343
|
fn grok_spawn_writes_resolved_team_agent_into_project_grok_config() {
|
|
346
344
|
let _hermetic = enter_hermetic("grok-overlay-spawn");
|
|
347
345
|
let ws = tmp_dir("grok-mcp-overlay");
|
|
346
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
348
347
|
let home = tmp_dir("grok-mcp-overlay-home");
|
|
349
348
|
seed_grok_home(&home, Some(&ws));
|
|
350
349
|
let _guard = HomeGuard::set(&home);
|
|
@@ -14,10 +14,7 @@ mod hermetic_guard;
|
|
|
14
14
|
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
15
15
|
|
|
16
16
|
fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
|
|
17
|
-
|
|
18
|
-
env.scrub_tmux();
|
|
19
|
-
env.assert_no_real_tmux();
|
|
20
|
-
env
|
|
17
|
+
hermetic_guard::HermeticTestEnv::enter_with_leader(tag)
|
|
21
18
|
}
|
|
22
19
|
|
|
23
20
|
use std::path::{Path, PathBuf};
|
|
@@ -31,7 +28,9 @@ use team_agent::transport::test_support::OfflineTransport;
|
|
|
31
28
|
#[test]
|
|
32
29
|
#[serial(env)]
|
|
33
30
|
fn grok_role_missing_model_uses_provider_default() {
|
|
31
|
+
let _hermetic = enter_hermetic("grok-no-model");
|
|
34
32
|
let ws = tmp_dir("grok-no-model");
|
|
33
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
35
34
|
let home = tmp_dir("grok-no-model-home");
|
|
36
35
|
seed_grok_home(&home, Some(&ws));
|
|
37
36
|
let _guard = HomeGuard::set(&home);
|
|
@@ -67,6 +66,7 @@ fn grok_role_missing_model_uses_provider_default() {
|
|
|
67
66
|
fn grok_role_explicit_model_reaches_argv() {
|
|
68
67
|
let _hermetic = enter_hermetic("grok-explicit-model");
|
|
69
68
|
let ws = tmp_dir("grok-with-model");
|
|
69
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
70
70
|
let home = tmp_dir("grok-with-model-home");
|
|
71
71
|
seed_grok_home(&home, Some(&ws));
|
|
72
72
|
let _guard = HomeGuard::set(&home);
|
|
@@ -103,6 +103,7 @@ fn grok_role_explicit_model_reaches_argv() {
|
|
|
103
103
|
fn claude_role_missing_model_still_starts() {
|
|
104
104
|
let _hermetic = enter_hermetic("grok-claude-missing-model");
|
|
105
105
|
let ws = tmp_dir("claude-no-model");
|
|
106
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
106
107
|
let team = write_role_team(
|
|
107
108
|
&ws,
|
|
108
109
|
"claudeteam",
|
|
@@ -22,10 +22,7 @@ mod hermetic_guard;
|
|
|
22
22
|
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
23
23
|
|
|
24
24
|
fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
|
|
25
|
-
|
|
26
|
-
env.scrub_tmux();
|
|
27
|
-
env.assert_no_real_tmux();
|
|
28
|
-
env
|
|
25
|
+
hermetic_guard::HermeticTestEnv::enter_with_leader(tag)
|
|
29
26
|
}
|
|
30
27
|
|
|
31
28
|
use std::path::{Path, PathBuf};
|
|
@@ -212,6 +209,7 @@ fn seed_healthy_coordinator(workspace: &Path) {
|
|
|
212
209
|
|
|
213
210
|
fn seed_grok(tag: &str) -> (PathBuf, PathBuf, HomeGuard, PathBuf) {
|
|
214
211
|
let ws = tmp_dir(tag);
|
|
212
|
+
super::launch_spawn::seed_healthy_coordinator(&ws);
|
|
215
213
|
let home = tmp_dir(&format!("{tag}-home"));
|
|
216
214
|
seed_grok_home(&home, Some(&ws));
|
|
217
215
|
let guard = HomeGuard::set(&home);
|
|
@@ -174,8 +174,9 @@ fn unowned_running_ws() -> PathBuf {
|
|
|
174
174
|
#[test]
|
|
175
175
|
#[serial(env)]
|
|
176
176
|
fn quick_start_compiles_real_spec_to_team_spec_yaml() {
|
|
177
|
-
let _hermetic =
|
|
177
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-compile-spec");
|
|
178
178
|
let team = quick_start_team_dir(QS_VALID_ROLE);
|
|
179
|
+
seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
|
|
179
180
|
let transport = OfflineTransport::new();
|
|
180
181
|
let result = quick_start_with_transport(&team, None, true, None, &transport);
|
|
181
182
|
|
|
@@ -209,9 +210,10 @@ fn quick_start_compiles_real_spec_to_team_spec_yaml() {
|
|
|
209
210
|
#[test]
|
|
210
211
|
#[serial(env)]
|
|
211
212
|
fn quick_start_without_team_id_uses_compiled_team_name_as_canonical_key() {
|
|
212
|
-
let _hermetic =
|
|
213
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-canonical-team");
|
|
213
214
|
let team = quick_start_team_dir(QS_VALID_ROLE);
|
|
214
215
|
let workspace = crate::model::paths::team_workspace(&team).unwrap();
|
|
216
|
+
seed_healthy_coordinator(&workspace);
|
|
215
217
|
let transport = OfflineTransport::new();
|
|
216
218
|
|
|
217
219
|
let result = quick_start_with_transport(&team, None, true, None, &transport);
|
|
@@ -231,8 +233,9 @@ fn quick_start_without_team_id_uses_compiled_team_name_as_canonical_key() {
|
|
|
231
233
|
#[test]
|
|
232
234
|
#[serial(env)]
|
|
233
235
|
fn existing_runtime_producer_scopes_canonical_team() {
|
|
234
|
-
let _hermetic =
|
|
236
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-existing-producer");
|
|
235
237
|
let team = quick_start_team_dir(QS_VALID_ROLE);
|
|
238
|
+
seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
|
|
236
239
|
let transport = OfflineTransport::new();
|
|
237
240
|
let first = quick_start_with_transport(&team, Some("from-name"), true, None, &transport)
|
|
238
241
|
.expect("first quick-start");
|
|
@@ -342,8 +345,9 @@ fn existing_runtime_empty_top_level_agents_still_emits_bob() {
|
|
|
342
345
|
#[test]
|
|
343
346
|
#[serial(env)]
|
|
344
347
|
fn quick_start_teamdir_under_dot_team_uses_project_workspace_for_status_and_collect() {
|
|
345
|
-
let _hermetic =
|
|
348
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-dot-team-workspace");
|
|
346
349
|
let workspace = temp_ws();
|
|
350
|
+
seed_healthy_coordinator(&workspace);
|
|
347
351
|
let team = workspace.join(".team").join("current");
|
|
348
352
|
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
349
353
|
std::fs::write(team.join("TEAM.md"), QS_TEAM_MD).unwrap();
|
|
@@ -417,7 +421,7 @@ fn quick_start_teamdir_under_dot_team_uses_project_workspace_for_status_and_coll
|
|
|
417
421
|
#[test]
|
|
418
422
|
#[serial(env)]
|
|
419
423
|
fn quick_start_default_workspace_compiled_spec_uses_project_root() {
|
|
420
|
-
let _hermetic =
|
|
424
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-default-workspace");
|
|
421
425
|
let workspace = temp_ws();
|
|
422
426
|
std::fs::create_dir_all(workspace.join("agents")).unwrap();
|
|
423
427
|
std::fs::write(workspace.join("TEAM.md"), QS_TEAM_MD).unwrap();
|
|
@@ -724,8 +728,9 @@ fn unowned_running_ws_all_paused() -> PathBuf {
|
|
|
724
728
|
#[test]
|
|
725
729
|
#[serial(env)]
|
|
726
730
|
fn spine_quick_start_seeds_state_under_team_workspace_not_team_dir() {
|
|
727
|
-
let _hermetic =
|
|
731
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-spine-workspace");
|
|
728
732
|
let team = quick_start_team_dir(QS_VALID_ROLE); // <base>/teamdir
|
|
733
|
+
seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
|
|
729
734
|
let transport = OfflineTransport::new();
|
|
730
735
|
let _ = quick_start_with_transport(&team, None, true, None, &transport);
|
|
731
736
|
let workspace = team.parent().unwrap(); // team_workspace(<base>/teamdir) = <base>
|
|
@@ -1302,21 +1307,32 @@ fn start_agent_session_generation_inherits_birth_or_stamps_recreated_session() {
|
|
|
1302
1307
|
/// Seed a HEALTHY coordinator at `workspace` (this process's pid + matching metadata + db schema) so
|
|
1303
1308
|
/// quick_start's internal start_coordinator returns AlreadyRunning — NO real daemon subprocess spawn.
|
|
1304
1309
|
pub(super) fn seed_healthy_coordinator(workspace: &std::path::Path) {
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
)
|
|
1314
|
-
.
|
|
1315
|
-
std::fs::
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
)
|
|
1319
|
-
|
|
1310
|
+
hermetic_guard::seed_in_process_coordinator(workspace);
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
#[test]
|
|
1314
|
+
#[cfg(unix)]
|
|
1315
|
+
#[serial(env)]
|
|
1316
|
+
fn issue337_owned_fixture_caller_admits_fresh_launch_without_guard_bypass() {
|
|
1317
|
+
let env = hermetic_guard::HermeticTestEnv::enter_with_leader("issue337-fixture-caller");
|
|
1318
|
+
let workspace = env.workspace("ws");
|
|
1319
|
+
let team = workspace.join("roles");
|
|
1320
|
+
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
1321
|
+
std::fs::write(team.join("TEAM.md"), QS_TEAM_MD).unwrap();
|
|
1322
|
+
std::fs::write(team.join("agents/implementer.md"), QS_VALID_ROLE).unwrap();
|
|
1323
|
+
seed_healthy_coordinator(&workspace);
|
|
1324
|
+
let transport = OfflineTransport::new();
|
|
1325
|
+
let report = quick_start_with_transport_in_workspace(
|
|
1326
|
+
&workspace, &team, None, true, Some("fixture-team"), &transport,
|
|
1327
|
+
).unwrap();
|
|
1328
|
+
let QuickStartReport::Ready { launch, .. } = report else { panic!("owned caller must admit launch: {report:?}") };
|
|
1329
|
+
assert!(launch.leader_receiver_attached);
|
|
1330
|
+
assert_eq!(launch.started.len(), 1);
|
|
1331
|
+
assert_eq!(transport.spawn_records().len(), 1);
|
|
1332
|
+
let socket = std::env::var("TMUX").unwrap().split(',').next().unwrap().to_string();
|
|
1333
|
+
assert!(std::path::Path::new(&socket).exists());
|
|
1334
|
+
drop(env);
|
|
1335
|
+
assert!(!std::path::Path::new(&socket).exists());
|
|
1320
1336
|
}
|
|
1321
1337
|
|
|
1322
1338
|
fn seed_canonical_team_identity(workspace: &std::path::Path, team_key: &str) {
|
|
@@ -1333,7 +1349,7 @@ fn seed_canonical_team_identity(workspace: &std::path::Path, team_key: &str) {
|
|
|
1333
1349
|
#[test]
|
|
1334
1350
|
#[serial(env)]
|
|
1335
1351
|
fn quick_start_with_transport_spawns_workers_not_dry_run() {
|
|
1336
|
-
let _hermetic =
|
|
1352
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-spawn-path");
|
|
1337
1353
|
let team = quick_start_team_dir(QS_VALID_ROLE); // one agent: implementer / provider codex
|
|
1338
1354
|
let workspace = team.parent().expect("team_workspace(team_dir) = parent"); // where start_coordinator runs
|
|
1339
1355
|
seed_healthy_coordinator(workspace);
|
|
@@ -1422,7 +1438,7 @@ fn adaptive_layout_plan_8_workers_is_3_3_2() {
|
|
|
1422
1438
|
#[test]
|
|
1423
1439
|
#[serial(env)]
|
|
1424
1440
|
fn quick_start_default_adaptive_groups_workers_into_layout_panes() {
|
|
1425
|
-
let _hermetic =
|
|
1441
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-adaptive-layout");
|
|
1426
1442
|
let roles = (1..=4)
|
|
1427
1443
|
.map(|i| {
|
|
1428
1444
|
let id = format!("w{i}");
|
|
@@ -1507,7 +1523,7 @@ fn quick_start_tmux_backend_uses_workspace_private_endpoint() {
|
|
|
1507
1523
|
#[test]
|
|
1508
1524
|
#[serial(env)]
|
|
1509
1525
|
fn quick_start_persists_selected_tmux_endpoint_and_attach_commands() {
|
|
1510
|
-
let _hermetic =
|
|
1526
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-endpoint-attach");
|
|
1511
1527
|
let team = quick_start_team_dir(QS_VALID_ROLE);
|
|
1512
1528
|
let workspace = team.parent().expect("team_workspace(team_dir) = parent");
|
|
1513
1529
|
seed_healthy_coordinator(workspace);
|
|
@@ -1568,23 +1584,27 @@ fn annotate_runtime_tmux_endpoint_persists_workspace_socket_as_full_path() {
|
|
|
1568
1584
|
#[test]
|
|
1569
1585
|
#[serial(env)]
|
|
1570
1586
|
fn quick_start_preserves_managed_leader_topology_and_emits_leader_attach_command() {
|
|
1571
|
-
let
|
|
1572
|
-
let
|
|
1573
|
-
let
|
|
1587
|
+
let hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-managed-leader");
|
|
1588
|
+
let workspace = hermetic.root();
|
|
1589
|
+
let team = workspace.join("teamdir");
|
|
1590
|
+
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
1591
|
+
std::fs::write(team.join("TEAM.md"), QS_TEAM_MD).unwrap();
|
|
1592
|
+
std::fs::write(team.join("agents/implementer.md"), QS_VALID_ROLE).unwrap();
|
|
1574
1593
|
seed_healthy_coordinator(workspace);
|
|
1594
|
+
let endpoint = std::env::var("TMUX").unwrap().split(',').next().unwrap().to_string();
|
|
1575
1595
|
crate::state::persist::save_runtime_state(
|
|
1576
1596
|
workspace,
|
|
1577
1597
|
&json!({
|
|
1578
1598
|
"active_team_key": "teamdir",
|
|
1579
|
-
"session_name":
|
|
1599
|
+
"session_name": null,
|
|
1580
1600
|
"is_external_leader": false,
|
|
1581
1601
|
"leader_client": {"diagnostic_only": true, "attach_mode": "attach-session"},
|
|
1582
|
-
"
|
|
1583
|
-
"
|
|
1602
|
+
"tmux_socket": endpoint,
|
|
1603
|
+
"tmux_endpoint": endpoint,
|
|
1584
1604
|
"agents": {},
|
|
1585
1605
|
"teams": {
|
|
1586
1606
|
"teamdir": {
|
|
1587
|
-
"session_name":
|
|
1607
|
+
"session_name": null,
|
|
1588
1608
|
"is_external_leader": false,
|
|
1589
1609
|
"leader_client": {"diagnostic_only": true, "attach_mode": "attach-session"},
|
|
1590
1610
|
"agents": {}
|
|
@@ -1593,9 +1613,25 @@ fn quick_start_preserves_managed_leader_topology_and_emits_leader_attach_command
|
|
|
1593
1613
|
}),
|
|
1594
1614
|
)
|
|
1595
1615
|
.expect("seed managed leader state");
|
|
1616
|
+
let attached = crate::cli::cmd_attach_leader(&crate::cli::AttachLeaderArgs {
|
|
1617
|
+
workspace: workspace.to_path_buf(), team: Some("teamdir".into()), pane: None,
|
|
1618
|
+
provider: crate::model::enums::Provider::Fake, confirm: false, json: true,
|
|
1619
|
+
}).expect("bind the real owned caller through the public lease port");
|
|
1620
|
+
assert_eq!(attached.exit, crate::cli::ExitCode::Ok, "{attached:?}");
|
|
1621
|
+
let mut state = crate::state::persist::load_runtime_state(workspace).unwrap();
|
|
1622
|
+
assert!(state["session_name"].is_null(), "leader-only bootstrap has no worker session");
|
|
1623
|
+
assert!(state["teams"]["teamdir"]["session_name"].is_null());
|
|
1624
|
+
assert!(state["teams"]["teamdir"]["agents"].as_object().unwrap().is_empty());
|
|
1625
|
+
// The owned fake caller models the already-managed launcher topology;
|
|
1626
|
+
// retain the real binding while setting the presentation fixture metadata.
|
|
1627
|
+
state["is_external_leader"] = json!(false);
|
|
1628
|
+
state["leader_client"] = json!({"diagnostic_only": true, "attach_mode": "attach-session"});
|
|
1629
|
+
state["teams"]["teamdir"]["is_external_leader"] = json!(false);
|
|
1630
|
+
state["teams"]["teamdir"]["leader_client"] = state["leader_client"].clone();
|
|
1631
|
+
crate::state::persist::save_runtime_state(workspace, &state).unwrap();
|
|
1596
1632
|
let transport = OfflineTransport::new();
|
|
1597
1633
|
|
|
1598
|
-
let report = quick_start_with_transport(&team, None, true,
|
|
1634
|
+
let report = quick_start_with_transport(&team, None, true, Some("teamdir"), &transport)
|
|
1599
1635
|
.expect("quick_start_with_transport must reach Ready");
|
|
1600
1636
|
let attach_commands = match report {
|
|
1601
1637
|
QuickStartReport::Ready {
|
|
@@ -2872,8 +2908,9 @@ fn stop_agent_adaptive_kills_target_pane_not_shared_layout_window() {
|
|
|
2872
2908
|
#[test]
|
|
2873
2909
|
#[serial(env)]
|
|
2874
2910
|
fn quick_start_seeds_tasks_key_from_compiled_spec() {
|
|
2875
|
-
let _hermetic =
|
|
2911
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-tasks-key");
|
|
2876
2912
|
let team = quick_start_team_dir(QS_VALID_ROLE);
|
|
2913
|
+
seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
|
|
2877
2914
|
let transport = OfflineTransport::new();
|
|
2878
2915
|
let _ = quick_start_with_transport(&team, None, true, None, &transport);
|
|
2879
2916
|
let workspace = team
|
|
@@ -2921,7 +2958,7 @@ fn quick_start_seeds_tasks_key_from_compiled_spec() {
|
|
|
2921
2958
|
// suffix (active_team_key, teams).
|
|
2922
2959
|
#[test]
|
|
2923
2960
|
#[serial(env)]
|
|
2924
|
-
fn
|
|
2961
|
+
fn quick_start_unbound_state_producer_seeds_spec_path_workspace_leader() {
|
|
2925
2962
|
let _hermetic = enter_hermetic("launch-spawn-state-seeds");
|
|
2926
2963
|
// Bug 2 owner team-scope: top-level owner triple is dropped when the seeded
|
|
2927
2964
|
// pane is empty; an ambient TMUX_PANE (tests run inside the dev's tmux session)
|
|
@@ -2932,10 +2969,14 @@ fn quick_start_state_seeds_spec_path_workspace_leader() {
|
|
|
2932
2969
|
let _tmux = EnvVarGuard::unset("TMUX");
|
|
2933
2970
|
let team = quick_start_team_dir(QS_VALID_ROLE);
|
|
2934
2971
|
let transport = OfflineTransport::new();
|
|
2935
|
-
let _ = quick_start_with_transport(&team, None, true, None, &transport);
|
|
2936
2972
|
let workspace = team
|
|
2937
2973
|
.parent()
|
|
2938
2974
|
.expect("team_workspace(<base>/teamdir) = <base>");
|
|
2975
|
+
// Test the real unbound state producer plus recording-only worker stage.
|
|
2976
|
+
// Public start correctly refuses this absent caller (separate regressions).
|
|
2977
|
+
crate::lifecycle::launch::materialize_unbound_state_for_test(
|
|
2978
|
+
workspace, &team, "quickteam", &transport,
|
|
2979
|
+
).expect("materialize the unbound producer fixture without public start");
|
|
2939
2980
|
// E5: spec_path in state now points to .team/runtime/<team_key>/, team_dir stays the user role dir.
|
|
2940
2981
|
let spec_path = quick_start_runtime_spec_path(&team);
|
|
2941
2982
|
let (raw, state) = raw_runtime_state(workspace);
|
|
@@ -3018,10 +3059,11 @@ fn quick_start_state_seeds_spec_path_workspace_leader() {
|
|
|
3018
3059
|
#[test]
|
|
3019
3060
|
#[serial(env)]
|
|
3020
3061
|
fn quick_start_running_agent_state_shape_after_spawn_is_golden() {
|
|
3021
|
-
let _hermetic =
|
|
3062
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("launch-spawn-running-state");
|
|
3022
3063
|
const FIXED_SPAWNED_AT: &str = "2026-06-04T00:00:00+00:00";
|
|
3023
3064
|
let _clock_guard = EnvVarGuard::set("TEAM_AGENT_TEST_FIXED_SPAWNED_AT", FIXED_SPAWNED_AT);
|
|
3024
3065
|
let team = quick_start_team_dir(QS_VALID_ROLE);
|
|
3066
|
+
seed_healthy_coordinator(&crate::model::paths::team_workspace(&team).unwrap());
|
|
3025
3067
|
let transport = OfflineTransport::new();
|
|
3026
3068
|
let _ = quick_start_with_transport(&team, None, true, None, &transport);
|
|
3027
3069
|
let workspace = team
|
|
@@ -32,7 +32,7 @@ fn enter_hermetic(tag: &str) -> hermetic_guard::HermeticTestEnv {
|
|
|
32
32
|
#[test]
|
|
33
33
|
#[serial_test::serial(env)]
|
|
34
34
|
fn concurrent_reset_discard_session_serializes() {
|
|
35
|
-
let _hermetic =
|
|
35
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-concurrent-reset");
|
|
36
36
|
let ids = (1..=6).map(|n| format!("w{n}")).collect::<Vec<_>>();
|
|
37
37
|
let role_docs = ids
|
|
38
38
|
.iter()
|
|
@@ -139,7 +139,7 @@ fn concurrent_reset_discard_session_serializes() {
|
|
|
139
139
|
#[test]
|
|
140
140
|
#[serial_test::serial(env)]
|
|
141
141
|
fn add_fork_remove_share_lifecycle_lock_behavior() {
|
|
142
|
-
let _hermetic =
|
|
142
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-fork");
|
|
143
143
|
let _home = LaneHomeGuard::enter("phase-b-fork");
|
|
144
144
|
let (add_team, add_workspace, add_role, add_transport) = add_fixture();
|
|
145
145
|
let add_agent = aid("w2");
|
|
@@ -324,7 +324,7 @@ fn add_fork_remove_share_lifecycle_lock_behavior() {
|
|
|
324
324
|
#[test]
|
|
325
325
|
#[serial_test::serial(env)]
|
|
326
326
|
fn breal_spawn_ownership_mismatch_fails_without_state_pollution() {
|
|
327
|
-
let _hermetic =
|
|
327
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-spawn-ownership");
|
|
328
328
|
let (workspace, _team) = breal_workspace();
|
|
329
329
|
let transport = BRealTransport::misowned();
|
|
330
330
|
|
|
@@ -366,7 +366,9 @@ fn breal_spawn_ownership_mismatch_fails_without_state_pollution() {
|
|
|
366
366
|
}
|
|
367
367
|
|
|
368
368
|
#[test]
|
|
369
|
+
#[serial_test::serial(env)]
|
|
369
370
|
fn breal_provider_missing_window_disappeared_fails_closed() {
|
|
371
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-missing-window");
|
|
370
372
|
let (workspace, _team) = breal_workspace();
|
|
371
373
|
let transport = BRealTransport::dead_after_spawn();
|
|
372
374
|
|
|
@@ -404,7 +406,9 @@ fn breal_provider_missing_window_disappeared_fails_closed() {
|
|
|
404
406
|
}
|
|
405
407
|
|
|
406
408
|
#[test]
|
|
409
|
+
#[serial_test::serial(env)]
|
|
407
410
|
fn breal_reset_rehydrates_role_context_from_compiled_spec() {
|
|
411
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-reset-context");
|
|
408
412
|
let (workspace, _team) = breal_workspace();
|
|
409
413
|
strip_runtime_command_context(&workspace);
|
|
410
414
|
let transport = BRealTransport::owned();
|
|
@@ -438,7 +442,7 @@ fn breal_reset_rehydrates_role_context_from_compiled_spec() {
|
|
|
438
442
|
#[test]
|
|
439
443
|
#[serial_test::serial(env)]
|
|
440
444
|
fn breal_restart_rehydrates_role_context_from_compiled_spec() {
|
|
441
|
-
let _hermetic =
|
|
445
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("phase-b-restart-role-context");
|
|
442
446
|
let (workspace, _team) = breal_one_worker_workspace();
|
|
443
447
|
strip_runtime_command_context(&workspace);
|
|
444
448
|
let transport = BRealTransport::owned();
|
|
@@ -94,8 +94,8 @@ fn hermetic_caller_envs() -> &'static [&'static str] {
|
|
|
94
94
|
|
|
95
95
|
#[test]
|
|
96
96
|
#[serial_test::serial(env)]
|
|
97
|
-
fn
|
|
98
|
-
let diagnose =
|
|
97
|
+
fn phase_golden_missing_registry_requires_index_publication_not_claim() {
|
|
98
|
+
let diagnose = run_diagnose_after_unbound_state_fixture();
|
|
99
99
|
let issues = diagnose
|
|
100
100
|
.get("issues")
|
|
101
101
|
.and_then(Value::as_array)
|
|
@@ -177,22 +177,16 @@ fn cmd_value(result: Result<CmdResult, crate::cli::CliError>) -> Value {
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
fn
|
|
180
|
+
fn run_diagnose_after_unbound_state_fixture() -> Value {
|
|
181
181
|
let hermetic = HermeticTestEnv::enter("tombstone-claim-leader");
|
|
182
182
|
let _permission_mode = EnvVarGuard::set(ANCESTRY_ENV, "[]");
|
|
183
183
|
let team = two_worker_team_dir(&hermetic);
|
|
184
184
|
let workspace = team.parent().expect("workspace").to_path_buf();
|
|
185
185
|
seed_healthy_coordinator(&workspace);
|
|
186
186
|
let launch_transport = codex_ready_transport();
|
|
187
|
-
|
|
188
|
-
&workspace,
|
|
189
|
-
|
|
190
|
-
None,
|
|
191
|
-
true,
|
|
192
|
-
None,
|
|
193
|
-
&launch_transport,
|
|
194
|
-
)
|
|
195
|
-
.expect("tombstone fixture must quick-start");
|
|
187
|
+
crate::lifecycle::launch::materialize_unbound_state_for_test(
|
|
188
|
+
&workspace, &team, "teamdir", &launch_transport,
|
|
189
|
+
).expect("materialize the recording-only tombstone fixture, not a callerless public start");
|
|
196
190
|
let mut state = crate::state::persist::load_runtime_state(&workspace)
|
|
197
191
|
.expect("tombstone fixture must persist runtime state");
|
|
198
192
|
crate::lifecycle::launch::seed_launched_owner_from_caller_with_provider_lookup(
|
|
@@ -90,6 +90,7 @@ impl Drop for PathEnvGuard {
|
|
|
90
90
|
struct Fixture {
|
|
91
91
|
#[cfg(unix)]
|
|
92
92
|
_path_guard: PathEnvGuard,
|
|
93
|
+
_caller: hermetic_guard::HermeticTestEnv,
|
|
93
94
|
root: PathBuf,
|
|
94
95
|
team_dir: PathBuf,
|
|
95
96
|
run_workspace: PathBuf,
|
|
@@ -103,6 +104,7 @@ struct Fixture {
|
|
|
103
104
|
|
|
104
105
|
impl Fixture {
|
|
105
106
|
fn new() -> Self {
|
|
107
|
+
let caller = hermetic_guard::HermeticTestEnv::enter_with_leader("pi-fork-owned-caller");
|
|
106
108
|
let root = super::temp_ws();
|
|
107
109
|
#[cfg(unix)]
|
|
108
110
|
let path_guard = PathEnvGuard::with_fake_pi(&root);
|
|
@@ -122,6 +124,7 @@ impl Fixture {
|
|
|
122
124
|
)
|
|
123
125
|
.expect("write referenced profile");
|
|
124
126
|
|
|
127
|
+
super::launch_spawn::seed_healthy_coordinator(&root);
|
|
125
128
|
let setup = team_agent::lifecycle::launch::quick_start_with_transport(
|
|
126
129
|
&team_dir,
|
|
127
130
|
None,
|
|
@@ -198,6 +201,7 @@ impl Fixture {
|
|
|
198
201
|
let fixture = Self {
|
|
199
202
|
#[cfg(unix)]
|
|
200
203
|
_path_guard: path_guard,
|
|
204
|
+
_caller: caller,
|
|
201
205
|
root,
|
|
202
206
|
team_dir,
|
|
203
207
|
run_workspace,
|
|
@@ -468,9 +468,7 @@ struct LaunchLatencyCase {
|
|
|
468
468
|
|
|
469
469
|
impl LaunchLatencyCase {
|
|
470
470
|
fn new(tag: &str, workers: usize) -> Self {
|
|
471
|
-
let env = hermetic_guard::HermeticTestEnv::
|
|
472
|
-
env.scrub_tmux();
|
|
473
|
-
env.assert_no_real_tmux();
|
|
471
|
+
let env = hermetic_guard::HermeticTestEnv::enter_with_leader(tag);
|
|
474
472
|
let workspace = env.workspace(tag);
|
|
475
473
|
let team_dir = workspace.join("teamdir");
|
|
476
474
|
fs::create_dir_all(&team_dir).expect("create launch team dir");
|
|
@@ -355,6 +355,13 @@ fn red6_cli_restart_reports_and_top_level_error_logs_are_redacted() {
|
|
|
355
355
|
combined.push_str(&String::from_utf8_lossy(&output.stdout));
|
|
356
356
|
combined.push_str(&String::from_utf8_lossy(&output.stderr));
|
|
357
357
|
}
|
|
358
|
+
assert!(read_cli_error_logs(&workspace).is_empty(),
|
|
359
|
+
"invalid profile syntax must not create a runtime diagnostic log");
|
|
360
|
+
// Keep the original physical-log redaction assertion on the same actual
|
|
361
|
+
// runtime-error emitter, separately from the new zero-log Usage boundary.
|
|
362
|
+
assert_eq!(crate::cli::emit::__test_runtime_error_emitter(
|
|
363
|
+
"restart", &workspace, &crate::cli::CliError::Runtime(diagnostic.clone())
|
|
364
|
+
), crate::cli::ExitCode::Error);
|
|
358
365
|
let logs = read_cli_error_logs(&workspace);
|
|
359
366
|
let output_safe = !combined.contains(&marker) && combined.contains(REDACTED);
|
|
360
367
|
let logs_safe = !logs.contains(&marker) && logs.contains(REDACTED);
|
|
@@ -35,10 +35,8 @@ use team_agent::transport::{
|
|
|
35
35
|
#[test]
|
|
36
36
|
#[serial(env)]
|
|
37
37
|
fn quick_start_team_id_persists_requested_team_key_for_lifecycle_selection() {
|
|
38
|
-
let hermetic = hermetic_guard::HermeticTestEnv::enter("team-in-team-requested-key");
|
|
39
|
-
hermetic.scrub_tmux();
|
|
40
|
-
hermetic.assert_no_real_tmux();
|
|
41
38
|
let _env = EnvGuard::unset();
|
|
39
|
+
let _hermetic = hermetic_guard::HermeticTestEnv::enter_with_leader("team-in-team-requested-key");
|
|
42
40
|
let root = tmp_dir("team-id-key");
|
|
43
41
|
seed_healthy_coordinator(&root);
|
|
44
42
|
let team_dir = write_team_dir(&root, "teamdir", "w1");
|
|
@@ -46,8 +46,9 @@ const CLAUDE_ROLE: &str = "---\nname: clauder\nrole: Claude Worker\nprovider: cl
|
|
|
46
46
|
#[test]
|
|
47
47
|
#[serial(env)]
|
|
48
48
|
fn worker_mcp_owner_team_scope_must_match_runtime_team_key_not_spec_name() {
|
|
49
|
-
let hermetic = HermeticTestEnv::
|
|
49
|
+
let hermetic = HermeticTestEnv::enter_with_leader("rs031-mcp-owner-scope");
|
|
50
50
|
let team = mixed_provider_team_dir(&hermetic, "mcp-owner-scope");
|
|
51
|
+
super::launch_spawn::seed_healthy_coordinator(team.parent().unwrap());
|
|
51
52
|
let transport = RecordingTransport::new()
|
|
52
53
|
.with_windows(vec![WindowName::new("clauder"), WindowName::new("codexer")]);
|
|
53
54
|
let launch = ready_launch(
|
|
@@ -512,6 +512,16 @@ pub enum QuickStartReport {
|
|
|
512
512
|
worker_readiness: QuickStartReadiness,
|
|
513
513
|
team: String,
|
|
514
514
|
},
|
|
515
|
+
/// Workers may exist, but post-spawn admission/coordinator completion failed.
|
|
516
|
+
/// Preserve confirmed effects; never disguise this as a preflight refusal.
|
|
517
|
+
Partial {
|
|
518
|
+
workspace: PathBuf,
|
|
519
|
+
session_name: SessionName,
|
|
520
|
+
launch: Box<LaunchReport>,
|
|
521
|
+
team: String,
|
|
522
|
+
error: String,
|
|
523
|
+
coordinator_state: String,
|
|
524
|
+
},
|
|
515
525
|
/// 已有 runtime state → quick-start 拒绝并引导用 restart。
|
|
516
526
|
ExistingRuntime {
|
|
517
527
|
team: Option<String>,
|