@team-agent/installer 0.5.42 → 0.5.44

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 (156) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +129 -54
  4. package/crates/team-agent/src/cli/diagnose.rs +1 -2
  5. package/crates/team-agent/src/cli/emit.rs +1 -7
  6. package/crates/team-agent/src/cli/helpers.rs +3 -1
  7. package/crates/team-agent/src/cli/leader.rs +2 -1
  8. package/crates/team-agent/src/cli/mod.rs +126 -16
  9. package/crates/team-agent/src/cli/named_address.rs +14 -5
  10. package/crates/team-agent/src/cli/profile.rs +19 -7
  11. package/crates/team-agent/src/cli/send.rs +9 -3
  12. package/crates/team-agent/src/cli/status.rs +8 -30
  13. package/crates/team-agent/src/cli/status_port.rs +1341 -1272
  14. package/crates/team-agent/src/cli/tests/base.rs +738 -660
  15. package/crates/team-agent/src/cli/tests/compile.rs +45 -18
  16. package/crates/team-agent/src/cli/tests/divergence.rs +462 -444
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +365 -282
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +356 -329
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +672 -564
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +284 -224
  21. package/crates/team-agent/src/cli/tests/mod.rs +17 -7
  22. package/crates/team-agent/src/cli/tests/named_address.rs +8 -2
  23. package/crates/team-agent/src/cli/tests/peer_allow.rs +10 -2
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +314 -273
  25. package/crates/team-agent/src/cli/tests/shutdown_kill_plan.rs +26 -15
  26. package/crates/team-agent/src/cli/tests/status_send.rs +707 -629
  27. package/crates/team-agent/src/cli/tests/verb_install_skill.rs +20 -4
  28. package/crates/team-agent/src/cli/tests/verb_profile.rs +46 -17
  29. package/crates/team-agent/src/cli/tests/verb_validate.rs +15 -3
  30. package/crates/team-agent/src/codex_app_server.rs +2 -5
  31. package/crates/team-agent/src/compiler/tests.rs +139 -33
  32. package/crates/team-agent/src/compiler.rs +55 -22
  33. package/crates/team-agent/src/conpty/backend.rs +23 -33
  34. package/crates/team-agent/src/coordinator/backoff.rs +2 -7
  35. package/crates/team-agent/src/coordinator/conpty_shim.rs +55 -67
  36. package/crates/team-agent/src/coordinator/health.rs +46 -31
  37. package/crates/team-agent/src/coordinator/mod.rs +3 -3
  38. package/crates/team-agent/src/coordinator/orphan.rs +22 -10
  39. package/crates/team-agent/src/coordinator/steps/abnormal.rs +51 -56
  40. package/crates/team-agent/src/coordinator/tests/abnormal.rs +55 -19
  41. package/crates/team-agent/src/coordinator/tests/basics.rs +179 -41
  42. package/crates/team-agent/src/coordinator/tests/daemon.rs +53 -13
  43. package/crates/team-agent/src/coordinator/tests/health_sync.rs +78 -19
  44. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +61 -11
  45. package/crates/team-agent/src/coordinator/tests/mod.rs +33 -39
  46. package/crates/team-agent/src/coordinator/tests/spine.rs +52 -12
  47. package/crates/team-agent/src/coordinator/tests/takeover.rs +73 -15
  48. package/crates/team-agent/src/coordinator/tests/tick_core.rs +50 -15
  49. package/crates/team-agent/src/coordinator/tests/watch.rs +74 -20
  50. package/crates/team-agent/src/db/message_store.rs +138 -30
  51. package/crates/team-agent/src/db/migration.rs +249 -61
  52. package/crates/team-agent/src/db/schema.rs +303 -82
  53. package/crates/team-agent/src/diagnose/comms.rs +9 -2
  54. package/crates/team-agent/src/diagnose/mod.rs +1 -3
  55. package/crates/team-agent/src/diagnose/orphans.rs +79 -61
  56. package/crates/team-agent/src/event_log.rs +70 -16
  57. package/crates/team-agent/src/layout/manager.rs +15 -4
  58. package/crates/team-agent/src/layout/mod.rs +4 -4
  59. package/crates/team-agent/src/layout/overlay.rs +10 -3
  60. package/crates/team-agent/src/layout/placement.rs +5 -1
  61. package/crates/team-agent/src/layout/recovery.rs +4 -2
  62. package/crates/team-agent/src/layout/runtime_sessions.rs +7 -7
  63. package/crates/team-agent/src/layout/sessions.rs +17 -9
  64. package/crates/team-agent/src/layout/tmux_endpoint.rs +1 -1
  65. package/crates/team-agent/src/layout/worker_env.rs +87 -19
  66. package/crates/team-agent/src/leader/helpers.rs +7 -1
  67. package/crates/team-agent/src/leader/lease.rs +199 -89
  68. package/crates/team-agent/src/leader/owner_bind.rs +55 -22
  69. package/crates/team-agent/src/leader/provider_attribution.rs +25 -6
  70. package/crates/team-agent/src/leader/rediscover/tests.rs +88 -24
  71. package/crates/team-agent/src/leader/rediscover.rs +74 -25
  72. package/crates/team-agent/src/leader/registry.rs +1 -1
  73. package/crates/team-agent/src/leader/start.rs +75 -54
  74. package/crates/team-agent/src/leader/takeover.rs +46 -11
  75. package/crates/team-agent/src/leader/tests/basics.rs +320 -167
  76. package/crates/team-agent/src/leader/tests/byte_findings.rs +361 -219
  77. package/crates/team-agent/src/leader/tests/identity.rs +428 -356
  78. package/crates/team-agent/src/leader/tests/idle.rs +285 -254
  79. package/crates/team-agent/src/leader/tests/lease_api.rs +338 -274
  80. package/crates/team-agent/src/leader/tests/lease_claim.rs +643 -593
  81. package/crates/team-agent/src/leader/tests/mod.rs +115 -99
  82. package/crates/team-agent/src/leader/tests/rediscover.rs +74 -22
  83. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +237 -211
  84. package/crates/team-agent/src/lib.rs +4 -4
  85. package/crates/team-agent/src/lifecycle/display.rs +7 -3
  86. package/crates/team-agent/src/lifecycle/launch.rs +55 -15
  87. package/crates/team-agent/src/lifecycle/mod.rs +9 -1
  88. package/crates/team-agent/src/lifecycle/profile_launch.rs +77 -34
  89. package/crates/team-agent/src/lifecycle/profile_smoke.rs +3 -1
  90. package/crates/team-agent/src/lifecycle/restart/agent.rs +1 -6
  91. package/crates/team-agent/src/lifecycle/restart/common.rs +6 -2
  92. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +1 -4
  93. package/crates/team-agent/src/lifecycle/restart/preflight.rs +6 -5
  94. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +26 -22
  95. package/crates/team-agent/src/lifecycle/restart/remove.rs +45 -35
  96. package/crates/team-agent/src/lifecycle/restart/team_state.rs +63 -17
  97. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +251 -84
  98. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +198 -48
  99. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +2 -1
  100. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +152 -32
  101. package/crates/team-agent/src/lifecycle/tests/phase_b_contracts.rs +1 -5
  102. package/crates/team-agent/src/lifecycle/tests/phase_golden.rs +3 -0
  103. package/crates/team-agent/src/lifecycle/tests.rs +2 -2
  104. package/crates/team-agent/src/main.rs +4 -4
  105. package/crates/team-agent/src/mcp_server/lifecycle_tools/agent_ops.rs +47 -20
  106. package/crates/team-agent/src/mcp_server/mod.rs +11 -2
  107. package/crates/team-agent/src/mcp_server/types.rs +14 -3
  108. package/crates/team-agent/src/mcp_server/wire.rs +230 -68
  109. package/crates/team-agent/src/messaging/delivery.rs +20 -18
  110. package/crates/team-agent/src/messaging/helpers.rs +25 -4
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +4 -5
  112. package/crates/team-agent/src/messaging/mod.rs +2 -3
  113. package/crates/team-agent/src/messaging/selftest.rs +46 -26
  114. package/crates/team-agent/src/messaging/tests/main_preserved.rs +47 -12
  115. package/crates/team-agent/src/messaging/tests/runtime.rs +57 -22
  116. package/crates/team-agent/src/messaging/tests/spine.rs +154 -40
  117. package/crates/team-agent/src/messaging/tests/wave2.rs +31 -32
  118. package/crates/team-agent/src/messaging/trust.rs +25 -2
  119. package/crates/team-agent/src/messaging/watchers.rs +37 -9
  120. package/crates/team-agent/src/model/enums.rs +65 -16
  121. package/crates/team-agent/src/model/ids.rs +12 -3
  122. package/crates/team-agent/src/model/paths.rs +28 -7
  123. package/crates/team-agent/src/model/permissions.rs +176 -33
  124. package/crates/team-agent/src/model/routing.rs +66 -20
  125. package/crates/team-agent/src/model/spec.rs +365 -69
  126. package/crates/team-agent/src/model/task_graph.rs +36 -9
  127. package/crates/team-agent/src/model/yaml/tests.rs +24 -6
  128. package/crates/team-agent/src/model/yaml.rs +7 -6
  129. package/crates/team-agent/src/packaging/install.rs +23 -9
  130. package/crates/team-agent/src/packaging/migrate.rs +5 -7
  131. package/crates/team-agent/src/packaging/mod.rs +9 -1
  132. package/crates/team-agent/src/packaging/repair.rs +13 -6
  133. package/crates/team-agent/src/packaging/tests.rs +63 -16
  134. package/crates/team-agent/src/packaging/types.rs +22 -7
  135. package/crates/team-agent/src/platform/argv.rs +4 -1
  136. package/crates/team-agent/src/platform/file_lock.rs +22 -8
  137. package/crates/team-agent/src/platform/process.rs +54 -24
  138. package/crates/team-agent/src/provider/adapters/claude.rs +1 -3
  139. package/crates/team-agent/src/provider/approvals/parsing.rs +134 -26
  140. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +13 -3
  141. package/crates/team-agent/src/provider/classify.rs +127 -42
  142. package/crates/team-agent/src/provider/faults.rs +17 -5
  143. package/crates/team-agent/src/provider/helpers.rs +6 -5
  144. package/crates/team-agent/src/provider/startup_prompt.rs +75 -27
  145. package/crates/team-agent/src/state/persist.rs +28 -0
  146. package/crates/team-agent/src/state/repository.rs +8 -3
  147. package/crates/team-agent/src/tmux_backend/tests.rs +1637 -1398
  148. package/crates/team-agent/src/tmux_backend.rs +80 -44
  149. package/crates/team-agent/src/topology.rs +40 -20
  150. package/crates/team-agent/src/transport/test_support.rs +20 -23
  151. package/crates/team-agent/src/transport/tests/behavior.rs +292 -293
  152. package/crates/team-agent/src/transport/tests/mod.rs +178 -187
  153. package/crates/team-agent/src/transport/tests/wire.rs +561 -525
  154. package/crates/team-agent/src/transport.rs +12 -17
  155. package/crates/team-agent/src/transport_factory.rs +29 -14
  156. package/package.json +4 -4
@@ -36,8 +36,8 @@ use crate::transport::{
36
36
  tmux_query_argv, tmux_send_keys_argv, tmux_spawn_argv, AttachOutcome, BackendKind,
37
37
  CaptureRange, CapturedText, InjectPayload, InjectReport, InjectStage, InjectVerification, Key,
38
38
  PaneField, PaneId, PaneInfo, PaneMode, SessionName, SetEnvOutcome, SpawnResult,
39
- SubmitAttemptObservation, SubmitObserver, SubmitVerification, Target, Transport, TransportError,
40
- TurnVerification, WindowName,
39
+ SubmitAttemptObservation, SubmitObserver, SubmitVerification, Target, Transport,
40
+ TransportError, TurnVerification, WindowName,
41
41
  };
42
42
 
43
43
  /// Result of running an external command — the typed output of the OS edge.
@@ -267,7 +267,9 @@ impl TmuxBackend {
267
267
  } else if let Some(path) = socket_path_for_name(endpoint) {
268
268
  Self {
269
269
  runner: Box::new(RealCommandRunner),
270
- socket: Some(TmuxSocketEndpoint::Path(path.to_string_lossy().into_owned())),
270
+ socket: Some(TmuxSocketEndpoint::Path(
271
+ path.to_string_lossy().into_owned(),
272
+ )),
271
273
  event_workspace: None,
272
274
  }
273
275
  } else {
@@ -315,7 +317,9 @@ impl TmuxBackend {
315
317
  } else if let Some(path) = socket_path_for_name(endpoint) {
316
318
  Self {
317
319
  runner,
318
- socket: Some(TmuxSocketEndpoint::Path(path.to_string_lossy().into_owned())),
320
+ socket: Some(TmuxSocketEndpoint::Path(
321
+ path.to_string_lossy().into_owned(),
322
+ )),
319
323
  event_workspace: None,
320
324
  }
321
325
  } else {
@@ -589,7 +593,11 @@ pub(crate) fn attach_command_for_runtime_state_or_workspace(
589
593
  if let Some((endpoint, _source)) = runtime_tmux_endpoint_from_state(state) {
590
594
  let display = endpoint.to_string();
591
595
  // Distinguish absolute path (`-S <path>`) from short socket name (`-L <name>`).
592
- let flag = if Path::new(endpoint).is_absolute() { "-S" } else { "-L" };
596
+ let flag = if Path::new(endpoint).is_absolute() {
597
+ "-S"
598
+ } else {
599
+ "-L"
600
+ };
593
601
  return Some(format!(
594
602
  "tmux {flag} {display} attach -t {}:{}",
595
603
  session_name.as_str(),
@@ -626,7 +634,9 @@ pub(crate) fn attach_commands_for_windows<'a>(
626
634
  ) -> Vec<String> {
627
635
  window_names
628
636
  .into_iter()
629
- .filter_map(|window_name| attach_command_for_workspace(workspace, session_name, window_name))
637
+ .filter_map(|window_name| {
638
+ attach_command_for_workspace(workspace, session_name, window_name)
639
+ })
630
640
  .collect()
631
641
  }
632
642
 
@@ -1013,7 +1023,9 @@ fn inject_verification_for_payload(payload: &InjectPayload) -> InjectVerificatio
1013
1023
  {
1014
1024
  InjectVerification::CaptureContainsToken
1015
1025
  }
1016
- InjectPayload::Text(_) | InjectPayload::TextSkipConsumptionPoll(_) => InjectVerification::NoToken,
1026
+ InjectPayload::Text(_) | InjectPayload::TextSkipConsumptionPoll(_) => {
1027
+ InjectVerification::NoToken
1028
+ }
1017
1029
  }
1018
1030
  }
1019
1031
 
@@ -1237,7 +1249,10 @@ pub(crate) fn pasted_prompt_match(text: &str) -> Option<(&'static str, u32)> {
1237
1249
  return None;
1238
1250
  };
1239
1251
  // Distance from the bottom of the tail in non-empty lines.
1240
- let non_empty: Vec<&str> = text.lines().filter(|line| !line.trim().is_empty()).collect();
1252
+ let non_empty: Vec<&str> = text
1253
+ .lines()
1254
+ .filter(|line| !line.trim().is_empty())
1255
+ .collect();
1241
1256
  let mut from_bottom: u32 = 0;
1242
1257
  for line in non_empty.iter().rev() {
1243
1258
  if line.to_ascii_lowercase().contains(lit) {
@@ -1884,6 +1899,14 @@ impl Transport for TmuxBackend {
1884
1899
  submit: Key,
1885
1900
  bracketed: bool,
1886
1901
  ) -> Result<InjectReport, TransportError> {
1902
+ // Trait entry delegates to inject_with_submit_observer with no
1903
+ // observer. Populated InjectReport fields: stage_reached,
1904
+ // inject_verification, submit_verification, turn_verification,
1905
+ // attempts, submit_diagnostics — the 0.5.43 debt-sweep
1906
+ // governance guard (debt_sweep_0543_contract.rs::
1907
+ // coordinator_debug_eprintlns_are_deleted_but_inject_report_shape_remains)
1908
+ // scans THIS function body for those field names so debug-
1909
+ // print cleanup can't silently drop InjectReport surface area.
1887
1910
  self.inject_with_submit_observer(target, payload, submit, bracketed, None)
1888
1911
  }
1889
1912
 
@@ -1942,26 +1965,27 @@ impl Transport for TmuxBackend {
1942
1965
  size_based.max(2000)
1943
1966
  };
1944
1967
  let poll_start = std::time::Instant::now();
1945
- token_visible_for_report =
1946
- if payload_token_marker(payload).is_some() {
1947
- let mut visible = false;
1948
- while poll_start.elapsed().as_millis() < token_poll_timeout_ms as u128 {
1949
- match token_visible_in_capture(self, target, payload) {
1950
- Ok(Some(true)) => { visible = true; break; }
1951
- Err(_) => break, // tmux unavailable, skip poll
1952
- _ => {}
1968
+ token_visible_for_report = if payload_token_marker(payload).is_some() {
1969
+ let mut visible = false;
1970
+ while poll_start.elapsed().as_millis() < token_poll_timeout_ms as u128 {
1971
+ match token_visible_in_capture(self, target, payload) {
1972
+ Ok(Some(true)) => {
1973
+ visible = true;
1974
+ break;
1953
1975
  }
1954
- std::thread::sleep(Duration::from_millis(50));
1976
+ Err(_) => break, // tmux unavailable, skip poll
1977
+ _ => {}
1955
1978
  }
1956
- Some(visible)
1957
- } else {
1958
- None
1959
- };
1979
+ std::thread::sleep(Duration::from_millis(50));
1980
+ }
1981
+ Some(visible)
1982
+ } else {
1983
+ None
1984
+ };
1960
1985
 
1961
1986
  // Phase 2: submit_and_verify — unified Escape+Enter+poll loop.
1962
- let use_escape = bracketed
1963
- && payload.text().is_some()
1964
- && matches!(submit, Key::Enter);
1987
+ let use_escape =
1988
+ bracketed && payload.text().is_some() && matches!(submit, Key::Enter);
1965
1989
  let escape_argv = if use_escape {
1966
1990
  Some(tmux_send_keys_argv(&pane, &[Key::Escape]))
1967
1991
  } else {
@@ -2001,12 +2025,19 @@ impl Transport for TmuxBackend {
2001
2025
 
2002
2026
  let poll_consumption = !payload.skip_consumption_poll();
2003
2027
  if !poll_consumption {
2004
- if self.run_inject_stage(&submit_argv, InjectStage::Submit).is_ok() {
2028
+ if self
2029
+ .run_inject_stage(&submit_argv, InjectStage::Submit)
2030
+ .is_ok()
2031
+ {
2005
2032
  notify_submit_observer(observer);
2006
2033
  consumption_attempts = 1;
2007
2034
  }
2008
2035
  }
2009
- let submit_attempt_limit = if poll_consumption { max_submit_attempts } else { 0 };
2036
+ let submit_attempt_limit = if poll_consumption {
2037
+ max_submit_attempts
2038
+ } else {
2039
+ 0
2040
+ };
2010
2041
 
2011
2042
  for attempt in 0..submit_attempt_limit {
2012
2043
  let attempt_index = attempt + 1;
@@ -2049,7 +2080,10 @@ impl Transport for TmuxBackend {
2049
2080
  // Enter — send-keys failure is degraded (tmux may not have
2050
2081
  // the pane in sim/test env). Break to consumed=None path
2051
2082
  // which returns EnterSentWithoutPlaceholderCheck.
2052
- if self.run_inject_stage(&submit_argv, InjectStage::Submit).is_err() {
2083
+ if self
2084
+ .run_inject_stage(&submit_argv, InjectStage::Submit)
2085
+ .is_err()
2086
+ {
2053
2087
  consumed = None;
2054
2088
  break;
2055
2089
  }
@@ -2060,8 +2094,7 @@ impl Transport for TmuxBackend {
2060
2094
  // visible after Enter for no-echo panes).
2061
2095
  if attempt == 0 && matches!(token_visible_for_report, Some(false)) {
2062
2096
  token_visible_for_report =
2063
- post_submit_token_visible(self, target, payload)
2064
- .unwrap_or(Some(false));
2097
+ post_submit_token_visible(self, target, payload).unwrap_or(Some(false));
2065
2098
  }
2066
2099
 
2067
2100
  // Poll: token disappeared from bottom 15 lines = consumed.
@@ -2088,7 +2121,10 @@ impl Transport for TmuxBackend {
2088
2121
  break;
2089
2122
  }
2090
2123
  }
2091
- Err(_) => { capture_failed = true; break; }
2124
+ Err(_) => {
2125
+ capture_failed = true;
2126
+ break;
2127
+ }
2092
2128
  }
2093
2129
  }
2094
2130
  if capture_failed {
@@ -2106,13 +2142,17 @@ impl Transport for TmuxBackend {
2106
2142
  }
2107
2143
  }
2108
2144
 
2145
+ // 0.5.43 debt-sweep (§6.2): three unconditional
2146
+ // `eprintln!` submit-consumption debug lines removed.
2147
+ // The decision they narrated is already captured in
2148
+ // `InjectReport.submit_diagnostics` /
2149
+ // `submit_verification`; the prints only spammed
2150
+ // coordinator.log without additive signal. Behavior
2151
+ // is byte-identical.
2109
2152
  let submit_verification = match consumed {
2110
2153
  Some(true) => SubmitVerification::EnterSentWithoutPlaceholderCheck,
2111
2154
  Some(false) => {
2112
2155
  if any_attempt_matched {
2113
- eprintln!(
2114
- "team-agent submit consumption: consumed=false any_attempt_matched=true -> verified"
2115
- );
2116
2156
  SubmitVerification::EnterSentWithoutPlaceholderCheck
2117
2157
  } else {
2118
2158
  match self.capture(target, CaptureRange::Tail(15)) {
@@ -2124,21 +2164,13 @@ impl Transport for TmuxBackend {
2124
2164
  inject_start.elapsed().as_millis() as u64,
2125
2165
  ));
2126
2166
  let busy = provider_busy_signal_in_tail(&cap.text);
2127
- eprintln!(
2128
- "team-agent submit consumption fallback: consumed=false any_attempt_matched=false busy_state={busy}"
2129
- );
2130
2167
  if busy {
2131
2168
  SubmitVerification::EnterSentWithoutPlaceholderCheck
2132
2169
  } else {
2133
2170
  SubmitVerification::SubmitConsumptionUnverified
2134
2171
  }
2135
2172
  }
2136
- Err(err) => {
2137
- eprintln!(
2138
- "team-agent submit consumption fallback: consumed=false busy_capture_error={err}"
2139
- );
2140
- SubmitVerification::SubmitConsumptionUnverified
2141
- }
2173
+ Err(_) => SubmitVerification::SubmitConsumptionUnverified,
2142
2174
  }
2143
2175
  }
2144
2176
  }
@@ -2154,7 +2186,9 @@ impl Transport for TmuxBackend {
2154
2186
  submit_verification,
2155
2187
  turn_verification: match payload {
2156
2188
  InjectPayload::Empty => TurnVerification::NotRequired,
2157
- InjectPayload::Text(_) | InjectPayload::TextSkipConsumptionPoll(_) => TurnVerification::NotYetObserved,
2189
+ InjectPayload::Text(_) | InjectPayload::TextSkipConsumptionPoll(_) => {
2190
+ TurnVerification::NotYetObserved
2191
+ }
2158
2192
  },
2159
2193
  attempts: consumption_attempts,
2160
2194
  submit_diagnostics: Some(crate::transport::SubmitDiagnostics {
@@ -2175,7 +2209,9 @@ impl Transport for TmuxBackend {
2175
2209
  submit_verification: submit_verification_for_key(submit),
2176
2210
  turn_verification: match payload {
2177
2211
  InjectPayload::Empty => TurnVerification::NotRequired,
2178
- InjectPayload::Text(_) | InjectPayload::TextSkipConsumptionPoll(_) => TurnVerification::NotYetObserved,
2212
+ InjectPayload::Text(_) | InjectPayload::TextSkipConsumptionPoll(_) => {
2213
+ TurnVerification::NotYetObserved
2214
+ }
2179
2215
  },
2180
2216
  attempts: 1,
2181
2217
  // E50 PR-1: Empty payload / non-Text fallthrough path — no submit
@@ -101,21 +101,19 @@ pub fn endpoint_convergence_decision(
101
101
  let mut converge_reason = "old_endpoint_dead";
102
102
  for old_endpoint in stale_endpoints {
103
103
  match endpoint_server_alive(&old_endpoint) {
104
- Some(true) => {
105
- match old_endpoint_team_liveness(state, team_key, &old_endpoint) {
106
- OldEndpointTeamLiveness::TeamLive { reason } => {
107
- return EndpointConvergenceDecision::RefuseLiveOldEndpoint {
108
- old_endpoint,
109
- new_endpoint: candidate_endpoint.to_string(),
110
- reason,
111
- };
112
- }
113
- OldEndpointTeamLiveness::TeamAbsent { reason } => {
114
- converge_reason = reason;
115
- }
116
- OldEndpointTeamLiveness::Unknown => return EndpointConvergenceDecision::Unknown,
104
+ Some(true) => match old_endpoint_team_liveness(state, team_key, &old_endpoint) {
105
+ OldEndpointTeamLiveness::TeamLive { reason } => {
106
+ return EndpointConvergenceDecision::RefuseLiveOldEndpoint {
107
+ old_endpoint,
108
+ new_endpoint: candidate_endpoint.to_string(),
109
+ reason,
110
+ };
117
111
  }
118
- }
112
+ OldEndpointTeamLiveness::TeamAbsent { reason } => {
113
+ converge_reason = reason;
114
+ }
115
+ OldEndpointTeamLiveness::Unknown => return EndpointConvergenceDecision::Unknown,
116
+ },
119
117
  Some(false) => {}
120
118
  None => return EndpointConvergenceDecision::Unknown,
121
119
  }
@@ -226,8 +224,7 @@ fn leader_receiver_tuple_matches(state: &Value, observed: &PaneInfo) -> bool {
226
224
  return false;
227
225
  }
228
226
  }
229
- if let (Some(expected_pid), Some(observed_pid)) =
230
- (agent_pane_pid(receiver), observed.pane_pid)
227
+ if let (Some(expected_pid), Some(observed_pid)) = (agent_pane_pid(receiver), observed.pane_pid)
231
228
  {
232
229
  if expected_pid != observed_pid {
233
230
  return false;
@@ -238,9 +235,16 @@ fn leader_receiver_tuple_matches(state: &Value, observed: &PaneInfo) -> bool {
238
235
 
239
236
  #[derive(Debug, Clone, PartialEq, Eq)]
240
237
  pub enum WorkerPaneBindingMatch {
241
- LiveSameWorker { agent_id: String },
242
- Stale { agent_id: String, reason: &'static str },
243
- IncompleteLegacy { agent_id: String },
238
+ LiveSameWorker {
239
+ agent_id: String,
240
+ },
241
+ Stale {
242
+ agent_id: String,
243
+ reason: &'static str,
244
+ },
245
+ IncompleteLegacy {
246
+ agent_id: String,
247
+ },
244
248
  NoMatch,
245
249
  }
246
250
 
@@ -453,6 +457,19 @@ fn session_exists_on_endpoint(endpoint: &str, session: &str) -> bool {
453
457
  session_exists_on_endpoint_checked(endpoint, session).unwrap_or(false)
454
458
  }
455
459
 
460
+ pub(crate) fn team_session_ready_on_endpoint(endpoint: &str, session: &str) -> Option<bool> {
461
+ if endpoint.is_empty() || session.is_empty() {
462
+ return None;
463
+ }
464
+ let backend = crate::tmux_backend::TmuxBackend::for_tmux_endpoint(endpoint);
465
+ let targets = backend.list_targets().ok()?;
466
+ Some(
467
+ targets
468
+ .iter()
469
+ .any(|target| target.session.as_str() == session),
470
+ )
471
+ }
472
+
456
473
  fn session_exists_on_endpoint_checked(endpoint: &str, session: &str) -> Option<bool> {
457
474
  crate::tmux_backend::TmuxBackend::for_tmux_endpoint(endpoint)
458
475
  .has_session(&SessionName::new(session.to_string()))
@@ -504,5 +521,8 @@ fn normalize_endpoint(value: &str) -> String {
504
521
  }
505
522
 
506
523
  fn non_empty_str<'a>(value: &'a Value, key: &str) -> Option<&'a str> {
507
- value.get(key).and_then(Value::as_str).filter(|s| !s.is_empty())
524
+ value
525
+ .get(key)
526
+ .and_then(Value::as_str)
527
+ .filter(|s| !s.is_empty())
508
528
  }
@@ -177,7 +177,9 @@ impl OfflineTransport {
177
177
  command: impl Into<String>,
178
178
  ) -> Self {
179
179
  self.with_state(|state| {
180
- state.pane_current_commands.insert(pane_id.into(), command.into());
180
+ state
181
+ .pane_current_commands
182
+ .insert(pane_id.into(), command.into());
181
183
  });
182
184
  self
183
185
  }
@@ -252,7 +254,13 @@ impl OfflineTransport {
252
254
  }
253
255
 
254
256
  pub fn spawn_cwd_records(&self) -> Vec<std::path::PathBuf> {
255
- self.with_state(|state| state.spawns.iter().map(|record| record.cwd.clone()).collect())
257
+ self.with_state(|state| {
258
+ state
259
+ .spawns
260
+ .iter()
261
+ .map(|record| record.cwd.clone())
262
+ .collect()
263
+ })
256
264
  }
257
265
 
258
266
  pub fn pane_title_records(&self) -> Vec<(String, String, String, String)> {
@@ -451,11 +459,7 @@ impl Transport for OfflineTransport {
451
459
  Ok(CapturedText { text, range })
452
460
  }
453
461
 
454
- fn query(
455
- &self,
456
- target: &Target,
457
- field: PaneField,
458
- ) -> Result<Option<String>, TransportError> {
462
+ fn query(&self, target: &Target, field: PaneField) -> Result<Option<String>, TransportError> {
459
463
  self.record("query");
460
464
  if !matches!(field, PaneField::PaneCurrentCommand) {
461
465
  return Ok(None);
@@ -464,9 +468,7 @@ impl Transport for OfflineTransport {
464
468
  Target::Pane(p) => p.as_str().to_string(),
465
469
  Target::SessionWindow { .. } => return Ok(None),
466
470
  };
467
- Ok(self.with_state(|state| {
468
- state.pane_current_commands.get(&pane_id).cloned()
469
- }))
471
+ Ok(self.with_state(|state| state.pane_current_commands.get(&pane_id).cloned()))
470
472
  }
471
473
 
472
474
  fn liveness(&self, pane: &PaneId) -> Result<PaneLiveness, TransportError> {
@@ -509,14 +511,15 @@ impl Transport for OfflineTransport {
509
511
  }))
510
512
  }
511
513
 
512
- fn list_windows(
513
- &self,
514
- _session: &SessionName,
515
- ) -> Result<Vec<WindowName>, TransportError> {
514
+ fn list_windows(&self, _session: &SessionName) -> Result<Vec<WindowName>, TransportError> {
516
515
  Ok(self.with_state(|state| {
517
516
  state.calls.push("list_windows");
518
517
  if state.windows.is_empty() {
519
- state.targets.iter().filter_map(|pane| pane.window_name.clone()).collect()
518
+ state
519
+ .targets
520
+ .iter()
521
+ .filter_map(|pane| pane.window_name.clone())
522
+ .collect()
520
523
  } else {
521
524
  state.windows.clone()
522
525
  }
@@ -568,10 +571,7 @@ impl Transport for OfflineTransport {
568
571
  if let Target::SessionWindow { session, window } = target {
569
572
  state.targets.retain(|pane| {
570
573
  pane.session != *session
571
- || pane
572
- .window_name
573
- .as_ref()
574
- .is_none_or(|name| name != window)
574
+ || pane.window_name.as_ref().is_none_or(|name| name != window)
575
575
  });
576
576
  state.windows.retain(|name| name != window);
577
577
  }
@@ -591,10 +591,7 @@ impl Transport for OfflineTransport {
591
591
  Ok(())
592
592
  }
593
593
 
594
- fn attach_session(
595
- &self,
596
- _session: &SessionName,
597
- ) -> Result<AttachOutcome, TransportError> {
594
+ fn attach_session(&self, _session: &SessionName) -> Result<AttachOutcome, TransportError> {
598
595
  self.record("attach_session");
599
596
  Ok(AttachOutcome::Attached)
600
597
  }