@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
|
@@ -0,0 +1,1007 @@
|
|
|
1
|
+
//! 0.3.6-B: copilot acceptance bug batch (B-2 / B-4 / B-5 / B-7) contracts.
|
|
2
|
+
//!
|
|
3
|
+
//! Basis: `.team/artifacts/copilot-acceptance-locate.md` (architect; the report's
|
|
4
|
+
//! mechanism theory was DISPROVEN — contracts follow the located root cause, not the
|
|
5
|
+
//! report's suggested fix) + the B-4 cr verdict (6 reverse cases).
|
|
6
|
+
//!
|
|
7
|
+
//! B-2: quick-start attach_commands — socket probe silent-empty must emit an
|
|
8
|
+
//! event/hint; the Ready report must carry non-empty attach_commands.
|
|
9
|
+
//! B-4: tick monitor-step failure must NOT block the deliver_pending trunk (the trunk
|
|
10
|
+
//! does not depend on classify); classify.unsupported flood must dedup; --force-paste
|
|
11
|
+
//! must never enter argv (REJECT grep guard).
|
|
12
|
+
//! B-5: status must consume the existing coordinator_health + pending count and surface
|
|
13
|
+
//! a runtime block + hint when coordinator is down AND there is accepted backlog
|
|
14
|
+
//! (N38, no auto-recovery).
|
|
15
|
+
//! B-7: TEAM_AGENT_LEADER_PANE_ID pointing at a dead/absent pane must fail-fast on the
|
|
16
|
+
//! active (quick-start) path with a clear error; unset must pass through.
|
|
17
|
+
|
|
18
|
+
#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
|
|
19
|
+
|
|
20
|
+
#[path = "../../../tests/support/hermetic.rs"]
|
|
21
|
+
mod hermetic_guard;
|
|
22
|
+
#[allow(dead_code)]
|
|
23
|
+
fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
|
|
24
|
+
|
|
25
|
+
use std::collections::BTreeMap;
|
|
26
|
+
use std::path::{Path, PathBuf};
|
|
27
|
+
use std::process::Command;
|
|
28
|
+
use std::sync::atomic::{AtomicU64, Ordering};
|
|
29
|
+
use std::sync::Mutex;
|
|
30
|
+
|
|
31
|
+
use serde_json::{json, Value};
|
|
32
|
+
use serial_test::serial;
|
|
33
|
+
use team_agent::coordinator::{Coordinator, ProviderRegistry, WorkspacePath};
|
|
34
|
+
use team_agent::lifecycle::QuickStartReport;
|
|
35
|
+
use team_agent::message_store::MessageStore;
|
|
36
|
+
use team_agent::model::enums::Provider;
|
|
37
|
+
use team_agent::provider::{get_adapter, ProviderAdapter};
|
|
38
|
+
use team_agent::state::persist::save_runtime_state;
|
|
39
|
+
use team_agent::transport::{
|
|
40
|
+
AttachOutcome, BackendKind, CaptureRange, CapturedText, InjectPayload, InjectReport,
|
|
41
|
+
InjectStage, InjectVerification, Key, PaneField, PaneId, PaneInfo, PaneLiveness, SessionName,
|
|
42
|
+
SetEnvOutcome, SpawnResult, SubmitVerification, Target, Transport, TransportError,
|
|
43
|
+
TurnVerification, WindowName,
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const ANCESTRY_KEY: &str = "TEAM_AGENT_TEST_PROCESS_ANCESTRY_ARGV_JSON";
|
|
47
|
+
const NEUTRAL_ANCESTRY: &str = "[\"/bin/zsh\"]";
|
|
48
|
+
|
|
49
|
+
// ───────────────────────────── B-4 · monitor step isolation ─────────────────────────
|
|
50
|
+
|
|
51
|
+
/// B-4 main reverse (cr `monitor_step_failure_does_not_block_deliver_pending`): a
|
|
52
|
+
/// monitor step (here: the runtime-approval pane capture, made to hard-fail) must NOT
|
|
53
|
+
/// abort the tick before deliver_pending. The tick must (1) not enter backoff, (2) log
|
|
54
|
+
/// a step-failure event, (3) still run deliver_pending, (4) push the accepted message
|
|
55
|
+
/// to delivered. Today the `?` on the monitor step aborts before delivery → RED.
|
|
56
|
+
#[test]
|
|
57
|
+
#[serial(env)]
|
|
58
|
+
fn b4_monitor_step_failure_does_not_block_deliver_pending() {
|
|
59
|
+
let _g = EnvGuard::set(&[(ANCESTRY_KEY, NEUTRAL_ANCESTRY)]);
|
|
60
|
+
let ws = tmp_ws("b4-monitor");
|
|
61
|
+
seed_copilot_team_state(&ws, "cp1", true);
|
|
62
|
+
let store = MessageStore::open(&ws).unwrap();
|
|
63
|
+
store
|
|
64
|
+
.create_message(
|
|
65
|
+
Some("task_mcp"),
|
|
66
|
+
"leader",
|
|
67
|
+
"cp1",
|
|
68
|
+
"do the thing",
|
|
69
|
+
None,
|
|
70
|
+
false,
|
|
71
|
+
None,
|
|
72
|
+
)
|
|
73
|
+
.unwrap();
|
|
74
|
+
|
|
75
|
+
let coord = Coordinator::new(
|
|
76
|
+
WorkspacePath::new(ws.clone()),
|
|
77
|
+
Box::new(RealRegistry),
|
|
78
|
+
Box::new(MonitorFailTransport::new()),
|
|
79
|
+
);
|
|
80
|
+
let report = coord.tick();
|
|
81
|
+
|
|
82
|
+
let mut failures = Vec::new();
|
|
83
|
+
match &report {
|
|
84
|
+
Err(e) => failures.push(format!(
|
|
85
|
+
"B-4: a monitor-step failure must not propagate as a tick Err (it must degrade \
|
|
86
|
+
+ continue, bug-084 philosophy); got Err({e})"
|
|
87
|
+
)),
|
|
88
|
+
Ok(report) => {
|
|
89
|
+
if report.delivered.is_empty() {
|
|
90
|
+
failures.push(
|
|
91
|
+
"B-4: deliver_pending must still run after a monitor-step failure — the \
|
|
92
|
+
delivery trunk does not depend on the monitor face (N36 three-way availability); 0 delivered"
|
|
93
|
+
.to_string(),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
let events = events_text(&ws);
|
|
99
|
+
if !(events.contains("_failed") && events.contains("tick")) {
|
|
100
|
+
failures.push(format!(
|
|
101
|
+
"B-4: the degraded monitor step must log a coordinator.tick.<step>_failed event \
|
|
102
|
+
(observable, not silent); events tail={}",
|
|
103
|
+
events.lines().rev().take(3).collect::<Vec<_>>().join(" | ")
|
|
104
|
+
));
|
|
105
|
+
}
|
|
106
|
+
let status: Option<String> = MessageStore::open(&ws)
|
|
107
|
+
.unwrap()
|
|
108
|
+
.inbox("cp1", 10, None)
|
|
109
|
+
.ok()
|
|
110
|
+
.and_then(|rows| {
|
|
111
|
+
rows.first()
|
|
112
|
+
.and_then(|r| r.get("status").and_then(Value::as_str).map(String::from))
|
|
113
|
+
});
|
|
114
|
+
if status.as_deref() == Some("accepted") {
|
|
115
|
+
failures.push(
|
|
116
|
+
"B-4: the accepted message must leave the 'accepted' state after the tick".to_string(),
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
assert!(
|
|
120
|
+
failures.is_empty(),
|
|
121
|
+
"B-4 monitor isolation contract failed:\n{}",
|
|
122
|
+
failures.join("\n")
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/// B-4 reverse (cr `force_paste_flag_not_in_argv`): the --force-paste flag (REJECTed —
|
|
127
|
+
/// a back-door on a gate that does not exist) must never be introduced into the
|
|
128
|
+
/// delivery / provider command code. Source grep guard across the crate.
|
|
129
|
+
#[test]
|
|
130
|
+
fn b4_force_paste_flag_rejected_not_in_source() {
|
|
131
|
+
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src");
|
|
132
|
+
let hits = grep_tree(&root, "force-paste") + grep_tree(&root, "force_paste");
|
|
133
|
+
assert_eq!(
|
|
134
|
+
hits, 0,
|
|
135
|
+
"B-4: --force-paste / force_paste must not appear in product code (cr REJECT: the \
|
|
136
|
+
delivery trunk does not consult classify, so this flag is a back-door on a non-existent \
|
|
137
|
+
gate — N35/MUST-17); found {hits} occurrence(s)"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// ───────────────────────────── B-2 · attach_commands ─────────────────────────────
|
|
142
|
+
|
|
143
|
+
/// B-2 (locate root cause): a successful quick-start Ready report must carry non-empty
|
|
144
|
+
/// attach_commands, and a socket-probe that finds no socket on disk must surface an
|
|
145
|
+
/// event/hint (not a silent empty set). Driven through quick-start with a recording
|
|
146
|
+
/// transport that provides a window.
|
|
147
|
+
#[test]
|
|
148
|
+
#[serial(env)]
|
|
149
|
+
fn b2_quick_start_ready_has_attach_commands_and_socket_miss_is_observable() {
|
|
150
|
+
let _g = EnvGuard::set(&[(ANCESTRY_KEY, NEUTRAL_ANCESTRY)]);
|
|
151
|
+
let ws = tmp_ws("b2-attach");
|
|
152
|
+
let team_dir = write_min_team(&ws, "b2team");
|
|
153
|
+
seed_healthy_coordinator(&ws);
|
|
154
|
+
let report = team_agent::lifecycle::quick_start_with_transport_in_workspace(
|
|
155
|
+
&ws,
|
|
156
|
+
&team_dir,
|
|
157
|
+
None,
|
|
158
|
+
true,
|
|
159
|
+
Some("b2team"),
|
|
160
|
+
&WindowedTransport::new("b2team", &["worker_a"]),
|
|
161
|
+
)
|
|
162
|
+
.expect("quick-start should run");
|
|
163
|
+
|
|
164
|
+
let mut failures = Vec::new();
|
|
165
|
+
match &report {
|
|
166
|
+
QuickStartReport::Ready {
|
|
167
|
+
attach_commands,
|
|
168
|
+
next_actions,
|
|
169
|
+
..
|
|
170
|
+
} => assert_attach_commands_or_socket_hint(
|
|
171
|
+
"Ready",
|
|
172
|
+
attach_commands,
|
|
173
|
+
next_actions,
|
|
174
|
+
&mut failures,
|
|
175
|
+
),
|
|
176
|
+
other => failures.push(format!("B-2 fixture expected Ready report; got {other:?}")),
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let existing = team_agent::lifecycle::quick_start_with_transport_in_workspace(
|
|
180
|
+
&ws,
|
|
181
|
+
&team_dir,
|
|
182
|
+
None,
|
|
183
|
+
true,
|
|
184
|
+
Some("b2team"),
|
|
185
|
+
&WindowedTransport::new("b2team", &["worker_a"]),
|
|
186
|
+
)
|
|
187
|
+
.expect("second quick-start should return ExistingRuntime");
|
|
188
|
+
match &existing {
|
|
189
|
+
QuickStartReport::ExistingRuntime {
|
|
190
|
+
attach_commands,
|
|
191
|
+
next_actions,
|
|
192
|
+
..
|
|
193
|
+
} => assert_attach_commands_or_socket_hint(
|
|
194
|
+
"ExistingRuntime",
|
|
195
|
+
attach_commands,
|
|
196
|
+
next_actions,
|
|
197
|
+
&mut failures,
|
|
198
|
+
),
|
|
199
|
+
other => failures.push(format!(
|
|
200
|
+
"B-2 duplicate quick-start expected ExistingRuntime report; got {other:?}"
|
|
201
|
+
)),
|
|
202
|
+
}
|
|
203
|
+
assert!(
|
|
204
|
+
failures.is_empty(),
|
|
205
|
+
"B-2 attach_commands contract failed:\n{}",
|
|
206
|
+
failures.join("\n")
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// ───────────────────────────── B-5 · status runtime block ─────────────────────────
|
|
211
|
+
|
|
212
|
+
/// B-5 (N38): when there is NO coordinator pid AND accepted backlog exists, status must
|
|
213
|
+
/// surface a runtime block (coordinator.ok==false) and a hint mentioning the coordinator
|
|
214
|
+
/// + restart. Driven through the public status entry.
|
|
215
|
+
#[test]
|
|
216
|
+
#[serial(env)]
|
|
217
|
+
fn b5_status_surfaces_runtime_hint_on_stale_coordinator_with_backlog() {
|
|
218
|
+
let _g = EnvGuard::set(&[(ANCESTRY_KEY, NEUTRAL_ANCESTRY)]);
|
|
219
|
+
let ws = tmp_ws("b5-stale");
|
|
220
|
+
save_runtime_state(
|
|
221
|
+
&ws,
|
|
222
|
+
&json!({"session_name": "team-b5", "active_team_key": "team-b5", "agents": {
|
|
223
|
+
"w1": {"status": "running", "provider": "codex", "agent_id": "w1", "window": "w1"}
|
|
224
|
+
}}),
|
|
225
|
+
)
|
|
226
|
+
.unwrap();
|
|
227
|
+
// NO coordinator pid file written (stale/dead). Two accepted messages backlog.
|
|
228
|
+
let store = MessageStore::open(&ws).unwrap();
|
|
229
|
+
store
|
|
230
|
+
.create_message(Some("t1"), "leader", "w1", "m1", None, false, None)
|
|
231
|
+
.unwrap();
|
|
232
|
+
store
|
|
233
|
+
.create_message(Some("t1"), "leader", "w1", "m2", None, false, None)
|
|
234
|
+
.unwrap();
|
|
235
|
+
|
|
236
|
+
let status = team_agent::cli::status_port::status(&ws, false, false).expect("status");
|
|
237
|
+
let text = status.to_string();
|
|
238
|
+
let mut failures = Vec::new();
|
|
239
|
+
|
|
240
|
+
let runtime_ok = status
|
|
241
|
+
.pointer("/runtime/coordinator/ok")
|
|
242
|
+
.and_then(Value::as_bool);
|
|
243
|
+
if runtime_ok != Some(false) {
|
|
244
|
+
failures.push(format!(
|
|
245
|
+
"B-5: status must carry a runtime block with coordinator.ok==false when the \
|
|
246
|
+
coordinator is not running (consume coordinator_health); got runtime/coordinator/ok={runtime_ok:?}"
|
|
247
|
+
));
|
|
248
|
+
}
|
|
249
|
+
let has_hint = text.contains("undelivered")
|
|
250
|
+
|| (text.to_lowercase().contains("coordinator") && text.contains("restart"));
|
|
251
|
+
if !has_hint {
|
|
252
|
+
failures.push(format!(
|
|
253
|
+
"B-5: status must add a hint naming the coordinator + restart when it is down \
|
|
254
|
+
AND there is accepted backlog (N38 explicable, no auto-recovery); status={text}"
|
|
255
|
+
));
|
|
256
|
+
}
|
|
257
|
+
assert!(
|
|
258
|
+
failures.is_empty(),
|
|
259
|
+
"B-5 status runtime-hint contract failed:\n{}",
|
|
260
|
+
failures.join("\n")
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// B-5 anti-nag: a HEALTHY coordinator with backlog must NOT show the down-hint.
|
|
265
|
+
#[test]
|
|
266
|
+
#[serial(env)]
|
|
267
|
+
fn b5_status_no_hint_when_coordinator_healthy() {
|
|
268
|
+
let _g = EnvGuard::set(&[(ANCESTRY_KEY, NEUTRAL_ANCESTRY)]);
|
|
269
|
+
let ws = tmp_ws("b5-healthy");
|
|
270
|
+
let live_session = LiveTmuxSession::start(&ws, "team-b5h");
|
|
271
|
+
let socket = live_session.socket.display().to_string();
|
|
272
|
+
save_runtime_state(
|
|
273
|
+
&ws,
|
|
274
|
+
&json!({"session_name": "team-b5h", "active_team_key": "team-b5h", "agents": {
|
|
275
|
+
"w1": {"status": "running", "provider": "codex", "agent_id": "w1", "window": "w1"}
|
|
276
|
+
}, "tmux_endpoint": socket, "tmux_socket": socket}),
|
|
277
|
+
)
|
|
278
|
+
.unwrap();
|
|
279
|
+
seed_healthy_coordinator(&ws);
|
|
280
|
+
let store = MessageStore::open(&ws).unwrap();
|
|
281
|
+
store
|
|
282
|
+
.create_message(Some("t1"), "leader", "w1", "m1", None, false, None)
|
|
283
|
+
.unwrap();
|
|
284
|
+
|
|
285
|
+
let status = team_agent::cli::status_port::status(&ws, false, false).expect("status");
|
|
286
|
+
let text = status.to_string().to_lowercase();
|
|
287
|
+
assert!(
|
|
288
|
+
!(text.contains("coordinator not running") || text.contains("run team-agent restart")),
|
|
289
|
+
"B-5 anti-nag: a healthy coordinator must not show the down-hint (no nag); status={text}"
|
|
290
|
+
);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ───────────────────────────── B-7 · leader pane env fail-fast ─────────────────────
|
|
294
|
+
|
|
295
|
+
/// B-7 (N38 fail-fast): a TEAM_AGENT_LEADER_PANE_ID pointing at a dead/absent pane must
|
|
296
|
+
/// make the active quick-start path fail with a clear error (naming the pane + an
|
|
297
|
+
/// action), not bind silently. Unset must pass through.
|
|
298
|
+
#[test]
|
|
299
|
+
#[serial(env)]
|
|
300
|
+
fn b7_dead_leader_pane_env_fails_fast_on_quick_start() {
|
|
301
|
+
let ws = tmp_ws("b7-deadpane");
|
|
302
|
+
let team_dir = write_min_team(&ws, "b7team");
|
|
303
|
+
seed_healthy_coordinator(&ws);
|
|
304
|
+
let _g = EnvGuard::set(&[
|
|
305
|
+
(ANCESTRY_KEY, NEUTRAL_ANCESTRY),
|
|
306
|
+
("TEAM_AGENT_LEADER_PANE_ID", "%9999"),
|
|
307
|
+
]);
|
|
308
|
+
|
|
309
|
+
let result = team_agent::lifecycle::quick_start_with_transport_in_workspace(
|
|
310
|
+
&ws,
|
|
311
|
+
&team_dir,
|
|
312
|
+
None,
|
|
313
|
+
true,
|
|
314
|
+
Some("b7team"),
|
|
315
|
+
&DeadPaneTransport,
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
let text = format!("{result:?}");
|
|
319
|
+
let mut failures = Vec::new();
|
|
320
|
+
if result.is_ok() {
|
|
321
|
+
failures.push(format!(
|
|
322
|
+
"B-7: a TEAM_AGENT_LEADER_PANE_ID pointing at a dead/absent pane (%9999) must \
|
|
323
|
+
make quick-start fail-fast, not bind silently; got Ok: {text}"
|
|
324
|
+
));
|
|
325
|
+
} else if !(text.contains("%9999") && (text.to_lowercase().contains("pane"))) {
|
|
326
|
+
failures.push(format!(
|
|
327
|
+
"B-7: the fail-fast error must name the offending pane id and be about the \
|
|
328
|
+
leader pane (N38 three-line: error/action/log); got {text}"
|
|
329
|
+
));
|
|
330
|
+
}
|
|
331
|
+
assert!(
|
|
332
|
+
failures.is_empty(),
|
|
333
|
+
"B-7 dead-pane fail-fast contract failed:\n{}",
|
|
334
|
+
failures.join("\n")
|
|
335
|
+
);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
/// B-7 (no false-positive): with the env UNSET, quick-start must proceed normally.
|
|
339
|
+
#[test]
|
|
340
|
+
#[serial(env)]
|
|
341
|
+
fn b7_unset_leader_pane_env_passes_through() {
|
|
342
|
+
let ws = tmp_ws("b7-unset");
|
|
343
|
+
let team_dir = write_min_team(&ws, "b7uteam");
|
|
344
|
+
seed_healthy_coordinator(&ws);
|
|
345
|
+
let _g = EnvGuard::set(&[(ANCESTRY_KEY, NEUTRAL_ANCESTRY)]);
|
|
346
|
+
std::env::remove_var("TEAM_AGENT_LEADER_PANE_ID");
|
|
347
|
+
|
|
348
|
+
let result = team_agent::lifecycle::quick_start_with_transport_in_workspace(
|
|
349
|
+
&ws,
|
|
350
|
+
&team_dir,
|
|
351
|
+
None,
|
|
352
|
+
true,
|
|
353
|
+
Some("b7uteam"),
|
|
354
|
+
&WindowedTransport::new("b7uteam", &["worker_a"]),
|
|
355
|
+
);
|
|
356
|
+
assert!(
|
|
357
|
+
result.is_ok(),
|
|
358
|
+
"B-7: an unset TEAM_AGENT_LEADER_PANE_ID must not trip the fail-fast (no false \
|
|
359
|
+
positive); got {result:?}"
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// ───────────────────────────────────── fixtures ─────────────────────────────────────
|
|
364
|
+
|
|
365
|
+
fn seed_copilot_team_state(ws: &Path, agent: &str, accepted_msg: bool) {
|
|
366
|
+
let _ = accepted_msg;
|
|
367
|
+
save_runtime_state(
|
|
368
|
+
ws,
|
|
369
|
+
&json!({
|
|
370
|
+
"session_name": "team-b4",
|
|
371
|
+
"active_team_key": "team-b4",
|
|
372
|
+
"agents": {
|
|
373
|
+
agent: {
|
|
374
|
+
"status": "running", "provider": "copilot", "agent_id": agent,
|
|
375
|
+
"window": agent, "pane_id": "%41",
|
|
376
|
+
"session_id": "11111111-2222-4333-8444-555555555555",
|
|
377
|
+
"spawn_cwd": ws.to_string_lossy(),
|
|
378
|
+
},
|
|
379
|
+
},
|
|
380
|
+
}),
|
|
381
|
+
)
|
|
382
|
+
.unwrap();
|
|
383
|
+
let _ = MessageStore::open(ws).unwrap();
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
fn write_min_team(ws: &Path, name: &str) -> PathBuf {
|
|
387
|
+
let team = ws.join(name);
|
|
388
|
+
std::fs::create_dir_all(team.join("agents")).unwrap();
|
|
389
|
+
std::fs::write(
|
|
390
|
+
team.join("TEAM.md"),
|
|
391
|
+
format!("---\nname: {name}\nobjective: B-batch fixture.\nprovider: codex\n---\n\nTeam.\n"),
|
|
392
|
+
)
|
|
393
|
+
.unwrap();
|
|
394
|
+
std::fs::write(
|
|
395
|
+
team.join("agents/worker_a.md"),
|
|
396
|
+
"---\nname: worker_a\nrole: Worker\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nWorker.\n",
|
|
397
|
+
)
|
|
398
|
+
.unwrap();
|
|
399
|
+
team
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
fn seed_healthy_coordinator(workspace: &Path) {
|
|
403
|
+
let workspace = team_agent::coordinator::WorkspacePath::new(workspace.to_path_buf());
|
|
404
|
+
let pid = team_agent::coordinator::Pid::new(std::process::id());
|
|
405
|
+
std::fs::create_dir_all(
|
|
406
|
+
team_agent::coordinator::coordinator_pid_path(&workspace)
|
|
407
|
+
.parent()
|
|
408
|
+
.unwrap(),
|
|
409
|
+
)
|
|
410
|
+
.unwrap();
|
|
411
|
+
team_agent::coordinator::write_coordinator_metadata(
|
|
412
|
+
&workspace,
|
|
413
|
+
pid,
|
|
414
|
+
team_agent::coordinator::MetadataSource::Boot,
|
|
415
|
+
)
|
|
416
|
+
.expect("write coordinator metadata");
|
|
417
|
+
std::fs::write(
|
|
418
|
+
team_agent::coordinator::coordinator_pid_path(&workspace),
|
|
419
|
+
pid.to_string(),
|
|
420
|
+
)
|
|
421
|
+
.unwrap();
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
fn events_text(ws: &Path) -> String {
|
|
425
|
+
std::fs::read_to_string(ws.join(".team/logs/events.jsonl")).unwrap_or_default()
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
fn grep_tree(root: &Path, needle: &str) -> usize {
|
|
429
|
+
let mut count = 0;
|
|
430
|
+
let entries = std::fs::read_dir(root).into_iter().flatten().flatten();
|
|
431
|
+
for entry in entries {
|
|
432
|
+
let path = entry.path();
|
|
433
|
+
if path.is_dir() {
|
|
434
|
+
if path.file_name().and_then(|name| name.to_str()) == Some("tests") {
|
|
435
|
+
continue;
|
|
436
|
+
}
|
|
437
|
+
count += grep_tree(&path, needle);
|
|
438
|
+
} else if path.extension().and_then(|s| s.to_str()) == Some("rs") {
|
|
439
|
+
if path.file_name().and_then(|name| name.to_str()) == Some("tests.rs") {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
count += std::fs::read_to_string(&path)
|
|
443
|
+
.unwrap_or_default()
|
|
444
|
+
.matches(needle)
|
|
445
|
+
.count();
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
count
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
struct RealRegistry;
|
|
452
|
+
impl ProviderRegistry for RealRegistry {
|
|
453
|
+
fn adapter_for(&self, provider: Provider) -> Box<dyn ProviderAdapter> {
|
|
454
|
+
get_adapter(provider)
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
struct EnvGuard {
|
|
459
|
+
previous: Vec<(&'static str, Option<String>)>,
|
|
460
|
+
}
|
|
461
|
+
impl EnvGuard {
|
|
462
|
+
fn set(values: &[(&'static str, &'static str)]) -> Self {
|
|
463
|
+
let previous = values
|
|
464
|
+
.iter()
|
|
465
|
+
.map(|(k, _)| (*k, std::env::var(k).ok()))
|
|
466
|
+
.collect();
|
|
467
|
+
for (k, v) in values {
|
|
468
|
+
std::env::set_var(k, v);
|
|
469
|
+
}
|
|
470
|
+
Self { previous }
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
impl Drop for EnvGuard {
|
|
474
|
+
fn drop(&mut self) {
|
|
475
|
+
for (k, v) in self.previous.drain(..).rev() {
|
|
476
|
+
match v {
|
|
477
|
+
Some(v) => std::env::set_var(k, v),
|
|
478
|
+
None => std::env::remove_var(k),
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
fn tmp_ws(tag: &str) -> PathBuf {
|
|
485
|
+
static N: AtomicU64 = AtomicU64::new(0);
|
|
486
|
+
let dir = std::env::temp_dir().join(format!(
|
|
487
|
+
"ta-rs-036b-{tag}-{}-{}",
|
|
488
|
+
std::process::id(),
|
|
489
|
+
N.fetch_add(1, Ordering::Relaxed)
|
|
490
|
+
));
|
|
491
|
+
let _ = std::fs::remove_dir_all(&dir);
|
|
492
|
+
std::fs::create_dir_all(&dir).unwrap();
|
|
493
|
+
std::fs::canonicalize(dir).unwrap()
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
struct LiveTmuxSession {
|
|
497
|
+
socket: PathBuf,
|
|
498
|
+
session: String,
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
impl LiveTmuxSession {
|
|
502
|
+
fn start(_workspace: &Path, session: &str) -> Self {
|
|
503
|
+
// 0.5.43 debt-sweep (§4.2): use the short_tmux_socket helper.
|
|
504
|
+
// The pre-0.5.43 code inherited $TMPDIR which on macOS is
|
|
505
|
+
// /var/folders/... > 104 bytes, tripping AF_UNIX sun_path
|
|
506
|
+
// and causing the Gate2 flake family.
|
|
507
|
+
let socket = hermetic_guard::short_tmux_socket("b5-live-session");
|
|
508
|
+
let socket_str = socket.to_str().expect("socket path utf8");
|
|
509
|
+
let out = Command::new("tmux")
|
|
510
|
+
.args([
|
|
511
|
+
"-S",
|
|
512
|
+
socket_str,
|
|
513
|
+
"new-session",
|
|
514
|
+
"-d",
|
|
515
|
+
"-s",
|
|
516
|
+
session,
|
|
517
|
+
"-n",
|
|
518
|
+
"leader",
|
|
519
|
+
"sleep 60",
|
|
520
|
+
])
|
|
521
|
+
.output()
|
|
522
|
+
.unwrap_or_else(|e| panic!("tmux new-session {session}: {e}"));
|
|
523
|
+
assert!(
|
|
524
|
+
out.status.success(),
|
|
525
|
+
"tmux new-session failed; stdout={} stderr={}",
|
|
526
|
+
String::from_utf8_lossy(&out.stdout),
|
|
527
|
+
String::from_utf8_lossy(&out.stderr)
|
|
528
|
+
);
|
|
529
|
+
Self {
|
|
530
|
+
socket,
|
|
531
|
+
session: session.to_string(),
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
impl Drop for LiveTmuxSession {
|
|
537
|
+
fn drop(&mut self) {
|
|
538
|
+
if let Some(socket) = self.socket.to_str() {
|
|
539
|
+
let _ = Command::new("tmux")
|
|
540
|
+
.args(["-S", socket, "kill-session", "-t", &self.session])
|
|
541
|
+
.status();
|
|
542
|
+
let _ = std::fs::remove_file(socket);
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
fn assert_attach_commands_or_socket_hint(
|
|
548
|
+
branch: &str,
|
|
549
|
+
attach_commands: &[String],
|
|
550
|
+
next_actions: &[String],
|
|
551
|
+
failures: &mut Vec<String>,
|
|
552
|
+
) {
|
|
553
|
+
if attach_commands.is_empty() {
|
|
554
|
+
if !has_socket_missing_hint(next_actions) {
|
|
555
|
+
failures.push(format!(
|
|
556
|
+
"B-2: {branch} with no attach_commands must explain the missing tmux socket \
|
|
557
|
+
and give an attach/restart action; next_actions={next_actions:?}"
|
|
558
|
+
));
|
|
559
|
+
}
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
for command in attach_commands {
|
|
564
|
+
let Some(socket_path) = attach_socket_path(command) else {
|
|
565
|
+
failures.push(format!(
|
|
566
|
+
"B-2: {branch} attach command must include `tmux -S <socket>`; command={command:?}"
|
|
567
|
+
));
|
|
568
|
+
continue;
|
|
569
|
+
};
|
|
570
|
+
if !socket_path.exists() {
|
|
571
|
+
if !has_socket_missing_hint(next_actions) {
|
|
572
|
+
failures.push(format!(
|
|
573
|
+
"B-2: {branch} attach command points at a socket missing on disk, so the report \
|
|
574
|
+
must also explain the socket miss; command={command:?} socket_path={} next_actions={next_actions:?}",
|
|
575
|
+
socket_path.display()
|
|
576
|
+
));
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
fn has_socket_missing_hint(next_actions: &[String]) -> bool {
|
|
583
|
+
let hints = next_actions.join("\n").to_lowercase();
|
|
584
|
+
hints.contains("socket")
|
|
585
|
+
&& (hints.contains("not found") || hints.contains("missing"))
|
|
586
|
+
&& (hints.contains("attach") || hints.contains("restart"))
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
fn attach_socket_path(command: &str) -> Option<PathBuf> {
|
|
590
|
+
let mut parts = command.split_whitespace();
|
|
591
|
+
while let Some(part) = parts.next() {
|
|
592
|
+
if part == "-S" {
|
|
593
|
+
return parts.next().map(PathBuf::from);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
None
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// ─────────────────────────────── transports ───────────────────────────────
|
|
600
|
+
|
|
601
|
+
/// A transport whose pane capture HARD-FAILS — used to drive a monitor-step failure
|
|
602
|
+
/// (B-4). Sessions/windows present so the tick proceeds to the monitor steps.
|
|
603
|
+
struct MonitorFailTransport {
|
|
604
|
+
session: Mutex<bool>,
|
|
605
|
+
}
|
|
606
|
+
impl MonitorFailTransport {
|
|
607
|
+
fn new() -> Self {
|
|
608
|
+
Self {
|
|
609
|
+
session: Mutex::new(true),
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
/// A quiet transport (sessions present, capture returns an idle screen) for the dedup
|
|
615
|
+
/// tests where we only care about the classify event count.
|
|
616
|
+
struct QuietTransport;
|
|
617
|
+
|
|
618
|
+
/// Provides named windows + sessions so attach_commands can be built (B-2).
|
|
619
|
+
struct WindowedTransport {
|
|
620
|
+
session: SessionName,
|
|
621
|
+
windows: Vec<WindowName>,
|
|
622
|
+
present: Mutex<bool>,
|
|
623
|
+
}
|
|
624
|
+
impl WindowedTransport {
|
|
625
|
+
fn new(team: &str, windows: &[&str]) -> Self {
|
|
626
|
+
Self {
|
|
627
|
+
session: SessionName::new(format!("team-{team}")),
|
|
628
|
+
windows: windows.iter().map(|w| WindowName::new(*w)).collect(),
|
|
629
|
+
present: Mutex::new(false),
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/// Reports the leader pane (%9999) as DEAD (B-7).
|
|
635
|
+
struct DeadPaneTransport;
|
|
636
|
+
|
|
637
|
+
impl Transport for MonitorFailTransport {
|
|
638
|
+
fn kind(&self) -> BackendKind {
|
|
639
|
+
BackendKind::Tmux
|
|
640
|
+
}
|
|
641
|
+
fn spawn_first(
|
|
642
|
+
&self,
|
|
643
|
+
s: &SessionName,
|
|
644
|
+
w: &WindowName,
|
|
645
|
+
_a: &[String],
|
|
646
|
+
_c: &Path,
|
|
647
|
+
_e: &BTreeMap<String, String>,
|
|
648
|
+
) -> Result<SpawnResult, TransportError> {
|
|
649
|
+
*self.session.lock().unwrap() = true;
|
|
650
|
+
Ok(SpawnResult {
|
|
651
|
+
pane_id: PaneId::new("%1"),
|
|
652
|
+
session: s.clone(),
|
|
653
|
+
window: w.clone(),
|
|
654
|
+
child_pid: None,
|
|
655
|
+
})
|
|
656
|
+
}
|
|
657
|
+
fn spawn_into(
|
|
658
|
+
&self,
|
|
659
|
+
s: &SessionName,
|
|
660
|
+
w: &WindowName,
|
|
661
|
+
a: &[String],
|
|
662
|
+
c: &Path,
|
|
663
|
+
e: &BTreeMap<String, String>,
|
|
664
|
+
) -> Result<SpawnResult, TransportError> {
|
|
665
|
+
self.spawn_first(s, w, a, c, e)
|
|
666
|
+
}
|
|
667
|
+
fn capture(&self, _t: &Target, _r: CaptureRange) -> Result<CapturedText, TransportError> {
|
|
668
|
+
Err(TransportError::Subprocess {
|
|
669
|
+
argv: vec!["tmux".to_string(), "capture-pane".to_string()],
|
|
670
|
+
code: Some(1),
|
|
671
|
+
stderr: "injected monitor-step capture failure".to_string(),
|
|
672
|
+
})
|
|
673
|
+
}
|
|
674
|
+
fn liveness(&self, _p: &PaneId) -> Result<PaneLiveness, TransportError> {
|
|
675
|
+
Ok(PaneLiveness::Live)
|
|
676
|
+
}
|
|
677
|
+
fn has_session(&self, _s: &SessionName) -> Result<bool, TransportError> {
|
|
678
|
+
Ok(*self.session.lock().unwrap())
|
|
679
|
+
}
|
|
680
|
+
fn send_keys(&self, _t: &Target, _k: &[Key]) -> Result<(), TransportError> {
|
|
681
|
+
Ok(())
|
|
682
|
+
}
|
|
683
|
+
fn query(&self, _t: &Target, f: PaneField) -> Result<Option<String>, TransportError> {
|
|
684
|
+
match f {
|
|
685
|
+
PaneField::PaneWidth => Ok(Some("120".to_string())),
|
|
686
|
+
_ => Ok(None),
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
|
|
690
|
+
Ok(Vec::new())
|
|
691
|
+
}
|
|
692
|
+
fn list_windows(&self, _s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
693
|
+
Ok(vec![WindowName::new("cp1")])
|
|
694
|
+
}
|
|
695
|
+
fn set_session_env(
|
|
696
|
+
&self,
|
|
697
|
+
_s: &SessionName,
|
|
698
|
+
_k: &str,
|
|
699
|
+
_v: &str,
|
|
700
|
+
) -> Result<SetEnvOutcome, TransportError> {
|
|
701
|
+
Ok(SetEnvOutcome::Applied)
|
|
702
|
+
}
|
|
703
|
+
fn kill_session(&self, _s: &SessionName) -> Result<(), TransportError> {
|
|
704
|
+
Ok(())
|
|
705
|
+
}
|
|
706
|
+
fn kill_window(&self, _t: &Target) -> Result<(), TransportError> {
|
|
707
|
+
Ok(())
|
|
708
|
+
}
|
|
709
|
+
fn attach_session(&self, _s: &SessionName) -> Result<AttachOutcome, TransportError> {
|
|
710
|
+
Ok(AttachOutcome::Attached)
|
|
711
|
+
}
|
|
712
|
+
fn inject(
|
|
713
|
+
&self,
|
|
714
|
+
_t: &Target,
|
|
715
|
+
_p: &InjectPayload,
|
|
716
|
+
_s: Key,
|
|
717
|
+
_b: bool,
|
|
718
|
+
) -> Result<InjectReport, TransportError> {
|
|
719
|
+
Ok(InjectReport {
|
|
720
|
+
stage_reached: InjectStage::Submit,
|
|
721
|
+
inject_verification: InjectVerification::CaptureContainsToken,
|
|
722
|
+
submit_verification: SubmitVerification::EnterSentWithoutPlaceholderCheck,
|
|
723
|
+
turn_verification: TurnVerification::NotYetObserved,
|
|
724
|
+
attempts: 1,
|
|
725
|
+
submit_diagnostics: None,
|
|
726
|
+
})
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
impl Transport for QuietTransport {
|
|
731
|
+
fn spawn_first(
|
|
732
|
+
&self,
|
|
733
|
+
s: &SessionName,
|
|
734
|
+
w: &WindowName,
|
|
735
|
+
_a: &[String],
|
|
736
|
+
_c: &Path,
|
|
737
|
+
_e: &BTreeMap<String, String>,
|
|
738
|
+
) -> Result<SpawnResult, TransportError> {
|
|
739
|
+
Ok(SpawnResult {
|
|
740
|
+
pane_id: PaneId::new("%1"),
|
|
741
|
+
session: s.clone(),
|
|
742
|
+
window: w.clone(),
|
|
743
|
+
child_pid: None,
|
|
744
|
+
})
|
|
745
|
+
}
|
|
746
|
+
fn spawn_into(
|
|
747
|
+
&self,
|
|
748
|
+
s: &SessionName,
|
|
749
|
+
w: &WindowName,
|
|
750
|
+
a: &[String],
|
|
751
|
+
c: &Path,
|
|
752
|
+
e: &BTreeMap<String, String>,
|
|
753
|
+
) -> Result<SpawnResult, TransportError> {
|
|
754
|
+
self.spawn_first(s, w, a, c, e)
|
|
755
|
+
}
|
|
756
|
+
fn capture(&self, _t: &Target, r: CaptureRange) -> Result<CapturedText, TransportError> {
|
|
757
|
+
Ok(CapturedText {
|
|
758
|
+
text: "Copilot\n> ".to_string(),
|
|
759
|
+
range: r,
|
|
760
|
+
})
|
|
761
|
+
}
|
|
762
|
+
fn liveness(&self, _p: &PaneId) -> Result<PaneLiveness, TransportError> {
|
|
763
|
+
Ok(PaneLiveness::Live)
|
|
764
|
+
}
|
|
765
|
+
fn has_session(&self, _s: &SessionName) -> Result<bool, TransportError> {
|
|
766
|
+
Ok(true)
|
|
767
|
+
}
|
|
768
|
+
fn list_windows(&self, _s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
769
|
+
Ok(vec![WindowName::new("cp1")])
|
|
770
|
+
}
|
|
771
|
+
fn kind(&self) -> BackendKind {
|
|
772
|
+
BackendKind::Tmux
|
|
773
|
+
}
|
|
774
|
+
fn send_keys(&self, _t: &Target, _k: &[Key]) -> Result<(), TransportError> {
|
|
775
|
+
Ok(())
|
|
776
|
+
}
|
|
777
|
+
fn query(&self, _t: &Target, f: PaneField) -> Result<Option<String>, TransportError> {
|
|
778
|
+
match f {
|
|
779
|
+
PaneField::PaneWidth => Ok(Some("120".to_string())),
|
|
780
|
+
_ => Ok(None),
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
|
|
784
|
+
Ok(Vec::new())
|
|
785
|
+
}
|
|
786
|
+
fn set_session_env(
|
|
787
|
+
&self,
|
|
788
|
+
_s: &SessionName,
|
|
789
|
+
_k: &str,
|
|
790
|
+
_v: &str,
|
|
791
|
+
) -> Result<SetEnvOutcome, TransportError> {
|
|
792
|
+
Ok(SetEnvOutcome::Applied)
|
|
793
|
+
}
|
|
794
|
+
fn kill_session(&self, _s: &SessionName) -> Result<(), TransportError> {
|
|
795
|
+
Ok(())
|
|
796
|
+
}
|
|
797
|
+
fn kill_window(&self, _t: &Target) -> Result<(), TransportError> {
|
|
798
|
+
Ok(())
|
|
799
|
+
}
|
|
800
|
+
fn attach_session(&self, _s: &SessionName) -> Result<AttachOutcome, TransportError> {
|
|
801
|
+
Ok(AttachOutcome::Attached)
|
|
802
|
+
}
|
|
803
|
+
fn inject(
|
|
804
|
+
&self,
|
|
805
|
+
_t: &Target,
|
|
806
|
+
_p: &InjectPayload,
|
|
807
|
+
_s: Key,
|
|
808
|
+
_b: bool,
|
|
809
|
+
) -> Result<InjectReport, TransportError> {
|
|
810
|
+
Ok(InjectReport {
|
|
811
|
+
stage_reached: InjectStage::Submit,
|
|
812
|
+
inject_verification: InjectVerification::CaptureContainsToken,
|
|
813
|
+
submit_verification: SubmitVerification::EnterSentWithoutPlaceholderCheck,
|
|
814
|
+
turn_verification: TurnVerification::NotYetObserved,
|
|
815
|
+
attempts: 1,
|
|
816
|
+
submit_diagnostics: None,
|
|
817
|
+
})
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
impl Transport for WindowedTransport {
|
|
822
|
+
fn spawn_first(
|
|
823
|
+
&self,
|
|
824
|
+
_s: &SessionName,
|
|
825
|
+
w: &WindowName,
|
|
826
|
+
_a: &[String],
|
|
827
|
+
_c: &Path,
|
|
828
|
+
_e: &BTreeMap<String, String>,
|
|
829
|
+
) -> Result<SpawnResult, TransportError> {
|
|
830
|
+
*self.present.lock().unwrap() = true;
|
|
831
|
+
Ok(SpawnResult {
|
|
832
|
+
pane_id: PaneId::new("%1"),
|
|
833
|
+
session: self.session.clone(),
|
|
834
|
+
window: w.clone(),
|
|
835
|
+
child_pid: None,
|
|
836
|
+
})
|
|
837
|
+
}
|
|
838
|
+
fn spawn_into(
|
|
839
|
+
&self,
|
|
840
|
+
s: &SessionName,
|
|
841
|
+
w: &WindowName,
|
|
842
|
+
a: &[String],
|
|
843
|
+
c: &Path,
|
|
844
|
+
e: &BTreeMap<String, String>,
|
|
845
|
+
) -> Result<SpawnResult, TransportError> {
|
|
846
|
+
self.spawn_first(s, w, a, c, e)
|
|
847
|
+
}
|
|
848
|
+
fn capture(&self, _t: &Target, r: CaptureRange) -> Result<CapturedText, TransportError> {
|
|
849
|
+
Ok(CapturedText {
|
|
850
|
+
text: "OpenAI Codex\ncodex>".to_string(),
|
|
851
|
+
range: r,
|
|
852
|
+
})
|
|
853
|
+
}
|
|
854
|
+
fn liveness(&self, _p: &PaneId) -> Result<PaneLiveness, TransportError> {
|
|
855
|
+
Ok(PaneLiveness::Live)
|
|
856
|
+
}
|
|
857
|
+
fn has_session(&self, _s: &SessionName) -> Result<bool, TransportError> {
|
|
858
|
+
Ok(*self.present.lock().unwrap())
|
|
859
|
+
}
|
|
860
|
+
fn list_windows(&self, _s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
861
|
+
Ok(self.windows.clone())
|
|
862
|
+
}
|
|
863
|
+
fn kind(&self) -> BackendKind {
|
|
864
|
+
BackendKind::Tmux
|
|
865
|
+
}
|
|
866
|
+
fn send_keys(&self, _t: &Target, _k: &[Key]) -> Result<(), TransportError> {
|
|
867
|
+
Ok(())
|
|
868
|
+
}
|
|
869
|
+
fn query(&self, _t: &Target, f: PaneField) -> Result<Option<String>, TransportError> {
|
|
870
|
+
match f {
|
|
871
|
+
PaneField::PaneWidth => Ok(Some("120".to_string())),
|
|
872
|
+
_ => Ok(None),
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
|
|
876
|
+
Ok(Vec::new())
|
|
877
|
+
}
|
|
878
|
+
fn set_session_env(
|
|
879
|
+
&self,
|
|
880
|
+
_s: &SessionName,
|
|
881
|
+
_k: &str,
|
|
882
|
+
_v: &str,
|
|
883
|
+
) -> Result<SetEnvOutcome, TransportError> {
|
|
884
|
+
Ok(SetEnvOutcome::Applied)
|
|
885
|
+
}
|
|
886
|
+
fn kill_session(&self, _s: &SessionName) -> Result<(), TransportError> {
|
|
887
|
+
Ok(())
|
|
888
|
+
}
|
|
889
|
+
fn kill_window(&self, _t: &Target) -> Result<(), TransportError> {
|
|
890
|
+
Ok(())
|
|
891
|
+
}
|
|
892
|
+
fn attach_session(&self, _s: &SessionName) -> Result<AttachOutcome, TransportError> {
|
|
893
|
+
Ok(AttachOutcome::Attached)
|
|
894
|
+
}
|
|
895
|
+
fn inject(
|
|
896
|
+
&self,
|
|
897
|
+
_t: &Target,
|
|
898
|
+
_p: &InjectPayload,
|
|
899
|
+
_s: Key,
|
|
900
|
+
_b: bool,
|
|
901
|
+
) -> Result<InjectReport, TransportError> {
|
|
902
|
+
Ok(InjectReport {
|
|
903
|
+
stage_reached: InjectStage::Submit,
|
|
904
|
+
inject_verification: InjectVerification::CaptureContainsToken,
|
|
905
|
+
submit_verification: SubmitVerification::EnterSentWithoutPlaceholderCheck,
|
|
906
|
+
turn_verification: TurnVerification::NotYetObserved,
|
|
907
|
+
attempts: 1,
|
|
908
|
+
submit_diagnostics: None,
|
|
909
|
+
})
|
|
910
|
+
}
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
impl Transport for DeadPaneTransport {
|
|
914
|
+
fn spawn_first(
|
|
915
|
+
&self,
|
|
916
|
+
s: &SessionName,
|
|
917
|
+
w: &WindowName,
|
|
918
|
+
_a: &[String],
|
|
919
|
+
_c: &Path,
|
|
920
|
+
_e: &BTreeMap<String, String>,
|
|
921
|
+
) -> Result<SpawnResult, TransportError> {
|
|
922
|
+
Ok(SpawnResult {
|
|
923
|
+
pane_id: PaneId::new("%1"),
|
|
924
|
+
session: s.clone(),
|
|
925
|
+
window: w.clone(),
|
|
926
|
+
child_pid: None,
|
|
927
|
+
})
|
|
928
|
+
}
|
|
929
|
+
fn spawn_into(
|
|
930
|
+
&self,
|
|
931
|
+
s: &SessionName,
|
|
932
|
+
w: &WindowName,
|
|
933
|
+
a: &[String],
|
|
934
|
+
c: &Path,
|
|
935
|
+
e: &BTreeMap<String, String>,
|
|
936
|
+
) -> Result<SpawnResult, TransportError> {
|
|
937
|
+
self.spawn_first(s, w, a, c, e)
|
|
938
|
+
}
|
|
939
|
+
fn capture(&self, _t: &Target, r: CaptureRange) -> Result<CapturedText, TransportError> {
|
|
940
|
+
Ok(CapturedText {
|
|
941
|
+
text: String::new(),
|
|
942
|
+
range: r,
|
|
943
|
+
})
|
|
944
|
+
}
|
|
945
|
+
fn liveness(&self, pane: &PaneId) -> Result<PaneLiveness, TransportError> {
|
|
946
|
+
// %9999 (the leader pane env) is dead; everything else live.
|
|
947
|
+
if pane.as_str() == "%9999" {
|
|
948
|
+
Ok(PaneLiveness::Dead)
|
|
949
|
+
} else {
|
|
950
|
+
Ok(PaneLiveness::Live)
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
fn has_session(&self, _s: &SessionName) -> Result<bool, TransportError> {
|
|
954
|
+
Ok(true)
|
|
955
|
+
}
|
|
956
|
+
fn list_windows(&self, _s: &SessionName) -> Result<Vec<WindowName>, TransportError> {
|
|
957
|
+
Ok(vec![WindowName::new("worker_a")])
|
|
958
|
+
}
|
|
959
|
+
fn kind(&self) -> BackendKind {
|
|
960
|
+
BackendKind::Tmux
|
|
961
|
+
}
|
|
962
|
+
fn send_keys(&self, _t: &Target, _k: &[Key]) -> Result<(), TransportError> {
|
|
963
|
+
Ok(())
|
|
964
|
+
}
|
|
965
|
+
fn query(&self, _t: &Target, f: PaneField) -> Result<Option<String>, TransportError> {
|
|
966
|
+
match f {
|
|
967
|
+
PaneField::PaneWidth => Ok(Some("120".to_string())),
|
|
968
|
+
_ => Ok(None),
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
|
|
972
|
+
Ok(Vec::new())
|
|
973
|
+
}
|
|
974
|
+
fn set_session_env(
|
|
975
|
+
&self,
|
|
976
|
+
_s: &SessionName,
|
|
977
|
+
_k: &str,
|
|
978
|
+
_v: &str,
|
|
979
|
+
) -> Result<SetEnvOutcome, TransportError> {
|
|
980
|
+
Ok(SetEnvOutcome::Applied)
|
|
981
|
+
}
|
|
982
|
+
fn kill_session(&self, _s: &SessionName) -> Result<(), TransportError> {
|
|
983
|
+
Ok(())
|
|
984
|
+
}
|
|
985
|
+
fn kill_window(&self, _t: &Target) -> Result<(), TransportError> {
|
|
986
|
+
Ok(())
|
|
987
|
+
}
|
|
988
|
+
fn attach_session(&self, _s: &SessionName) -> Result<AttachOutcome, TransportError> {
|
|
989
|
+
Ok(AttachOutcome::Attached)
|
|
990
|
+
}
|
|
991
|
+
fn inject(
|
|
992
|
+
&self,
|
|
993
|
+
_t: &Target,
|
|
994
|
+
_p: &InjectPayload,
|
|
995
|
+
_s: Key,
|
|
996
|
+
_b: bool,
|
|
997
|
+
) -> Result<InjectReport, TransportError> {
|
|
998
|
+
Ok(InjectReport {
|
|
999
|
+
stage_reached: InjectStage::Submit,
|
|
1000
|
+
inject_verification: InjectVerification::CaptureContainsToken,
|
|
1001
|
+
submit_verification: SubmitVerification::EnterSentWithoutPlaceholderCheck,
|
|
1002
|
+
turn_verification: TurnVerification::NotYetObserved,
|
|
1003
|
+
attempts: 1,
|
|
1004
|
+
submit_diagnostics: None,
|
|
1005
|
+
})
|
|
1006
|
+
}
|
|
1007
|
+
}
|