@team-agent/installer 0.5.60 → 0.5.62
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 +49 -3
- package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
- package/crates/team-agent/src/cli/diagnose.rs +184 -0
- package/crates/team-agent/src/cli/emit.rs +104 -21
- package/crates/team-agent/src/cli/helpers.rs +1 -0
- package/crates/team-agent/src/cli/leader.rs +14 -2
- package/crates/team-agent/src/cli/leaders.rs +2 -0
- package/crates/team-agent/src/cli/mod.rs +296 -8
- package/crates/team-agent/src/cli/named_address.rs +26 -2
- package/crates/team-agent/src/cli/profile.rs +2 -1
- package/crates/team-agent/src/cli/send/coordinator.rs +1 -0
- package/crates/team-agent/src/cli/send/mailbox.rs +3 -0
- package/crates/team-agent/src/cli/send/persist.rs +1 -0
- package/crates/team-agent/src/cli/send/presentation.rs +3 -0
- package/crates/team-agent/src/cli/send/resolve.rs +206 -1
- package/crates/team-agent/src/cli/send.rs +2 -1
- package/crates/team-agent/src/cli/spec.rs +5 -1
- package/crates/team-agent/src/cli/status.rs +2 -1
- package/crates/team-agent/src/cli/status_port/compact.rs +1 -0
- package/crates/team-agent/src/cli/status_port/format.rs +1 -0
- package/crates/team-agent/src/cli/status_port/inbox.rs +1 -0
- package/crates/team-agent/src/cli/status_port/store.rs +7 -3
- package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
- package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
- package/crates/team-agent/src/cli/types.rs +36 -2
- package/crates/team-agent/src/communication_mode/mod.rs +54 -0
- package/crates/team-agent/src/compiler/tests.rs +5 -5
- package/crates/team-agent/src/compiler.rs +54 -1
- package/crates/team-agent/src/conpty/backend.rs +1 -0
- package/crates/team-agent/src/conpty/mod.rs +1 -0
- package/crates/team-agent/src/coordinator/backoff.rs +5 -8
- package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
- package/crates/team-agent/src/coordinator/health.rs +1 -0
- package/crates/team-agent/src/coordinator/mod.rs +2 -2
- package/crates/team-agent/src/coordinator/orphan.rs +4 -116
- package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
- package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/abnormal.rs +6 -5
- package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
- package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
- package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
- package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
- package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +0 -9
- package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
- package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
- package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -42
- package/crates/team-agent/src/coordinator/tick.rs +24 -28
- package/crates/team-agent/src/coordinator/types.rs +3 -176
- package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
- package/crates/team-agent/src/db/message_store.rs +47 -2
- package/crates/team-agent/src/db/migration.rs +5 -4
- package/crates/team-agent/src/db/mod.rs +1 -0
- package/crates/team-agent/src/db/schema.rs +1 -0
- package/crates/team-agent/src/diagnose/comms.rs +2 -1
- package/crates/team-agent/src/diagnose/mod.rs +1 -0
- package/crates/team-agent/src/diagnose/orphans.rs +1 -0
- package/crates/team-agent/src/fake_worker.rs +21 -1
- package/crates/team-agent/src/layout/manager.rs +3 -7
- package/crates/team-agent/src/layout/mod.rs +2 -2
- package/crates/team-agent/src/layout/overlay.rs +6 -1
- package/crates/team-agent/src/layout/placement.rs +1 -0
- package/crates/team-agent/src/layout/recovery.rs +3 -0
- package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
- package/crates/team-agent/src/layout/sessions.rs +8 -1
- package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
- package/crates/team-agent/src/layout/worker_env.rs +3 -0
- package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
- package/crates/team-agent/src/leader/helpers.rs +1 -0
- package/crates/team-agent/src/leader/incident.rs +1 -0
- package/crates/team-agent/src/leader/lease.rs +27 -22
- package/crates/team-agent/src/leader/mod.rs +2 -3
- package/crates/team-agent/src/leader/owner_bind.rs +13 -3
- package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
- package/crates/team-agent/src/leader/rediscover.rs +4 -17
- package/crates/team-agent/src/leader/registry.rs +3 -15
- package/crates/team-agent/src/leader/start.rs +1072 -186
- package/crates/team-agent/src/leader/takeover.rs +10 -232
- package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
- package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
- package/crates/team-agent/src/leader/tests/identity.rs +71 -59
- package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
- package/crates/team-agent/src/leader/tests/idle.rs +0 -193
- package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
- package/crates/team-agent/src/leader/tests/mod.rs +4 -41
- package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
- package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
- package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
- package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
- package/crates/team-agent/src/leader/types.rs +1 -28
- package/crates/team-agent/src/lib.rs +7 -0
- package/crates/team-agent/src/lifecycle/display.rs +7 -1
- package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
- package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +1 -1
- package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
- package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
- package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
- package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -28
- package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
- package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
- package/crates/team-agent/src/lifecycle/launch.rs +11 -12
- package/crates/team-agent/src/lifecycle/mod.rs +2 -1
- package/crates/team-agent/src/lifecycle/restart/agent.rs +4 -4
- package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
- package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
- package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
- package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
- package/crates/team-agent/src/lifecycle/restart.rs +13 -11
- package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
- package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
- package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
- package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
- package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
- package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
- package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
- package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
- package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
- package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
- package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
- package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
- package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
- package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
- package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
- package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
- package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
- package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
- package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
- package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
- package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
- package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
- package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
- package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
- package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
- package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
- package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
- package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
- package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
- package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
- package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
- package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
- package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
- package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
- package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
- package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
- package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
- package/crates/team-agent/src/lifecycle/tests.rs +59 -0
- package/crates/team-agent/src/lifecycle/types.rs +0 -9
- package/crates/team-agent/src/lifecycle/worker_command_context.rs +29 -11
- package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
- package/crates/team-agent/src/mcp_server/mod.rs +1 -0
- package/crates/team-agent/src/mcp_server/normalize.rs +5 -3
- package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
- package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
- package/crates/team-agent/src/mcp_server/tools.rs +101 -66
- package/crates/team-agent/src/mcp_server/types.rs +6 -18
- package/crates/team-agent/src/mcp_server/wire.rs +32 -26
- package/crates/team-agent/src/messaging/activity.rs +1 -0
- package/crates/team-agent/src/messaging/address.rs +1 -0
- package/crates/team-agent/src/messaging/delivery.rs +109 -41
- package/crates/team-agent/src/messaging/helpers.rs +2 -1
- package/crates/team-agent/src/messaging/leader_channel.rs +34 -10
- package/crates/team-agent/src/messaging/leader_receiver.rs +42 -24
- package/crates/team-agent/src/messaging/mod.rs +10 -14
- package/crates/team-agent/src/messaging/peers.rs +2 -0
- package/crates/team-agent/src/messaging/persist.rs +3 -1
- package/crates/team-agent/src/messaging/presentation.rs +111 -1
- package/crates/team-agent/src/messaging/results.rs +166 -17
- package/crates/team-agent/src/messaging/scheduler.rs +1 -0
- package/crates/team-agent/src/messaging/selftest.rs +1 -1
- package/crates/team-agent/src/messaging/send.rs +135 -89
- package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
- package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
- package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
- package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
- package/crates/team-agent/src/messaging/types.rs +7 -2
- package/crates/team-agent/src/messaging/watchers.rs +24 -13
- package/crates/team-agent/src/model/enums.rs +1 -0
- package/crates/team-agent/src/model/errors.rs +1 -0
- package/crates/team-agent/src/model/ids.rs +1 -0
- package/crates/team-agent/src/model/mod.rs +2 -0
- package/crates/team-agent/src/model/name_similarity.rs +1 -0
- package/crates/team-agent/src/model/pane_authority_refusal.rs +359 -0
- package/crates/team-agent/src/model/paths.rs +1 -0
- package/crates/team-agent/src/model/permissions.rs +3 -2
- package/crates/team-agent/src/model/routing.rs +1 -0
- package/crates/team-agent/src/model/spec.rs +17 -0
- package/crates/team-agent/src/model/task_graph.rs +1 -0
- package/crates/team-agent/src/model/yaml/tests.rs +1 -0
- package/crates/team-agent/src/model/yaml.rs +1 -0
- package/crates/team-agent/src/packaging/install.rs +9 -147
- package/crates/team-agent/src/packaging/migrate.rs +2 -0
- package/crates/team-agent/src/packaging/mod.rs +1 -0
- package/crates/team-agent/src/packaging/repair.rs +2 -0
- package/crates/team-agent/src/packaging/tests.rs +29 -218
- package/crates/team-agent/src/packaging/types.rs +8 -15
- package/crates/team-agent/src/platform/argv.rs +4 -0
- package/crates/team-agent/src/platform/errors.rs +10 -1
- package/crates/team-agent/src/platform/file_lock.rs +4 -157
- package/crates/team-agent/src/platform/mod.rs +0 -61
- package/crates/team-agent/src/platform/process.rs +1 -0
- package/crates/team-agent/src/provider/adapter.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
- package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
- package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
- package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
- package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
- package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
- package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
- package/crates/team-agent/src/provider/classify.rs +1 -0
- package/crates/team-agent/src/provider/faults.rs +1 -26
- package/crates/team-agent/src/provider/helpers.rs +1 -0
- package/crates/team-agent/src/provider/mod.rs +1 -1
- package/crates/team-agent/src/provider/session/capture.rs +41 -22
- package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
- package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
- package/crates/team-agent/src/provider/session/mod.rs +2 -4
- package/crates/team-agent/src/provider/session/resume.rs +2 -209
- package/crates/team-agent/src/provider/session_scan/claude.rs +93 -3
- package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
- package/crates/team-agent/src/provider/session_scan/common.rs +24 -7
- package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
- package/crates/team-agent/src/provider/session_scan.rs +6 -42
- package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
- package/crates/team-agent/src/provider/types.rs +1 -0
- package/crates/team-agent/src/provider/wire.rs +1 -0
- package/crates/team-agent/src/state/identity.rs +16 -5
- package/crates/team-agent/src/state/identity_keys.rs +1 -0
- package/crates/team-agent/src/state/mod.rs +2 -0
- package/crates/team-agent/src/state/owner_gate.rs +5 -0
- package/crates/team-agent/src/state/ownership.rs +10 -4
- package/crates/team-agent/src/state/paths.rs +12 -0
- package/crates/team-agent/src/state/persist.rs +9 -3
- package/crates/team-agent/src/state/projection.rs +15 -2
- package/crates/team-agent/src/state/repository/intent.rs +1 -0
- package/crates/team-agent/src/state/repository/tests.rs +1 -0
- package/crates/team-agent/src/state/repository.rs +7 -0
- package/crates/team-agent/src/state/selector.rs +1 -0
- package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
- package/crates/team-agent/src/tmux_backend.rs +63 -0
- package/crates/team-agent/src/topology.rs +3 -0
- package/crates/team-agent/src/transport.rs +15 -0
- package/package.json +4 -4
- package/skills/team-agent/command-coverage.json +379 -0
- package/crates/team-agent/src/leader/inject.rs +0 -33
- package/crates/team-agent/src/provider/command.rs +0 -80
|
@@ -94,8 +94,13 @@ fn leader_start_plan_pins_mode_and_leader_env() {
|
|
|
94
94
|
);
|
|
95
95
|
assert!(!plan.is_external_leader);
|
|
96
96
|
assert!(
|
|
97
|
-
plan.argv.
|
|
98
|
-
|
|
97
|
+
plan.argv.len() >= 5
|
|
98
|
+
&& plan.argv[0] == "tmux"
|
|
99
|
+
&& plan.argv[1] == "-L"
|
|
100
|
+
&& plan.argv[2].starts_with("ta-")
|
|
101
|
+
&& plan.argv.iter().any(|arg| arg == "attach-session")
|
|
102
|
+
&& plan.argv.iter().any(|arg| arg.ends_with(":fake")),
|
|
103
|
+
"no-tmux managed launch uses the workspace socket and attaches the user client to the leader window: {:?}",
|
|
99
104
|
plan.argv
|
|
100
105
|
);
|
|
101
106
|
// plan 边界 detached 恒 false(`-d` 插入在 start_leader 层,非此处)。
|
|
@@ -130,7 +135,7 @@ fn leader_start_plan_external_leader_keeps_exec_provider_in_tmux() {
|
|
|
130
135
|
std::fs::create_dir_all(&ws).unwrap();
|
|
131
136
|
|
|
132
137
|
let provider_args = vec!["--".to_string(), "--model".to_string(), "opus".to_string()];
|
|
133
|
-
let plan =
|
|
138
|
+
let plan = crate::leader::start::leader_start_plan_after_ambient_authority_check(
|
|
134
139
|
Provider::Fake,
|
|
135
140
|
&provider_args,
|
|
136
141
|
&ws,
|
|
@@ -202,7 +207,7 @@ fn managed_leader_uses_dedicated_leader_session_independent_of_state_session_nam
|
|
|
202
207
|
|
|
203
208
|
#[test]
|
|
204
209
|
#[serial_test::serial(env)]
|
|
205
|
-
fn
|
|
210
|
+
fn in_tmux_default_leader_uses_managed_client_and_external_opt_out_keeps_exec_provider() {
|
|
206
211
|
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
207
212
|
let ws = std::env::temp_dir().join(format!("ta_rs_lsp_switch_{}", std::process::id()));
|
|
208
213
|
std::fs::create_dir_all(&ws).unwrap();
|
|
@@ -210,26 +215,65 @@ fn in_tmux_default_leader_runs_provider_in_current_pane() {
|
|
|
210
215
|
let endpoint = format!("/private/tmp/tmux-501/{socket},88432,187");
|
|
211
216
|
let _e = EnvGuard::apply(&[("TMUX", Some(&endpoint)), ("TMUX_PANE", Some("%7"))]);
|
|
212
217
|
|
|
213
|
-
let
|
|
218
|
+
let managed = crate::leader::start::leader_start_plan_after_ambient_authority_check(
|
|
219
|
+
Provider::Fake,
|
|
220
|
+
&[],
|
|
221
|
+
&ws,
|
|
222
|
+
false,
|
|
223
|
+
false,
|
|
224
|
+
None,
|
|
225
|
+
false,
|
|
226
|
+
)
|
|
227
|
+
.unwrap();
|
|
214
228
|
|
|
215
|
-
assert_eq!(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
229
|
+
assert_eq!(
|
|
230
|
+
managed.mode,
|
|
231
|
+
LeaderStartMode::ManagedTmuxClient,
|
|
232
|
+
"N41 managed default: an in-tmux caller on the workspace server must use the managed client path"
|
|
233
|
+
);
|
|
234
|
+
assert!(!managed.is_external_leader);
|
|
235
|
+
assert!(managed.session_name.is_some());
|
|
236
|
+
assert_eq!(
|
|
237
|
+
managed.leader_window.as_ref().map(WindowName::as_str),
|
|
238
|
+
Some("fake")
|
|
239
|
+
);
|
|
220
240
|
assert!(
|
|
221
|
-
|
|
241
|
+
managed.argv.iter().any(|arg| arg == "switch-client")
|
|
242
|
+
&& !managed.argv.iter().any(|arg| arg == "attach-session"),
|
|
243
|
+
"N41 managed default: same-server dispatch must switch the existing client, not nested-attach: {:?}",
|
|
244
|
+
managed.argv
|
|
245
|
+
);
|
|
246
|
+
assert_eq!(managed.provider_argv, vec!["fake".to_string()]);
|
|
247
|
+
|
|
248
|
+
let external = crate::leader::start::leader_start_plan_after_ambient_authority_check(
|
|
249
|
+
Provider::Fake,
|
|
250
|
+
&[],
|
|
251
|
+
&ws,
|
|
252
|
+
false,
|
|
253
|
+
false,
|
|
254
|
+
None,
|
|
255
|
+
true,
|
|
256
|
+
)
|
|
257
|
+
.unwrap();
|
|
258
|
+
|
|
259
|
+
assert_eq!(external.mode, LeaderStartMode::ExecProvider);
|
|
260
|
+
assert!(external.is_external_leader);
|
|
261
|
+
assert_eq!(external.leader_window, None);
|
|
262
|
+
assert_eq!(external.argv, vec!["fake".to_string()]);
|
|
263
|
+
assert_eq!(external.provider_argv, vec!["fake".to_string()]);
|
|
264
|
+
assert!(
|
|
265
|
+
!external
|
|
222
266
|
.argv
|
|
223
267
|
.iter()
|
|
224
268
|
.any(|arg| arg == "switch-client" || arg == "attach-session"),
|
|
225
|
-
"
|
|
226
|
-
|
|
269
|
+
"N41 external opt-out: explicit external topology must remain a direct provider path: {:?}",
|
|
270
|
+
external.argv
|
|
227
271
|
);
|
|
228
272
|
}
|
|
229
273
|
|
|
230
274
|
#[test]
|
|
231
275
|
#[serial_test::serial(env)]
|
|
232
|
-
fn
|
|
276
|
+
fn in_tmux_external_opt_out_keeps_exec_provider_across_server_boundary() {
|
|
233
277
|
let _g = ENV_LOCK.lock().unwrap_or_else(|p| p.into_inner());
|
|
234
278
|
let _e = EnvGuard::apply(&[
|
|
235
279
|
("TMUX", Some("/private/tmp/tmux-501/default,88432,187")),
|
|
@@ -238,12 +282,21 @@ fn in_tmux_default_leader_does_not_refuse_different_tmux_server() {
|
|
|
238
282
|
let ws = std::env::temp_dir().join(format!("ta_rs_lsp_refuse_{}", std::process::id()));
|
|
239
283
|
std::fs::create_dir_all(&ws).unwrap();
|
|
240
284
|
|
|
241
|
-
let plan =
|
|
285
|
+
let plan = crate::leader::start::leader_start_plan_after_ambient_authority_check(
|
|
286
|
+
Provider::Fake,
|
|
287
|
+
&[],
|
|
288
|
+
&ws,
|
|
289
|
+
false,
|
|
290
|
+
false,
|
|
291
|
+
None,
|
|
292
|
+
true,
|
|
293
|
+
)
|
|
294
|
+
.unwrap();
|
|
242
295
|
|
|
243
296
|
assert_eq!(plan.mode, LeaderStartMode::ExecProvider);
|
|
244
|
-
assert!(
|
|
245
|
-
assert!(plan.session_name.is_none());
|
|
297
|
+
assert!(plan.is_external_leader);
|
|
246
298
|
assert_eq!(plan.argv, vec!["fake".to_string()]);
|
|
299
|
+
assert_eq!(plan.provider_argv, vec!["fake".to_string()]);
|
|
247
300
|
}
|
|
248
301
|
|
|
249
302
|
#[test]
|
|
@@ -420,45 +473,4 @@ fn p2_leader_detect_divergence_catches_owner_uuid_split() {
|
|
|
420
473
|
// refactor must also update these tests.
|
|
421
474
|
// ═══════════════════════════════════════════════════════════════════════
|
|
422
475
|
|
|
423
|
-
|
|
424
|
-
fn unit0_leader_session_prefix_constant_is_stable() {
|
|
425
|
-
// Pinned: the literal leader session prefix that gates "is this a
|
|
426
|
-
// leader session" decisions everywhere in the runtime.
|
|
427
|
-
assert_eq!(
|
|
428
|
-
crate::leader::start::LEADER_SESSION_PREFIX,
|
|
429
|
-
"team-agent-leader-"
|
|
430
|
-
);
|
|
431
|
-
// The layout module re-exports the same value (kept in sync via
|
|
432
|
-
// const re-export).
|
|
433
|
-
assert_eq!(
|
|
434
|
-
crate::layout::sessions::LEADER_SESSION_PREFIX,
|
|
435
|
-
crate::leader::start::LEADER_SESSION_PREFIX
|
|
436
|
-
);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
#[test]
|
|
440
|
-
fn unit0_leader_prefixed_name_must_never_be_taken_as_worker_session() {
|
|
441
|
-
// Pinned invariant fed into unit-1 typed identity: any session
|
|
442
|
-
// name starting with `LEADER_SESSION_PREFIX` is a leader launcher
|
|
443
|
-
// session, never a worker session. unit-1 will wrap this rule in
|
|
444
|
-
// typed constructors (`WorkerSession::new` rejects the prefix,
|
|
445
|
-
// `LeaderLauncherSession::new` requires it).
|
|
446
|
-
let leader = "team-agent-leader-claude-x-aaaaaaaaaaaa";
|
|
447
|
-
let worker = "team-real-worker";
|
|
448
|
-
let prefix = crate::leader::start::LEADER_SESSION_PREFIX;
|
|
449
|
-
assert!(leader.starts_with(prefix));
|
|
450
|
-
assert!(!worker.starts_with(prefix));
|
|
451
|
-
// Symmetry: the runtime today uses this exact check
|
|
452
|
-
// (cli/mod.rs:261 `starts_with(LEADER_SESSION_PREFIX)`) to spare
|
|
453
|
-
// leader sessions during shutdown.
|
|
454
|
-
let leader_name = crate::transport::SessionName::new(leader);
|
|
455
|
-
let worker_name = crate::transport::SessionName::new(worker);
|
|
456
|
-
assert!(
|
|
457
|
-
crate::layout::sessions::is_leader_session(&leader_name),
|
|
458
|
-
"is_leader_session must accept a leader-prefixed session",
|
|
459
|
-
);
|
|
460
|
-
assert!(
|
|
461
|
-
!crate::layout::sessions::is_leader_session(&worker_name),
|
|
462
|
-
"is_leader_session must reject a non-prefixed session",
|
|
463
|
-
);
|
|
464
|
-
}
|
|
476
|
+
include!("identity_session_names.rs");
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
#[test]
|
|
2
|
+
fn unit0_leader_session_prefix_constant_is_stable() {
|
|
3
|
+
// Pinned: the literal leader session prefix that gates "is this a
|
|
4
|
+
// leader session" decisions everywhere in the runtime.
|
|
5
|
+
assert_eq!(
|
|
6
|
+
crate::leader::start::LEADER_SESSION_PREFIX,
|
|
7
|
+
"team-agent-leader-"
|
|
8
|
+
);
|
|
9
|
+
// The layout module re-exports the same value (kept in sync via
|
|
10
|
+
// const re-export).
|
|
11
|
+
assert_eq!(
|
|
12
|
+
crate::layout::sessions::LEADER_SESSION_PREFIX,
|
|
13
|
+
crate::leader::start::LEADER_SESSION_PREFIX
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
#[test]
|
|
18
|
+
fn unit0_leader_prefixed_name_must_never_be_taken_as_worker_session() {
|
|
19
|
+
// Pinned invariant fed into unit-1 typed identity: any session
|
|
20
|
+
// name starting with `LEADER_SESSION_PREFIX` is a leader launcher
|
|
21
|
+
// session, never a worker session. unit-1 will wrap this rule in
|
|
22
|
+
// typed constructors (`WorkerSession::new` rejects the prefix,
|
|
23
|
+
// `LeaderLauncherSession::new` requires it).
|
|
24
|
+
let leader = "team-agent-leader-claude-x-aaaaaaaaaaaa";
|
|
25
|
+
let worker = "team-real-worker";
|
|
26
|
+
let prefix = crate::leader::start::LEADER_SESSION_PREFIX;
|
|
27
|
+
assert!(leader.starts_with(prefix));
|
|
28
|
+
assert!(!worker.starts_with(prefix));
|
|
29
|
+
// Symmetry: the runtime today uses this exact check
|
|
30
|
+
// (cli/mod.rs:261 `starts_with(LEADER_SESSION_PREFIX)`) to spare
|
|
31
|
+
// leader sessions during shutdown.
|
|
32
|
+
let leader_name = crate::transport::SessionName::new(leader);
|
|
33
|
+
let worker_name = crate::transport::SessionName::new(worker);
|
|
34
|
+
assert!(
|
|
35
|
+
crate::layout::sessions::is_leader_session(&leader_name),
|
|
36
|
+
"is_leader_session must accept a leader-prefixed session",
|
|
37
|
+
);
|
|
38
|
+
assert!(
|
|
39
|
+
!crate::layout::sessions::is_leader_session(&worker_name),
|
|
40
|
+
"is_leader_session must reject a non-prefixed session",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
@@ -1,198 +1,5 @@
|
|
|
1
1
|
use super::*;
|
|
2
2
|
|
|
3
|
-
// =====================================================================
|
|
4
|
-
// 8. idle-takeover 接线 — build_idle_nodes / leader_node(unimplemented → RED)
|
|
5
|
-
// 命门:rollout_path=None → Unknown → never idle;leader path/provider 缺 → 省略;
|
|
6
|
-
// MUST-NOT-13:经 TurnStateClassifier mock,断言零 provider client 直连。
|
|
7
|
-
// =====================================================================
|
|
8
|
-
|
|
9
|
-
// build_idle_nodes:一个 worker 有 rollout_path(可读)→ classifier.classify 被调一次;
|
|
10
|
-
// 经注入分类器(零 provider client)。stopped/paused 跳过。
|
|
11
|
-
#[test]
|
|
12
|
-
fn build_idle_nodes_uses_injected_classifier_no_provider_client() {
|
|
13
|
-
// 真实 session 文件,使 _read_session_tail 读到非空 → classifier 返回注入 state。
|
|
14
|
-
let dir = std::env::temp_dir().join(format!("ta_rs_idle_{}", std::process::id()));
|
|
15
|
-
std::fs::create_dir_all(&dir).unwrap();
|
|
16
|
-
let log = dir.join("w1.jsonl");
|
|
17
|
-
std::fs::write(&log, b"{\"type\":\"turn_complete\"}\n").unwrap();
|
|
18
|
-
let state = serde_json::json!({
|
|
19
|
-
"agents": {
|
|
20
|
-
"w1": {"provider": "codex", "rollout_path": log.to_string_lossy(), "status": "running"},
|
|
21
|
-
"w_stopped": {"provider": "codex", "rollout_path": log.to_string_lossy(), "status": "stopped"},
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
let clf = CountingClassifier::new(TurnState::Idle);
|
|
25
|
-
let nodes = build_idle_nodes(&state, &clf).unwrap();
|
|
26
|
-
// stopped 被跳过(__init__ wiring:29)→ 仅 w1。
|
|
27
|
-
assert_eq!(nodes.len(), 1);
|
|
28
|
-
assert_eq!(nodes[0].node_id, "w1");
|
|
29
|
-
assert_eq!(nodes[0].role, NodeRole::Worker);
|
|
30
|
-
assert_eq!(nodes[0].state, TurnState::Idle);
|
|
31
|
-
// MUST-NOT-13:分类只经注入 classifier(此 mock 计数==1),零 provider client 直连。
|
|
32
|
-
assert_eq!(clf.calls.get(), 1, "每个 live node 恰调一次注入 classify");
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
// bug-085:rollout_path=None → 读到空串 → Unknown(不当 idle)。
|
|
36
|
-
#[test]
|
|
37
|
-
fn build_idle_nodes_none_rollout_path_yields_unknown_not_idle() {
|
|
38
|
-
let state = serde_json::json!({
|
|
39
|
-
"agents": {
|
|
40
|
-
"w1": {"provider": "codex", "status": "running"} // 无 rollout_path。
|
|
41
|
-
}
|
|
42
|
-
});
|
|
43
|
-
// 即使 mock 默认想返 Idle,空 session-log → classifier 返 Unknown(见 mock 逻辑)。
|
|
44
|
-
let clf = CountingClassifier::new(TurnState::Idle);
|
|
45
|
-
let nodes = build_idle_nodes(&state, &clf).unwrap();
|
|
46
|
-
assert_eq!(nodes.len(), 1);
|
|
47
|
-
assert_eq!(
|
|
48
|
-
nodes[0].state,
|
|
49
|
-
TurnState::Unknown,
|
|
50
|
-
"None rollout_path → Unknown,绝不 idle"
|
|
51
|
-
);
|
|
52
|
-
assert!(
|
|
53
|
-
!nodes[0].state.is_idle_for_takeover(),
|
|
54
|
-
"unknown ≠ idle:不得对 takeover 放行"
|
|
55
|
-
);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// _leader_node:leader path 或 provider 缺 → None(省略而非猜 idle)。
|
|
59
|
-
#[test]
|
|
60
|
-
fn leader_node_omitted_when_path_or_provider_missing() {
|
|
61
|
-
let clf = CountingClassifier::new(TurnState::Idle);
|
|
62
|
-
// 既无 leader.rollout_path 也无 receiver.rollout_path → None。
|
|
63
|
-
let state_no_path = serde_json::json!({
|
|
64
|
-
"leader": {"id": "leader", "provider": "codex"},
|
|
65
|
-
"leader_receiver": {"provider": "codex"}
|
|
66
|
-
});
|
|
67
|
-
assert!(
|
|
68
|
-
leader_node(&state_no_path, &clf).unwrap().is_none(),
|
|
69
|
-
"缺 path → 省略 leader 节点"
|
|
70
|
-
);
|
|
71
|
-
// 有 path 但无 provider → None。
|
|
72
|
-
let dir = std::env::temp_dir().join(format!("ta_rs_lnode_{}", std::process::id()));
|
|
73
|
-
std::fs::create_dir_all(&dir).unwrap();
|
|
74
|
-
let log = dir.join("leader.jsonl");
|
|
75
|
-
std::fs::write(&log, b"{\"type\":\"turn_open\"}\n").unwrap();
|
|
76
|
-
let state_no_provider = serde_json::json!({
|
|
77
|
-
"leader": {"id": "leader", "rollout_path": log.to_string_lossy()}
|
|
78
|
-
});
|
|
79
|
-
assert!(
|
|
80
|
-
leader_node(&state_no_provider, &clf).unwrap().is_none(),
|
|
81
|
-
"缺 provider → 省略 leader 节点(不猜 idle)"
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
// _leader_node:path+provider 齐 → 经 classifier 产 role=leader 节点(C13)。
|
|
86
|
-
#[test]
|
|
87
|
-
fn leader_node_classified_via_injected_classifier() {
|
|
88
|
-
let dir = std::env::temp_dir().join(format!("ta_rs_lnode2_{}", std::process::id()));
|
|
89
|
-
std::fs::create_dir_all(&dir).unwrap();
|
|
90
|
-
let log = dir.join("leader.jsonl");
|
|
91
|
-
std::fs::write(&log, b"{\"type\":\"turn_complete\"}\n").unwrap();
|
|
92
|
-
let state = serde_json::json!({
|
|
93
|
-
"leader": {"id": "leader", "provider": "codex", "rollout_path": log.to_string_lossy()}
|
|
94
|
-
});
|
|
95
|
-
let clf = CountingClassifier::new(TurnState::Working);
|
|
96
|
-
let node = leader_node(&state, &clf)
|
|
97
|
-
.unwrap()
|
|
98
|
-
.expect("path+provider 齐 → 有 leader 节点");
|
|
99
|
-
assert_eq!(node.role, NodeRole::Leader);
|
|
100
|
-
assert_eq!(node.node_id, "leader");
|
|
101
|
-
assert_eq!(node.state, TurnState::Working);
|
|
102
|
-
assert_eq!(
|
|
103
|
-
clf.calls.get(),
|
|
104
|
-
1,
|
|
105
|
-
"leader 分类经注入 classifier 一次,零 provider client"
|
|
106
|
-
);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// =====================================================================
|
|
110
|
-
// 9. classify_provider_turn_state 门面(unimplemented → RED)
|
|
111
|
-
// unknown/abnormal 且有 event_sink → 写 idle_takeover.classify。
|
|
112
|
-
// =====================================================================
|
|
113
|
-
|
|
114
|
-
// 门面经注入 classifier 分类;空文本 → Unknown(本 mock),验证返回 TurnClassification。
|
|
115
|
-
#[test]
|
|
116
|
-
fn classify_provider_turn_state_returns_classification_via_injected_classifier() {
|
|
117
|
-
let clf = CountingClassifier::new(TurnState::Idle);
|
|
118
|
-
let c =
|
|
119
|
-
classify_provider_turn_state(Provider::Codex, "{\"type\":\"turn_complete\"}", &clf, None)
|
|
120
|
-
.unwrap();
|
|
121
|
-
assert_eq!(c.state, TurnState::Idle);
|
|
122
|
-
assert_eq!(clf.calls.get(), 1);
|
|
123
|
-
// 空文本 → Unknown(unknown ≠ idle 命门下游)。
|
|
124
|
-
let clf2 = CountingClassifier::new(TurnState::Idle);
|
|
125
|
-
let c2 = classify_provider_turn_state(Provider::Codex, "", &clf2, None).unwrap();
|
|
126
|
-
assert_eq!(c2.state, TurnState::Unknown);
|
|
127
|
-
assert!(!c2.state.is_idle_for_takeover());
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// event_sink + unknown/abnormal → 写 idle_takeover.classify(事件名字节锁)。
|
|
131
|
-
#[test]
|
|
132
|
-
fn classify_event_name_is_idle_takeover_classify() {
|
|
133
|
-
assert_eq!(
|
|
134
|
-
LeaderEvent::IdleTakeoverClassify.name(),
|
|
135
|
-
"idle_takeover.classify"
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// =====================================================================
|
|
140
|
-
// 10. push_idle_reminder(unimplemented → RED):!should_ping → no-op。
|
|
141
|
-
// =====================================================================
|
|
142
|
-
|
|
143
|
-
#[test]
|
|
144
|
-
fn push_idle_reminder_noop_when_should_not_ping() {
|
|
145
|
-
let ws = std::env::temp_dir().join(format!("ta_rs_push_{}", std::process::id()));
|
|
146
|
-
std::fs::create_dir_all(&ws).unwrap();
|
|
147
|
-
let event_log = crate::event_log::EventLog::new(&ws);
|
|
148
|
-
let state = serde_json::json!({"leader": {"id": "leader"}});
|
|
149
|
-
let result = TakeoverReminderResult {
|
|
150
|
-
should_ping: false,
|
|
151
|
-
message: None,
|
|
152
|
-
interrupted_nodes: vec![],
|
|
153
|
-
reason: Some("not_armed_no_worker_turn".into()),
|
|
154
|
-
};
|
|
155
|
-
// should_ping=false → no-op,返回 Ok(())。现 unimplemented → RED。
|
|
156
|
-
push_idle_reminder(&ws, &state, &event_log, &result).unwrap();
|
|
157
|
-
// 强化:no-op 必须真的什么都不做 —— 不写 idle_takeover.reminder 事件(EventLog 无该事件)。
|
|
158
|
-
let events = event_log.tail(50).unwrap();
|
|
159
|
-
assert!(
|
|
160
|
-
!events
|
|
161
|
-
.iter()
|
|
162
|
-
.any(|e| e["event"] == serde_json::json!("idle_takeover.reminder")),
|
|
163
|
-
"should_ping=false 时绝不写 reminder 事件"
|
|
164
|
-
);
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// #236 nag_removal (N35) — push_idle_reminder is now a no-op shim.
|
|
168
|
-
// [OLD] assertion: should_ping=true → writes idle_takeover.reminder event (with
|
|
169
|
-
// interrupted/reason byte-locked golden payload).
|
|
170
|
-
// [NEW] assertion: even when should_ping=true, push_idle_reminder writes NO event
|
|
171
|
-
// and emits NO leader-bound message; ownership/handover happens only via explicit
|
|
172
|
-
// `claim-leader` / `takeover` commands. The function signature is preserved so
|
|
173
|
-
// existing callers (coordinator/tick.rs, lifecycle wiring) still resolve.
|
|
174
|
-
#[test]
|
|
175
|
-
fn push_idle_reminder_is_silent_no_op_under_n35_even_when_should_ping_true() {
|
|
176
|
-
let ws = std::env::temp_dir().join(format!("ta_rs_push2_{}", std::process::id()));
|
|
177
|
-
std::fs::create_dir_all(&ws).unwrap();
|
|
178
|
-
let event_log = crate::event_log::EventLog::new(&ws);
|
|
179
|
-
let state = serde_json::json!({"leader": {"id": "leader"}});
|
|
180
|
-
let result = TakeoverReminderResult {
|
|
181
|
-
should_ping: true,
|
|
182
|
-
message: Some("neutral reminder body".into()),
|
|
183
|
-
interrupted_nodes: vec!["w1".into()],
|
|
184
|
-
reason: Some("armed_all_idle".into()),
|
|
185
|
-
};
|
|
186
|
-
push_idle_reminder(&ws, &state, &event_log, &result).unwrap();
|
|
187
|
-
let events = event_log.tail(50).unwrap();
|
|
188
|
-
assert!(
|
|
189
|
-
!events
|
|
190
|
-
.iter()
|
|
191
|
-
.any(|e| e["event"] == serde_json::json!("idle_takeover.reminder")),
|
|
192
|
-
"#236 N35: push_idle_reminder must no longer emit the reminder nag event; got {events:?}"
|
|
193
|
-
);
|
|
194
|
-
}
|
|
195
|
-
|
|
196
3
|
// idle_takeover.reminder / push_failed 事件名字节锁。
|
|
197
4
|
#[test]
|
|
198
5
|
fn idle_takeover_event_names_byte_locked() {
|
|
@@ -67,51 +67,6 @@ fn attach_leader_binds_pane_advances_epoch_and_persists() {
|
|
|
67
67
|
assert_eq!(persisted["team_owner"]["owner_epoch"], serde_json::json!(1));
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// autobind:$TMUX_PANE 缺 → Ok(None)(__init__.py:885-887,锁前直接返回,不开锁)。
|
|
71
|
-
// 强化:断言这是 lock-not-acquired 早退 —— 不写 state.json(无 receiver/owner 落盘),
|
|
72
|
-
// 不发任何 leader_receiver.* 事件(锁前 return,连 EventLog 都不构造)。
|
|
73
|
-
#[test]
|
|
74
|
-
fn autobind_returns_none_when_tmux_pane_missing() {
|
|
75
|
-
if std::env::var_os("TMUX_PANE").is_some() {
|
|
76
|
-
return; // 在 tmux 内:走绑定路径,本用例只验缺失分支。
|
|
77
|
-
}
|
|
78
|
-
let ws = std::env::temp_dir().join(format!("ta_rs_auto_{}", std::process::id()));
|
|
79
|
-
std::fs::create_dir_all(&ws).unwrap();
|
|
80
|
-
let r = autobind_leader_receiver_from_env(&ws, Provider::Codex, LeaseSource::Restart).unwrap();
|
|
81
|
-
assert!(r.is_none(), "$TMUX_PANE 缺 → autobind 返回 Ok(None)");
|
|
82
|
-
// lock-not-acquired 早退:state.json 未被写(load 兜底成空 state,无 team_owner/receiver)。
|
|
83
|
-
let st = crate::state::persist::load_runtime_state(&ws).unwrap();
|
|
84
|
-
assert!(
|
|
85
|
-
st.get("leader_receiver").is_none(),
|
|
86
|
-
"skip 路径不应落 receiver"
|
|
87
|
-
);
|
|
88
|
-
assert!(st.get("team_owner").is_none(), "skip 路径不应落 owner");
|
|
89
|
-
// 早退发生在 EventLog 构造前 → 无任何事件文件。
|
|
90
|
-
let events = crate::event_log::EventLog::new(&ws).tail(50).unwrap();
|
|
91
|
-
assert!(events.is_empty(), "skip 路径绝不写审计事件");
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// autobind 成功支:$TMUX_PANE 命中 → 锁内 attach_leader_to_state → Ok(Some(receiver))。
|
|
95
|
-
// receiver.pane_id == 注入 pane,discovery==EnvPane($TMUX_PANE 直接命中)。
|
|
96
|
-
// env 变更进程全局且与并行 test race,且依赖跨 lane 的 live pane resolver,故 real-machine-gated。
|
|
97
|
-
#[test]
|
|
98
|
-
#[ignore = "needs live $TMUX_PANE + cross-lane _resolve_leader_pane; env mutation races parallel tests"]
|
|
99
|
-
fn autobind_binds_env_pane_on_success() {
|
|
100
|
-
let ws = std::env::temp_dir().join(format!("ta_rs_auto_ok_{}", std::process::id()));
|
|
101
|
-
std::fs::create_dir_all(&ws).unwrap();
|
|
102
|
-
// SAFETY: #[ignore]d,单独 `--ignored --test-threads=1` 跑,不与并行 test race。
|
|
103
|
-
unsafe { std::env::set_var("TMUX_PANE", "%42") };
|
|
104
|
-
let r = autobind_leader_receiver_from_env(&ws, Provider::Codex, LeaseSource::Restart).unwrap();
|
|
105
|
-
unsafe { std::env::remove_var("TMUX_PANE") };
|
|
106
|
-
let receiver = r.expect("$TMUX_PANE 命中 → Ok(Some(receiver))");
|
|
107
|
-
assert_eq!(receiver.pane_id, PaneId::new("%42"));
|
|
108
|
-
assert_eq!(
|
|
109
|
-
receiver.discovery,
|
|
110
|
-
Some(Discovery::EnvPane),
|
|
111
|
-
"$TMUX_PANE 命中 → discovery=env_pane"
|
|
112
|
-
);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
70
|
// claim_leader:无 ambiguous incident → 走 claim_lease_no_incident 直接 acquire/CAS。
|
|
116
71
|
// 现 unimplemented → RED;锁住返回 LeaseResult。
|
|
117
72
|
#[test]
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
2
2
|
use super::*;
|
|
3
|
-
use std::cell::Cell;
|
|
4
3
|
|
|
5
4
|
// ── helpers ────────────────────────────────────────────────────────────
|
|
6
5
|
/// derive 一个真 LeaderSessionUuid(骨架无公开裸构造器,仅 `derive`)。
|
|
@@ -8,46 +7,6 @@ fn uuid(fp: &str, ws: &str, user: &str, team: &str) -> LeaderSessionUuid {
|
|
|
8
7
|
LeaderSessionUuid::derive(fp, ws, user, team).unwrap()
|
|
9
8
|
}
|
|
10
9
|
|
|
11
|
-
/// 注入式 turn-state 分类器:MUST-NOT-13 命门 —— 统计 classify 调用次数,
|
|
12
|
-
/// 据此断言 idle 面经此 trait 分类、绝不直连任何 provider client。
|
|
13
|
-
struct CountingClassifier {
|
|
14
|
-
calls: Cell<usize>,
|
|
15
|
-
result: TurnState,
|
|
16
|
-
}
|
|
17
|
-
impl CountingClassifier {
|
|
18
|
-
fn new(result: TurnState) -> Self {
|
|
19
|
-
Self {
|
|
20
|
-
calls: Cell::new(0),
|
|
21
|
-
result,
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
impl TurnStateClassifier for CountingClassifier {
|
|
26
|
-
fn classify(
|
|
27
|
-
&self,
|
|
28
|
-
_provider: Provider,
|
|
29
|
-
session_log_text: &str,
|
|
30
|
-
) -> Result<TurnClassification, LeaderError> {
|
|
31
|
-
self.calls.set(self.calls.get() + 1);
|
|
32
|
-
// 空 session-log 文本 → unknown(bug-085:None rollout_path 漏穿后读到空串)。
|
|
33
|
-
let state = if session_log_text.is_empty() {
|
|
34
|
-
TurnState::Unknown
|
|
35
|
-
} else {
|
|
36
|
-
self.result
|
|
37
|
-
};
|
|
38
|
-
Ok(TurnClassification {
|
|
39
|
-
state,
|
|
40
|
-
turn_id: None,
|
|
41
|
-
annotations: vec![],
|
|
42
|
-
reason: if state == TurnState::Unknown {
|
|
43
|
-
Some("empty_session_log".into())
|
|
44
|
-
} else {
|
|
45
|
-
None
|
|
46
|
-
},
|
|
47
|
-
})
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
10
|
static ENV_LOCK: std::sync::Mutex<()> = std::sync::Mutex::new(());
|
|
52
11
|
|
|
53
12
|
/// RAII env override: restores prior values on drop (incl. panic unwind), so a
|
|
@@ -133,9 +92,13 @@ fn event_names(ws: &std::path::Path) -> Vec<String> {
|
|
|
133
92
|
|
|
134
93
|
mod basics;
|
|
135
94
|
mod byte_findings;
|
|
95
|
+
mod claim_leader_mailbox_flush_contract;
|
|
96
|
+
mod historical_inventory_upgrade_gate_red;
|
|
136
97
|
mod identity;
|
|
137
98
|
mod idle;
|
|
138
99
|
mod lease_api;
|
|
139
100
|
mod lease_claim;
|
|
140
101
|
mod rediscover;
|
|
102
|
+
mod team_in_team_state_scope_red;
|
|
103
|
+
mod terminal_and_replay_vs_rerender_invariants_red;
|
|
141
104
|
mod wake_start_owner;
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
use std::path::PathBuf;
|
|
2
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
3
|
+
|
|
4
|
+
const CALLER_IDENTITY_ENVS: &[&str] = &[
|
|
5
|
+
"TMUX",
|
|
6
|
+
"TMUX_PANE",
|
|
7
|
+
"TEAM_AGENT_LEADER_PANE_ID",
|
|
8
|
+
"TEAM_AGENT_LEADER_SESSION_UUID",
|
|
9
|
+
"TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
|
|
10
|
+
"TEAM_AGENT_LEADER_SESSION_NAME",
|
|
11
|
+
"TEAM_AGENT_LEADER_PROVIDER",
|
|
12
|
+
"TEAM_AGENT_MACHINE_FINGERPRINT",
|
|
13
|
+
"TEAM_AGENT_WORKSPACE",
|
|
14
|
+
"TEAM_AGENT_TEAM_ID",
|
|
15
|
+
"TEAM_AGENT_OWNER_TEAM_ID",
|
|
16
|
+
"TEAM_AGENT_ACTIVE_TEAM",
|
|
17
|
+
"TEAM_AGENT_ID",
|
|
18
|
+
"TEAM_AGENT_AGENT_ID",
|
|
19
|
+
"TEAM_AGENT_AUTH_MODE",
|
|
20
|
+
"TEAM_AGENT_LEADER_BYPASS",
|
|
21
|
+
"TEAM_AGENT_LEADER_BYPASS_SOURCE",
|
|
22
|
+
"TEAM_AGENT_LEADER_BYPASS_PROVIDER",
|
|
23
|
+
"TEAM_AGENT_LEADER_BYPASS_FLAG",
|
|
24
|
+
"TEAM_AGENT_MCP_AUTO_APPROVE",
|
|
25
|
+
"TEAM_AGENT_MCP_AUTO_APPROVE_SOURCE",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
static COUNTER: AtomicU64 = AtomicU64::new(0);
|
|
29
|
+
|
|
30
|
+
pub(super) struct RehomedTestEnv {
|
|
31
|
+
root: PathBuf,
|
|
32
|
+
previous: Vec<(&'static str, Option<String>)>,
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
impl RehomedTestEnv {
|
|
36
|
+
pub(super) fn enter(tag: &str) -> Self {
|
|
37
|
+
let root = std::env::temp_dir().join(format!(
|
|
38
|
+
"ta-leader-rehomed-{tag}-{}-{}",
|
|
39
|
+
std::process::id(),
|
|
40
|
+
COUNTER.fetch_add(1, Ordering::Relaxed)
|
|
41
|
+
));
|
|
42
|
+
let _ = std::fs::remove_dir_all(&root);
|
|
43
|
+
let home = root.join("home");
|
|
44
|
+
std::fs::create_dir_all(&home).expect("create rehomed test HOME");
|
|
45
|
+
|
|
46
|
+
let mut previous = Vec::new();
|
|
47
|
+
for key in std::iter::once("HOME").chain(CALLER_IDENTITY_ENVS.iter().copied()) {
|
|
48
|
+
previous.push((key, std::env::var(key).ok()));
|
|
49
|
+
}
|
|
50
|
+
unsafe {
|
|
51
|
+
std::env::set_var("HOME", &home);
|
|
52
|
+
for key in CALLER_IDENTITY_ENVS {
|
|
53
|
+
std::env::remove_var(key);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Self { root, previous }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
pub(super) fn workspace(&self, tag: &str) -> PathBuf {
|
|
61
|
+
let path = self.root.join(format!(
|
|
62
|
+
"workspace-{tag}-{}",
|
|
63
|
+
COUNTER.fetch_add(1, Ordering::Relaxed)
|
|
64
|
+
));
|
|
65
|
+
std::fs::create_dir_all(&path).expect("create rehomed test workspace");
|
|
66
|
+
path
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
impl Drop for RehomedTestEnv {
|
|
71
|
+
fn drop(&mut self) {
|
|
72
|
+
for (key, value) in self.previous.drain(..).rev() {
|
|
73
|
+
unsafe {
|
|
74
|
+
if let Some(value) = value {
|
|
75
|
+
std::env::set_var(key, value);
|
|
76
|
+
} else {
|
|
77
|
+
std::env::remove_var(key);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
let _ = std::fs::remove_dir_all(&self.root);
|
|
82
|
+
}
|
|
83
|
+
}
|