@team-agent/installer 0.5.61 → 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.
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 +6 -5
  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 +0 -9
  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 -42
  49. package/crates/team-agent/src/coordinator/tick.rs +24 -28
  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 -3
  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 -11
  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 -0
  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,846 @@
1
+ //! Real-machine command-file defects, clusters 1-6.
2
+ //!
3
+ //! These are canonical Team Agent CLI flows from
4
+ //! `.team/test-designs/realmachine-product-defects.md`. The harness command files are the contract;
5
+ //! if they fail on a real machine, the Rust framework changes rather than weakening the commands.
6
+
7
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
8
+
9
+ #[path = "../../../tests/support/hermetic.rs"]
10
+ mod hermetic_guard;
11
+ #[allow(dead_code)]
12
+ fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
13
+
14
+ use std::collections::{BTreeMap, HashSet};
15
+ use std::path::{Path, PathBuf};
16
+ use std::process::{Command, Output};
17
+ use std::sync::atomic::{AtomicU64, Ordering};
18
+ use std::sync::Mutex;
19
+
20
+ use serde_json::{json, Value};
21
+ use team_agent::lifecycle::{quick_start_with_transport, QuickStartReport};
22
+ use team_agent::transport::{
23
+ AttachOutcome, BackendKind, CaptureRange, CapturedText, InjectPayload, InjectReport,
24
+ InjectStage, InjectVerification, Key, PaneField, PaneId, PaneInfo, SessionName, SetEnvOutcome,
25
+ SpawnResult, SubmitVerification, Target, Transport, TransportError, TurnVerification,
26
+ WindowName,
27
+ };
28
+
29
+ fn bin() -> &'static str {
30
+ crate::lifecycle::tests::test_binary_path()
31
+ }
32
+
33
+ fn tmp_dir(tag: &str) -> PathBuf {
34
+ static N: AtomicU64 = AtomicU64::new(0);
35
+ let dir = std::env::temp_dir().join(format!(
36
+ "ta-rs-rm-c1-c6-{tag}-{}-{}",
37
+ std::process::id(),
38
+ N.fetch_add(1, Ordering::Relaxed)
39
+ ));
40
+ let _ = std::fs::remove_dir_all(&dir);
41
+ std::fs::create_dir_all(&dir).unwrap();
42
+ std::fs::canonicalize(dir).unwrap()
43
+ }
44
+
45
+ fn run(args: &[&str], cwd: &Path) -> Output {
46
+ Command::new(bin())
47
+ .args(args)
48
+ .current_dir(cwd)
49
+ .output()
50
+ .unwrap()
51
+ }
52
+
53
+ fn stdout_json(out: &Output) -> Value {
54
+ serde_json::from_slice(&out.stdout).unwrap_or_else(|_| {
55
+ panic!(
56
+ "stdout must be JSON; code={:?} stdout={} stderr={}",
57
+ out.status.code(),
58
+ String::from_utf8_lossy(&out.stdout),
59
+ String::from_utf8_lossy(&out.stderr)
60
+ )
61
+ })
62
+ }
63
+
64
+ // OLD seed: flat top-level `agents` / `tasks` (Python parity, pre-Bug-1/2).
65
+ // NEW seed (Bug 1/2 — team-in-team state scope, see
66
+ // tests/team_in_team_state_scope_red.rs): send_message projects the raw state
67
+ // through `project_top_level_view(active_team_key)` which sources agents/tasks
68
+ // from `teams[<key>].*`. Without the nested copy a single-team send refuses
69
+ // `target_not_in_team` BEFORE persistence (no DB schema is ever created → the
70
+ // downstream `select count(*) from messages` query in c4 fails with "no such
71
+ // table"). The seed is widened to carry BOTH the flat keys (so c1/c2/c5/c6
72
+ // helpers that read flat state directly stay working) AND the nested team-scoped
73
+ // copy (so the send/persist path resolves `worker_a` as in-team). The behavior
74
+ // under test (caller --message-id + dedup) is unchanged; this is fixture-only
75
+ // sync to the new state shape.
76
+ fn seed_runtime_state(workspace: &Path) {
77
+ team_agent::state::persist::save_runtime_state(
78
+ workspace,
79
+ &json!({
80
+ "leader": {"id": "leader"},
81
+ "agents": {
82
+ "worker_a": {
83
+ "provider": "fake",
84
+ "role": "Worker A",
85
+ "status": "running"
86
+ }
87
+ },
88
+ "tasks": [],
89
+ "active_team_key": "current",
90
+ "teams": {"current": {
91
+ "leader": {"id": "leader"},
92
+ "agents": {
93
+ "worker_a": {
94
+ "provider": "fake",
95
+ "role": "Worker A",
96
+ "status": "running"
97
+ }
98
+ },
99
+ "tasks": []
100
+ }}
101
+ }),
102
+ )
103
+ .unwrap();
104
+ }
105
+
106
+ #[test]
107
+ #[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
108
+ fn c1_lifecycle_commands_resolve_active_teamdir_from_root_workspace_selector() {
109
+ let fixture = seed_c1_active_team_fixture("c1-lifecycle");
110
+ let mut failures = Vec::new();
111
+ let role_file = fixture.root.join(".team").join("roles").join("worker_b.md");
112
+
113
+ let cases: Vec<(&str, Vec<String>, &[&str])> = vec![
114
+ (
115
+ "CR-007/021/052 restart",
116
+ vec![
117
+ "restart".into(),
118
+ "--workspace".into(),
119
+ fixture.root_str(),
120
+ "--json".into(),
121
+ ],
122
+ &["missing spec for restart", "team.spec.yaml"],
123
+ ),
124
+ (
125
+ "CR-031/055 stop-agent",
126
+ vec![
127
+ "stop-agent".into(),
128
+ "worker_a".into(),
129
+ "--workspace".into(),
130
+ fixture.root_str(),
131
+ "--json".into(),
132
+ ],
133
+ &["missing spec:", "team.spec.yaml"],
134
+ ),
135
+ (
136
+ "CR-033 reset-agent",
137
+ vec![
138
+ "reset-agent".into(),
139
+ "worker_a".into(),
140
+ "--discard-session".into(),
141
+ "--workspace".into(),
142
+ fixture.root_str(),
143
+ "--json".into(),
144
+ ],
145
+ &["missing spec:", "team.spec.yaml"],
146
+ ),
147
+ (
148
+ "CR-030 add-agent",
149
+ vec![
150
+ "add-agent".into(),
151
+ "worker_b".into(),
152
+ "--role-file".into(),
153
+ role_file.to_string_lossy().to_string(),
154
+ "--workspace".into(),
155
+ fixture.root_str(),
156
+ "--json".into(),
157
+ ],
158
+ &["missing TEAM.md", "spec compile failed"],
159
+ ),
160
+ (
161
+ "CR-030 remove-agent",
162
+ vec![
163
+ "remove-agent".into(),
164
+ "worker_a".into(),
165
+ "--from-spec".into(),
166
+ "--confirm".into(),
167
+ "--force".into(),
168
+ "--workspace".into(),
169
+ fixture.root_str(),
170
+ "--json".into(),
171
+ ],
172
+ &["missing spec:", "team.spec.yaml"],
173
+ ),
174
+ ];
175
+
176
+ for (label, args, forbidden) in cases {
177
+ let refs = args.iter().map(String::as_str).collect::<Vec<_>>();
178
+ let out = run(&refs, &fixture.root);
179
+ let stdout = String::from_utf8_lossy(&out.stdout);
180
+ let stderr = String::from_utf8_lossy(&out.stderr);
181
+ let combined = format!("{stdout}\n{stderr}");
182
+ if out.status.code() != Some(0) || forbidden.iter().any(|needle| combined.contains(needle))
183
+ {
184
+ failures.push(format!(
185
+ "{label}: expected resolve_active_team(root)->teamdir/spec_path, got code={:?} stdout={stdout:?} stderr={stderr:?}",
186
+ out.status.code()
187
+ ));
188
+ }
189
+ }
190
+ assert!(
191
+ failures.is_empty(),
192
+ "Cluster 1 resolve_active_team contract: after `quick-start teamdir`, lifecycle commands using --workspace <root> must select state.spec_path/team_dir, not root TEAM.md/team.spec.yaml:\n{}",
193
+ failures.join("\n")
194
+ );
195
+ }
196
+
197
+ #[test]
198
+ #[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
199
+ fn c1_runtime_commands_resolve_active_team_for_status_send_and_collect() {
200
+ let fixture = seed_c1_active_team_fixture("c1-runtime");
201
+ let mut failures = Vec::new();
202
+
203
+ let status = run(
204
+ &[
205
+ "status",
206
+ "--workspace",
207
+ fixture.teamdir.to_str().unwrap(),
208
+ "--json",
209
+ ],
210
+ &fixture.root,
211
+ );
212
+ let status_json = stdout_json(&status);
213
+ if status.status.code() != Some(0)
214
+ || status_json
215
+ .get("agents")
216
+ .and_then(Value::as_object)
217
+ .is_none_or(|agents| !agents.contains_key("worker_a"))
218
+ {
219
+ failures.push(format!(
220
+ "status should resolve --workspace <teamdir> to the active run workspace/team projection; code={:?} json={status_json}",
221
+ status.status.code()
222
+ ));
223
+ }
224
+
225
+ let send = run(
226
+ &[
227
+ "send",
228
+ "worker_a",
229
+ "selector ping",
230
+ "--workspace",
231
+ fixture.teamdir.to_str().unwrap(),
232
+ "--json",
233
+ ],
234
+ &fixture.root,
235
+ );
236
+ let send_json = stdout_json(&send);
237
+ if send.status.code() != Some(0)
238
+ || send_json["ok"] != json!(true)
239
+ || send_json["reason"] == json!("target_not_in_team")
240
+ {
241
+ failures.push(format!(
242
+ "send should resolve --workspace <teamdir> to the active run workspace and route against worker_a; code={:?} json={send_json}",
243
+ send.status.code()
244
+ ));
245
+ }
246
+
247
+ let collect = run(
248
+ &[
249
+ "collect",
250
+ "--workspace",
251
+ fixture.teamdir.to_str().unwrap(),
252
+ "--json",
253
+ ],
254
+ &fixture.root,
255
+ );
256
+ let collect_stdout = String::from_utf8_lossy(&collect.stdout);
257
+ let collect_stderr = String::from_utf8_lossy(&collect.stderr);
258
+ if collect.status.code() != Some(0)
259
+ || collect_stdout.contains("Cannot read")
260
+ || collect_stdout.contains("/team.spec.yaml")
261
+ {
262
+ failures.push(format!(
263
+ "collect should reuse resolve_active_team root->teamdir spec selection; code={:?} stdout={collect_stdout:?} stderr={collect_stderr:?}",
264
+ collect.status.code()
265
+ ));
266
+ }
267
+
268
+ assert!(
269
+ failures.is_empty(),
270
+ "Cluster 1 resolve_active_team contract: status/send/collect using --workspace <teamdir> must operate on the active run workspace projection/spec, not an empty teamdir-local runtime:\n{}",
271
+ failures.join("\n")
272
+ );
273
+ }
274
+
275
+ #[test]
276
+ #[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
277
+ fn c2_stop_team_level_command_exists_and_keeps_state_while_stopping_runtime() {
278
+ let ws = tmp_dir("c2-stop");
279
+ seed_runtime_state(&ws);
280
+
281
+ let out = run(
282
+ &["stop", "--workspace", ws.to_str().unwrap(), "--json"],
283
+ &ws,
284
+ );
285
+ let err = String::from_utf8_lossy(&out.stderr);
286
+ assert!(
287
+ !err.contains("invalid choice: 'stop'"),
288
+ "CR-005: canonical `team-agent stop --workspace <ws> --json` must be a registered team-level stop verb, not argparse invalid-choice; stderr={err:?}"
289
+ );
290
+ assert_eq!(
291
+ out.status.code(),
292
+ Some(0),
293
+ "CR-005: stop should stop the runtime while keeping state on a valid workspace; stdout={} stderr={}",
294
+ String::from_utf8_lossy(&out.stdout),
295
+ err
296
+ );
297
+ let value = stdout_json(&out);
298
+ assert_eq!(
299
+ value["ok"],
300
+ json!(true),
301
+ "stop must return a successful JSON envelope"
302
+ );
303
+ assert!(
304
+ team_agent::state::persist::runtime_state_path(&ws).exists(),
305
+ "stop keeps runtime state for later restart"
306
+ );
307
+ }
308
+
309
+ #[test]
310
+ #[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
311
+ fn c2_canonical_verbs_expose_help_instead_of_invalid_choice() {
312
+ let cwd = tmp_dir("c2-help-verbs");
313
+ let mut failures = Vec::new();
314
+ for verb in ["start", "restart-agent", "purge-agent"] {
315
+ let out = run(&[verb, "--help"], &cwd);
316
+ let stdout = String::from_utf8_lossy(&out.stdout);
317
+ let stderr = String::from_utf8_lossy(&out.stderr);
318
+ if out.status.code() != Some(0)
319
+ || stderr.contains("invalid choice:")
320
+ || !(stdout.contains("usage") || stderr.contains("usage"))
321
+ {
322
+ failures.push(format!(
323
+ "{verb}: code={:?} stdout={stdout:?} stderr={stderr:?}",
324
+ out.status.code()
325
+ ));
326
+ }
327
+ }
328
+ assert!(
329
+ failures.is_empty(),
330
+ "CR-030/032/035/063: canonical lifecycle verbs must be registered and expose help, not invalid-choice:\n{}",
331
+ failures.join("\n")
332
+ );
333
+ }
334
+
335
+ #[test]
336
+ #[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
337
+ fn c2_quick_start_help_is_zero_token_help_only_and_does_not_compile_workspace() {
338
+ let cwd = tmp_dir("c2-quick-help");
339
+ let out = run(&["quick-start", "--help"], &cwd);
340
+ let stdout = String::from_utf8_lossy(&out.stdout);
341
+ let stderr = String::from_utf8_lossy(&out.stderr);
342
+ let combined = format!("{stdout}\n{stderr}");
343
+
344
+ let mut failures = Vec::new();
345
+ if out.status.code() != Some(0) {
346
+ failures.push(format!(
347
+ "help exited {:?}, expected 0; stdout={stdout:?} stderr={stderr:?}",
348
+ out.status.code()
349
+ ));
350
+ }
351
+ if !(combined.contains("usage") && combined.contains("quick-start")) {
352
+ failures.push(format!(
353
+ "help output did not include quick-start usage text; got {combined:?}"
354
+ ));
355
+ }
356
+ if combined.contains("spec compile failed") || combined.contains("missing TEAM.md") {
357
+ failures.push(format!(
358
+ "help path compiled/validated the current workspace; got {combined:?}"
359
+ ));
360
+ }
361
+ if cwd.join("team.spec.yaml").exists() || cwd.join(".team").exists() {
362
+ failures.push(format!(
363
+ "help path created artifacts: team.spec.yaml={} .team={}",
364
+ cwd.join("team.spec.yaml").exists(),
365
+ cwd.join(".team").exists()
366
+ ));
367
+ }
368
+ assert!(
369
+ failures.is_empty(),
370
+ "CR-047: `team-agent quick-start --help` must be local help-only, zero-token, zero-pollution:\n{}",
371
+ failures.join("\n")
372
+ );
373
+ }
374
+
375
+ #[test]
376
+ #[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
377
+ fn c3_repeated_quick_start_uses_requested_team_identity_for_session_names() {
378
+ let root = tmp_dir("c3-multiteam");
379
+ seed_healthy_coordinator(&root);
380
+ let teamdir = write_fake_team_dir(&root, "command-harness-template");
381
+ let transport = SessionRecordingTransport::default();
382
+
383
+ let first = quick_start_with_transport(
384
+ &teamdir,
385
+ Some("command-harness-parent"),
386
+ true,
387
+ Some("parent"),
388
+ &transport,
389
+ );
390
+ let second = quick_start_with_transport(
391
+ &teamdir,
392
+ Some("command-harness-child"),
393
+ true,
394
+ Some("child"),
395
+ &transport,
396
+ );
397
+
398
+ assert!(
399
+ first.is_ok(),
400
+ "CR-040/042 fixture sanity: first quick-start should succeed; got {first:?}"
401
+ );
402
+ assert!(
403
+ second.is_ok(),
404
+ "CR-040/042: second quick-start from the same template with --name/--team-id child must not collide on the template-derived tmux session; got {second:?}"
405
+ );
406
+ let sessions = transport.spawned_sessions();
407
+ assert_eq!(
408
+ sessions.len(),
409
+ 2,
410
+ "two requested teams should produce two spawned sessions"
411
+ );
412
+ assert_ne!(
413
+ sessions[0], sessions[1],
414
+ "CR-040/042: session names must derive from requested team identity, not reused template name"
415
+ );
416
+ assert!(
417
+ sessions.iter().any(|s| s.contains("parent"))
418
+ && sessions.iter().any(|s| s.contains("child")),
419
+ "session names should carry requested team ids/names; got {sessions:?}"
420
+ );
421
+ let report = second.unwrap();
422
+ match report {
423
+ QuickStartReport::Ready { session_name, .. } => assert!(
424
+ session_name.as_str().contains("child"),
425
+ "reported child session should carry requested child identity; got {session_name}"
426
+ ),
427
+ other => panic!("second quick-start should reach Ready; got {other:?}"),
428
+ }
429
+ }
430
+
431
+ #[test]
432
+ #[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
433
+ fn c4_send_generates_distinct_ids_for_repeated_payloads() {
434
+ let ws = tmp_dir("c4-message-id");
435
+ seed_runtime_state(&ws);
436
+
437
+ let first = run(
438
+ &[
439
+ "send",
440
+ "worker_a",
441
+ "duplicate once",
442
+ "--workspace",
443
+ ws.to_str().unwrap(),
444
+ "--json",
445
+ ],
446
+ &ws,
447
+ );
448
+ let second = run(
449
+ &[
450
+ "send",
451
+ "worker_a",
452
+ "duplicate once",
453
+ "--workspace",
454
+ ws.to_str().unwrap(),
455
+ "--json",
456
+ ],
457
+ &ws,
458
+ );
459
+ let first_json = stdout_json(&first);
460
+ let second_json = stdout_json(&second);
461
+
462
+ let mut failures = Vec::new();
463
+ let first_id = first_json.get("message_id").and_then(Value::as_str);
464
+ let second_id = second_json.get("message_id").and_then(Value::as_str);
465
+ if first_json["ok"] != json!(true)
466
+ || second_json["ok"] != json!(true)
467
+ || first_id.is_none()
468
+ || second_id.is_none()
469
+ || first_id == second_id
470
+ {
471
+ failures.push(format!(
472
+ "canonical sends must generate distinct correlation ids; first={} second={}",
473
+ first_json, second_json
474
+ ));
475
+ }
476
+ let db = ws.join(".team").join("runtime").join("team.db");
477
+ let conn = rusqlite::Connection::open(db).unwrap();
478
+ let total: i64 = conn
479
+ .query_row("select count(*) from messages", [], |row| row.get(0))
480
+ .unwrap();
481
+ let content_rows: i64 = conn
482
+ .query_row(
483
+ "select count(*) from messages where content = 'duplicate once'",
484
+ [],
485
+ |row| row.get(0),
486
+ )
487
+ .unwrap();
488
+ if total != 2 || content_rows != 2 {
489
+ failures.push(format!(
490
+ "message store should contain both generated-id rows with unchanged content; total={total} content_rows={content_rows}"
491
+ ));
492
+ }
493
+ assert!(
494
+ failures.is_empty(),
495
+ "CR-015/054: caller-supplied --message-id must be metadata and duplicate key:\n{}",
496
+ failures.join("\n")
497
+ );
498
+ }
499
+
500
+ #[test]
501
+ #[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
502
+ fn c5_send_without_target_reports_routing_ambiguous_not_target_not_in_team() {
503
+ let ws = tmp_dir("c5-no-target");
504
+ seed_runtime_state(&ws);
505
+
506
+ let out = run(
507
+ &[
508
+ "send",
509
+ "fix the build",
510
+ "--workspace",
511
+ ws.to_str().unwrap(),
512
+ "--json",
513
+ ],
514
+ &ws,
515
+ );
516
+ let value = stdout_json(&out);
517
+ assert_eq!(
518
+ value["reason"],
519
+ json!("routing_ambiguous"),
520
+ "CR-061/N27: no-target send should reject as routing_ambiguous unless a default route exists; got {value}"
521
+ );
522
+ assert_eq!(
523
+ value["content"],
524
+ json!("fix the build"),
525
+ "CR-061: the prompt text is content, not an agent target; got {value}"
526
+ );
527
+ assert!(
528
+ value.get("agent_id").is_none() || value["agent_id"].is_null(),
529
+ "CR-061: no-target send must not report the prompt text as agent_id; got {value}"
530
+ );
531
+ }
532
+
533
+ #[test]
534
+ #[ignore = "real-machine: command-file gate uses real team-agent binary/lifecycle"]
535
+ fn c6_invalid_workspace_status_returns_shaped_error_without_polluting_invalid_path() {
536
+ let root = tmp_dir("c6-invalid-root");
537
+ let invalid = root.join("no").join("such").join("team");
538
+ assert!(!invalid.exists(), "fixture path starts nonexistent");
539
+
540
+ let out = run(
541
+ &["status", "--workspace", invalid.to_str().unwrap(), "--json"],
542
+ &root,
543
+ );
544
+ let value = stdout_json(&out);
545
+ let mut failures = Vec::new();
546
+ if out.status.code() == Some(0) {
547
+ failures.push(format!(
548
+ "invalid workspace returned exit 0 / successful empty status: {value}"
549
+ ));
550
+ }
551
+ if value["ok"] != json!(false) {
552
+ failures.push(format!(
553
+ "invalid workspace must return ok=false; got {value}"
554
+ ));
555
+ }
556
+ if !value["error"]
557
+ .as_str()
558
+ .is_some_and(|error| error.contains("workspace") && !error.starts_with("io:"))
559
+ {
560
+ failures.push(format!(
561
+ "error should clearly name invalid workspace, not an opaque IO/logging failure; got {value}"
562
+ ));
563
+ }
564
+ if let Some(log) = value.get("log").and_then(Value::as_str) {
565
+ if Path::new(log).starts_with(&invalid) {
566
+ failures.push(format!(
567
+ "error log must not be written or pointed inside invalid workspace; log={log}"
568
+ ));
569
+ }
570
+ } else {
571
+ failures.push(format!("invalid workspace response should include shaped error log outside invalid path; got {value}"));
572
+ }
573
+ if invalid.join(".team").exists() {
574
+ failures.push(format!(
575
+ "status created .team runtime/log state under invalid path: {}",
576
+ invalid.join(".team").display()
577
+ ));
578
+ }
579
+ assert!(
580
+ failures.is_empty(),
581
+ "CR-064/N20: invalid workspace must fail clearly without state/log pollution:\n{}",
582
+ failures.join("\n")
583
+ );
584
+ }
585
+
586
+ fn write_fake_team_dir(root: &Path, team_name: &str) -> PathBuf {
587
+ let teamdir = root.join("teamdir");
588
+ std::fs::create_dir_all(teamdir.join("agents")).unwrap();
589
+ std::fs::write(
590
+ teamdir.join("TEAM.md"),
591
+ format!(
592
+ "---\nname: {team_name}\nobjective: Multi-team session derivation.\nprovider: fake\n---\n\nTeam.\n"
593
+ ),
594
+ )
595
+ .unwrap();
596
+ std::fs::write(
597
+ teamdir.join("agents").join("worker_a.md"),
598
+ "---\nname: worker_a\nrole: Worker A\nprovider: fake\ntools:\n - mcp_team\n---\n\nWorker.\n",
599
+ )
600
+ .unwrap();
601
+ teamdir
602
+ }
603
+
604
+ #[derive(Debug)]
605
+ struct C1Fixture {
606
+ root: PathBuf,
607
+ teamdir: PathBuf,
608
+ }
609
+
610
+ impl C1Fixture {
611
+ fn root_str(&self) -> String {
612
+ self.root.to_string_lossy().to_string()
613
+ }
614
+ }
615
+
616
+ fn seed_c1_active_team_fixture(tag: &str) -> C1Fixture {
617
+ let root = tmp_dir(tag);
618
+ let teamdir = write_fake_team_dir(&root, "command-harness-template");
619
+ let spec = team_agent::compiler::compile_team(&teamdir).unwrap();
620
+ let spec_path = teamdir.join("team.spec.yaml");
621
+ std::fs::write(&spec_path, team_agent::model::yaml::dumps(&spec)).unwrap();
622
+ let roles_dir = root.join(".team").join("roles");
623
+ std::fs::create_dir_all(&roles_dir).unwrap();
624
+ std::fs::write(
625
+ roles_dir.join("worker_b.md"),
626
+ "---\nname: worker_b\nrole: Worker B\nprovider: fake\ntools:\n - mcp_team\n---\n\nWorker B.\n",
627
+ )
628
+ .unwrap();
629
+ let now = "2026-05-27T10:00:00+00:00";
630
+ team_agent::state::persist::save_runtime_state(
631
+ &root,
632
+ &json!({
633
+ "active_team_key": "command-harness-template",
634
+ "spec_path": spec_path.to_string_lossy().to_string(),
635
+ "team_dir": teamdir.to_string_lossy().to_string(),
636
+ "session_name": "team-command-harness-template",
637
+ "leader": {"id": "leader"},
638
+ "agents": {
639
+ "worker_a": {
640
+ "provider": "fake",
641
+ "role": "Worker A",
642
+ "status": "running",
643
+ "session_id": "sess-worker-a",
644
+ "first_send_at": now,
645
+ "pane_id": "%1",
646
+ "window": "worker_a"
647
+ }
648
+ },
649
+ "tasks": [
650
+ {
651
+ "id": "task_initial",
652
+ "title": "Initial task",
653
+ "type": "implementation",
654
+ "assignee": "worker_a",
655
+ "status": "running"
656
+ }
657
+ ],
658
+ "teams": {
659
+ "command-harness-template": {
660
+ "status": "running",
661
+ "spec_path": spec_path.to_string_lossy().to_string(),
662
+ "team_dir": teamdir.to_string_lossy().to_string(),
663
+ "session_name": "team-command-harness-template",
664
+ "leader": {"id": "leader"},
665
+ "agents": {
666
+ "worker_a": {
667
+ "provider": "fake",
668
+ "role": "Worker A",
669
+ "status": "running",
670
+ "session_id": "sess-worker-a",
671
+ "first_send_at": now,
672
+ "pane_id": "%1",
673
+ "window": "worker_a"
674
+ }
675
+ },
676
+ "tasks": [
677
+ {
678
+ "id": "task_initial",
679
+ "title": "Initial task",
680
+ "type": "implementation",
681
+ "assignee": "worker_a",
682
+ "status": "running"
683
+ }
684
+ ]
685
+ }
686
+ }
687
+ }),
688
+ )
689
+ .unwrap();
690
+ seed_healthy_coordinator(&root);
691
+ C1Fixture { root, teamdir }
692
+ }
693
+
694
+ fn seed_healthy_coordinator(workspace: &Path) {
695
+ let workspace = team_agent::coordinator::WorkspacePath::new(workspace.to_path_buf());
696
+ std::fs::create_dir_all(team_agent::model::paths::runtime_dir(workspace.as_path())).unwrap();
697
+ let _ = team_agent::message_store::MessageStore::open(workspace.as_path()).unwrap();
698
+ let pid = team_agent::coordinator::Pid::new(std::process::id());
699
+ team_agent::coordinator::write_coordinator_metadata(
700
+ &workspace,
701
+ pid,
702
+ team_agent::coordinator::MetadataSource::Boot,
703
+ )
704
+ .unwrap();
705
+ std::fs::write(
706
+ team_agent::coordinator::coordinator_pid_path(&workspace),
707
+ pid.to_string(),
708
+ )
709
+ .unwrap();
710
+ }
711
+
712
+ #[derive(Debug, Default)]
713
+ struct SessionRecordingTransport {
714
+ sessions: Mutex<HashSet<String>>,
715
+ spawned: Mutex<Vec<String>>,
716
+ }
717
+
718
+ impl SessionRecordingTransport {
719
+ fn spawned_sessions(&self) -> Vec<String> {
720
+ self.spawned.lock().unwrap().clone()
721
+ }
722
+
723
+ fn spawn_result(
724
+ &self,
725
+ session: &SessionName,
726
+ window: &WindowName,
727
+ kind: &'static str,
728
+ ) -> SpawnResult {
729
+ self.sessions
730
+ .lock()
731
+ .unwrap()
732
+ .insert(session.as_str().to_string());
733
+ let mut spawned = self.spawned.lock().unwrap();
734
+ spawned.push(session.as_str().to_string());
735
+ SpawnResult {
736
+ pane_id: PaneId::new(format!("%{}-{kind}", spawned.len())),
737
+ session: session.clone(),
738
+ window: window.clone(),
739
+ child_pid: None,
740
+ }
741
+ }
742
+ }
743
+
744
+ impl Transport for SessionRecordingTransport {
745
+ fn kind(&self) -> BackendKind {
746
+ BackendKind::Tmux
747
+ }
748
+
749
+ fn spawn_first(
750
+ &self,
751
+ session: &SessionName,
752
+ window: &WindowName,
753
+ _argv: &[String],
754
+ _cwd: &Path,
755
+ _env: &BTreeMap<String, String>,
756
+ ) -> Result<SpawnResult, TransportError> {
757
+ Ok(self.spawn_result(session, window, "first"))
758
+ }
759
+
760
+ fn spawn_into(
761
+ &self,
762
+ session: &SessionName,
763
+ window: &WindowName,
764
+ _argv: &[String],
765
+ _cwd: &Path,
766
+ _env: &BTreeMap<String, String>,
767
+ ) -> Result<SpawnResult, TransportError> {
768
+ Ok(self.spawn_result(session, window, "into"))
769
+ }
770
+
771
+ fn inject(
772
+ &self,
773
+ _target: &Target,
774
+ _payload: &InjectPayload,
775
+ _submit: Key,
776
+ _bracketed: bool,
777
+ ) -> Result<InjectReport, TransportError> {
778
+ Ok(InjectReport {
779
+ stage_reached: InjectStage::Submit,
780
+ inject_verification: InjectVerification::CaptureContainsToken,
781
+ submit_verification: SubmitVerification::EnterSentWithoutPlaceholderCheck,
782
+ turn_verification: TurnVerification::NotYetObserved,
783
+ attempts: 1,
784
+ submit_diagnostics: None,
785
+ })
786
+ }
787
+
788
+ fn send_keys(&self, _target: &Target, _keys: &[Key]) -> Result<(), TransportError> {
789
+ Ok(())
790
+ }
791
+
792
+ fn capture(
793
+ &self,
794
+ _target: &Target,
795
+ range: CaptureRange,
796
+ ) -> Result<CapturedText, TransportError> {
797
+ Ok(CapturedText {
798
+ text: String::new(),
799
+ range,
800
+ })
801
+ }
802
+
803
+ fn query(&self, _target: &Target, _field: PaneField) -> Result<Option<String>, TransportError> {
804
+ Ok(None)
805
+ }
806
+
807
+ fn liveness(
808
+ &self,
809
+ _pane: &PaneId,
810
+ ) -> Result<team_agent::transport::PaneLiveness, TransportError> {
811
+ Ok(team_agent::transport::PaneLiveness::Live)
812
+ }
813
+
814
+ fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
815
+ Ok(Vec::new())
816
+ }
817
+
818
+ fn has_session(&self, session: &SessionName) -> Result<bool, TransportError> {
819
+ Ok(self.sessions.lock().unwrap().contains(session.as_str()))
820
+ }
821
+
822
+ fn list_windows(&self, _session: &SessionName) -> Result<Vec<WindowName>, TransportError> {
823
+ Ok(Vec::new())
824
+ }
825
+
826
+ fn set_session_env(
827
+ &self,
828
+ _session: &SessionName,
829
+ _key: &str,
830
+ _value: &str,
831
+ ) -> Result<SetEnvOutcome, TransportError> {
832
+ Ok(SetEnvOutcome::Applied)
833
+ }
834
+
835
+ fn kill_session(&self, _session: &SessionName) -> Result<(), TransportError> {
836
+ Ok(())
837
+ }
838
+
839
+ fn kill_window(&self, _target: &Target) -> Result<(), TransportError> {
840
+ Ok(())
841
+ }
842
+
843
+ fn attach_session(&self, _session: &SessionName) -> Result<AttachOutcome, TransportError> {
844
+ Ok(AttachOutcome::Attached)
845
+ }
846
+ }