@team-agent/installer 0.5.61 → 0.5.63

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.
Files changed (250) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +5 -2
  4. package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +1 -0
  6. package/crates/team-agent/src/cli/emit.rs +41 -7
  7. package/crates/team-agent/src/cli/helpers.rs +1 -0
  8. package/crates/team-agent/src/cli/leader.rs +6 -1
  9. package/crates/team-agent/src/cli/leaders.rs +2 -0
  10. package/crates/team-agent/src/cli/mod.rs +33 -3
  11. package/crates/team-agent/src/cli/named_address.rs +8 -1
  12. package/crates/team-agent/src/cli/profile.rs +2 -1
  13. package/crates/team-agent/src/cli/send/coordinator.rs +1 -0
  14. package/crates/team-agent/src/cli/send/mailbox.rs +3 -0
  15. package/crates/team-agent/src/cli/send/persist.rs +1 -0
  16. package/crates/team-agent/src/cli/send/presentation.rs +1 -0
  17. package/crates/team-agent/src/cli/send/resolve.rs +206 -1
  18. package/crates/team-agent/src/cli/send.rs +2 -1
  19. package/crates/team-agent/src/cli/spec.rs +1 -0
  20. package/crates/team-agent/src/cli/status.rs +2 -1
  21. package/crates/team-agent/src/cli/status_port/compact.rs +1 -0
  22. package/crates/team-agent/src/cli/status_port/format.rs +1 -0
  23. package/crates/team-agent/src/cli/status_port/inbox.rs +1 -0
  24. package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
  25. package/crates/team-agent/src/cli/types.rs +3 -2
  26. package/crates/team-agent/src/communication_mode/mod.rs +1 -0
  27. package/crates/team-agent/src/compiler.rs +1 -0
  28. package/crates/team-agent/src/conpty/backend.rs +1 -0
  29. package/crates/team-agent/src/conpty/mod.rs +1 -0
  30. package/crates/team-agent/src/coordinator/backoff.rs +5 -8
  31. package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
  32. package/crates/team-agent/src/coordinator/health.rs +1 -0
  33. package/crates/team-agent/src/coordinator/mod.rs +2 -2
  34. package/crates/team-agent/src/coordinator/orphan.rs +4 -116
  35. package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
  36. package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
  37. package/crates/team-agent/src/coordinator/steps/abnormal.rs +15 -866
  38. package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
  39. package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
  40. package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
  41. package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
  42. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
  43. package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
  44. package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
  45. package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +139 -375
  46. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
  47. package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -49
  49. package/crates/team-agent/src/coordinator/tick.rs +23 -40
  50. package/crates/team-agent/src/coordinator/types.rs +3 -176
  51. package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
  52. package/crates/team-agent/src/db/message_store.rs +2 -1
  53. package/crates/team-agent/src/db/migration.rs +5 -4
  54. package/crates/team-agent/src/db/mod.rs +1 -0
  55. package/crates/team-agent/src/db/schema.rs +1 -0
  56. package/crates/team-agent/src/diagnose/comms.rs +2 -1
  57. package/crates/team-agent/src/diagnose/mod.rs +1 -0
  58. package/crates/team-agent/src/diagnose/orphans.rs +1 -0
  59. package/crates/team-agent/src/layout/manager.rs +3 -7
  60. package/crates/team-agent/src/layout/mod.rs +2 -2
  61. package/crates/team-agent/src/layout/overlay.rs +6 -1
  62. package/crates/team-agent/src/layout/placement.rs +1 -0
  63. package/crates/team-agent/src/layout/recovery.rs +3 -0
  64. package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
  65. package/crates/team-agent/src/layout/sessions.rs +8 -1
  66. package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
  67. package/crates/team-agent/src/layout/worker_env.rs +3 -0
  68. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  69. package/crates/team-agent/src/leader/helpers.rs +1 -0
  70. package/crates/team-agent/src/leader/incident.rs +1 -0
  71. package/crates/team-agent/src/leader/lease.rs +27 -22
  72. package/crates/team-agent/src/leader/mod.rs +2 -3
  73. package/crates/team-agent/src/leader/owner_bind.rs +13 -3
  74. package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
  75. package/crates/team-agent/src/leader/rediscover.rs +4 -17
  76. package/crates/team-agent/src/leader/registry.rs +3 -15
  77. package/crates/team-agent/src/leader/start.rs +373 -125
  78. package/crates/team-agent/src/leader/takeover.rs +10 -232
  79. package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
  80. package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
  81. package/crates/team-agent/src/leader/tests/identity.rs +7 -2
  82. package/crates/team-agent/src/leader/tests/idle.rs +0 -193
  83. package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
  84. package/crates/team-agent/src/leader/tests/mod.rs +4 -41
  85. package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
  86. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
  87. package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
  88. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
  89. package/crates/team-agent/src/leader/types.rs +1 -28
  90. package/crates/team-agent/src/lib.rs +3 -0
  91. package/crates/team-agent/src/lifecycle/display.rs +7 -1
  92. package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
  93. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
  94. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
  95. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
  96. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
  97. package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
  98. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
  99. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
  100. package/crates/team-agent/src/lifecycle/launch/spawn.rs +1 -27
  101. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
  102. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
  103. package/crates/team-agent/src/lifecycle/launch.rs +11 -12
  104. package/crates/team-agent/src/lifecycle/mod.rs +2 -1
  105. package/crates/team-agent/src/lifecycle/restart/agent.rs +3 -66
  106. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
  107. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
  108. package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
  109. package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
  110. package/crates/team-agent/src/lifecycle/restart.rs +13 -15
  111. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
  112. package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
  113. package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
  114. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
  115. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
  116. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
  117. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
  118. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
  119. package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
  120. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
  121. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
  122. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
  123. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
  124. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
  125. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
  126. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
  127. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
  128. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
  129. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
  130. package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
  131. package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
  132. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
  133. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
  134. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
  135. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
  136. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
  137. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
  138. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
  139. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
  140. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
  141. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
  142. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
  143. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
  144. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
  145. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
  146. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
  147. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
  148. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
  149. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
  150. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
  151. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
  152. package/crates/team-agent/src/lifecycle/tests.rs +59 -0
  153. package/crates/team-agent/src/lifecycle/types.rs +0 -9
  154. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  155. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
  156. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
  157. package/crates/team-agent/src/mcp_server/mod.rs +1 -0
  158. package/crates/team-agent/src/mcp_server/normalize.rs +4 -3
  159. package/crates/team-agent/src/mcp_server/tools.rs +1 -0
  160. package/crates/team-agent/src/mcp_server/types.rs +1 -18
  161. package/crates/team-agent/src/mcp_server/wire.rs +5 -5
  162. package/crates/team-agent/src/messaging/activity.rs +1 -0
  163. package/crates/team-agent/src/messaging/address.rs +1 -0
  164. package/crates/team-agent/src/messaging/delivery.rs +4 -3
  165. package/crates/team-agent/src/messaging/helpers.rs +2 -1
  166. package/crates/team-agent/src/messaging/leader_channel.rs +1 -0
  167. package/crates/team-agent/src/messaging/leader_receiver.rs +2 -1
  168. package/crates/team-agent/src/messaging/mod.rs +10 -14
  169. package/crates/team-agent/src/messaging/peers.rs +2 -0
  170. package/crates/team-agent/src/messaging/persist.rs +3 -1
  171. package/crates/team-agent/src/messaging/presentation.rs +2 -1
  172. package/crates/team-agent/src/messaging/results.rs +1 -0
  173. package/crates/team-agent/src/messaging/scheduler.rs +1 -0
  174. package/crates/team-agent/src/messaging/selftest.rs +1 -1
  175. package/crates/team-agent/src/messaging/send.rs +41 -8
  176. package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
  177. package/crates/team-agent/src/messaging/types.rs +2 -2
  178. package/crates/team-agent/src/messaging/watchers.rs +19 -13
  179. package/crates/team-agent/src/model/enums.rs +1 -0
  180. package/crates/team-agent/src/model/errors.rs +1 -0
  181. package/crates/team-agent/src/model/ids.rs +1 -0
  182. package/crates/team-agent/src/model/mod.rs +1 -0
  183. package/crates/team-agent/src/model/name_similarity.rs +1 -0
  184. package/crates/team-agent/src/model/pane_authority_refusal.rs +1 -0
  185. package/crates/team-agent/src/model/paths.rs +1 -0
  186. package/crates/team-agent/src/model/permissions.rs +3 -2
  187. package/crates/team-agent/src/model/routing.rs +1 -0
  188. package/crates/team-agent/src/model/spec.rs +1 -0
  189. package/crates/team-agent/src/model/task_graph.rs +1 -0
  190. package/crates/team-agent/src/model/yaml/tests.rs +1 -0
  191. package/crates/team-agent/src/model/yaml.rs +1 -0
  192. package/crates/team-agent/src/packaging/install.rs +9 -147
  193. package/crates/team-agent/src/packaging/migrate.rs +2 -0
  194. package/crates/team-agent/src/packaging/mod.rs +1 -0
  195. package/crates/team-agent/src/packaging/repair.rs +2 -0
  196. package/crates/team-agent/src/packaging/tests.rs +29 -218
  197. package/crates/team-agent/src/packaging/types.rs +8 -15
  198. package/crates/team-agent/src/platform/argv.rs +4 -0
  199. package/crates/team-agent/src/platform/errors.rs +10 -1
  200. package/crates/team-agent/src/platform/file_lock.rs +4 -157
  201. package/crates/team-agent/src/platform/mod.rs +0 -61
  202. package/crates/team-agent/src/platform/process.rs +1 -0
  203. package/crates/team-agent/src/provider/adapter.rs +1 -0
  204. package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
  205. package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
  206. package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
  207. package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
  208. package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
  209. package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
  210. package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
  211. package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
  212. package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
  213. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
  214. package/crates/team-agent/src/provider/classify.rs +1 -0
  215. package/crates/team-agent/src/provider/faults.rs +1 -26
  216. package/crates/team-agent/src/provider/helpers.rs +1 -0
  217. package/crates/team-agent/src/provider/mod.rs +1 -1
  218. package/crates/team-agent/src/provider/session/capture.rs +1 -4
  219. package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
  220. package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
  221. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
  222. package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
  223. package/crates/team-agent/src/provider/session/mod.rs +2 -4
  224. package/crates/team-agent/src/provider/session/resume.rs +2 -209
  225. package/crates/team-agent/src/provider/session_scan/claude.rs +1 -0
  226. package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
  227. package/crates/team-agent/src/provider/session_scan/common.rs +1 -0
  228. package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
  229. package/crates/team-agent/src/provider/session_scan.rs +1 -42
  230. package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
  231. package/crates/team-agent/src/provider/types.rs +1 -0
  232. package/crates/team-agent/src/provider/wire.rs +1 -0
  233. package/crates/team-agent/src/state/identity.rs +16 -5
  234. package/crates/team-agent/src/state/identity_keys.rs +1 -0
  235. package/crates/team-agent/src/state/mod.rs +2 -0
  236. package/crates/team-agent/src/state/owner_gate.rs +5 -0
  237. package/crates/team-agent/src/state/ownership.rs +10 -4
  238. package/crates/team-agent/src/state/paths.rs +12 -0
  239. package/crates/team-agent/src/state/persist.rs +9 -3
  240. package/crates/team-agent/src/state/projection.rs +15 -2
  241. package/crates/team-agent/src/state/repository/intent.rs +1 -0
  242. package/crates/team-agent/src/state/repository/tests.rs +1 -0
  243. package/crates/team-agent/src/state/repository.rs +7 -13
  244. package/crates/team-agent/src/state/selector.rs +1 -0
  245. package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
  246. package/crates/team-agent/src/tmux_backend.rs +63 -0
  247. package/crates/team-agent/src/transport.rs +15 -0
  248. package/package.json +4 -4
  249. package/crates/team-agent/src/leader/inject.rs +0 -33
  250. 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
+ }