@team-agent/installer 0.5.60 → 0.5.62

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (265) hide show
  1. package/Cargo.lock +1 -1
  2. package/Cargo.toml +1 -1
  3. package/crates/team-agent/src/cli/adapters.rs +49 -3
  4. package/crates/team-agent/src/cli/attach_app_server_leader.rs +1 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +184 -0
  6. package/crates/team-agent/src/cli/emit.rs +104 -21
  7. package/crates/team-agent/src/cli/helpers.rs +1 -0
  8. package/crates/team-agent/src/cli/leader.rs +14 -2
  9. package/crates/team-agent/src/cli/leaders.rs +2 -0
  10. package/crates/team-agent/src/cli/mod.rs +296 -8
  11. package/crates/team-agent/src/cli/named_address.rs +26 -2
  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 +3 -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 +5 -1
  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/status_port/store.rs +7 -3
  25. package/crates/team-agent/src/cli/tests/named_address.rs +65 -0
  26. package/crates/team-agent/src/cli/tests/verb_profile.rs +38 -0
  27. package/crates/team-agent/src/cli/types.rs +36 -2
  28. package/crates/team-agent/src/communication_mode/mod.rs +54 -0
  29. package/crates/team-agent/src/compiler/tests.rs +5 -5
  30. package/crates/team-agent/src/compiler.rs +54 -1
  31. package/crates/team-agent/src/conpty/backend.rs +1 -0
  32. package/crates/team-agent/src/conpty/mod.rs +1 -0
  33. package/crates/team-agent/src/coordinator/backoff.rs +5 -8
  34. package/crates/team-agent/src/coordinator/conpty_shim.rs +3 -2
  35. package/crates/team-agent/src/coordinator/health.rs +1 -0
  36. package/crates/team-agent/src/coordinator/mod.rs +2 -2
  37. package/crates/team-agent/src/coordinator/orphan.rs +4 -116
  38. package/crates/team-agent/src/coordinator/runtime_detectors.rs +1 -0
  39. package/crates/team-agent/src/coordinator/runtime_observation.rs +1 -0
  40. package/crates/team-agent/src/coordinator/steps/abnormal.rs +6 -5
  41. package/crates/team-agent/src/coordinator/steps/delivery.rs +1 -0
  42. package/crates/team-agent/src/coordinator/steps/health_sync.rs +1 -0
  43. package/crates/team-agent/src/coordinator/steps/mod.rs +2 -0
  44. package/crates/team-agent/src/coordinator/steps/persist.rs +1 -0
  45. package/crates/team-agent/src/coordinator/steps/runtime_prompts.rs +1 -0
  46. package/crates/team-agent/src/coordinator/steps/session_gate.rs +1 -0
  47. package/crates/team-agent/src/coordinator/tests/abnormal.rs +0 -174
  48. package/crates/team-agent/src/coordinator/tests/api_error_recovery.rs +0 -9
  49. package/crates/team-agent/src/coordinator/tests/basics.rs +0 -184
  50. package/crates/team-agent/src/coordinator/tests/mod.rs +2 -17
  51. package/crates/team-agent/src/coordinator/tests/tick_core.rs +0 -42
  52. package/crates/team-agent/src/coordinator/tick.rs +24 -28
  53. package/crates/team-agent/src/coordinator/types.rs +3 -176
  54. package/crates/team-agent/src/db/agent_health_capture.rs +1 -0
  55. package/crates/team-agent/src/db/message_store.rs +47 -2
  56. package/crates/team-agent/src/db/migration.rs +5 -4
  57. package/crates/team-agent/src/db/mod.rs +1 -0
  58. package/crates/team-agent/src/db/schema.rs +1 -0
  59. package/crates/team-agent/src/diagnose/comms.rs +2 -1
  60. package/crates/team-agent/src/diagnose/mod.rs +1 -0
  61. package/crates/team-agent/src/diagnose/orphans.rs +1 -0
  62. package/crates/team-agent/src/fake_worker.rs +21 -1
  63. package/crates/team-agent/src/layout/manager.rs +3 -7
  64. package/crates/team-agent/src/layout/mod.rs +2 -2
  65. package/crates/team-agent/src/layout/overlay.rs +6 -1
  66. package/crates/team-agent/src/layout/placement.rs +1 -0
  67. package/crates/team-agent/src/layout/recovery.rs +3 -0
  68. package/crates/team-agent/src/layout/runtime_sessions.rs +8 -3
  69. package/crates/team-agent/src/layout/sessions.rs +8 -1
  70. package/crates/team-agent/src/layout/tmux_endpoint.rs +3 -0
  71. package/crates/team-agent/src/layout/worker_env.rs +3 -0
  72. package/crates/team-agent/src/layout/worker_window_helpers.rs +2 -0
  73. package/crates/team-agent/src/leader/helpers.rs +1 -0
  74. package/crates/team-agent/src/leader/incident.rs +1 -0
  75. package/crates/team-agent/src/leader/lease.rs +27 -22
  76. package/crates/team-agent/src/leader/mod.rs +2 -3
  77. package/crates/team-agent/src/leader/owner_bind.rs +13 -3
  78. package/crates/team-agent/src/leader/provider_attribution.rs +1 -0
  79. package/crates/team-agent/src/leader/rediscover.rs +4 -17
  80. package/crates/team-agent/src/leader/registry.rs +3 -15
  81. package/crates/team-agent/src/leader/start.rs +1072 -186
  82. package/crates/team-agent/src/leader/takeover.rs +10 -232
  83. package/crates/team-agent/src/leader/tests/claim_leader_mailbox_flush_contract.rs +544 -0
  84. package/crates/team-agent/src/leader/tests/historical_inventory_upgrade_gate_red.rs +624 -0
  85. package/crates/team-agent/src/leader/tests/identity.rs +71 -59
  86. package/crates/team-agent/src/leader/tests/identity_session_names.rs +42 -0
  87. package/crates/team-agent/src/leader/tests/idle.rs +0 -193
  88. package/crates/team-agent/src/leader/tests/lease_api.rs +0 -45
  89. package/crates/team-agent/src/leader/tests/mod.rs +4 -41
  90. package/crates/team-agent/src/leader/tests/rehomed_env.rs +83 -0
  91. package/crates/team-agent/src/leader/tests/team_in_team_state_scope_red.rs +1273 -0
  92. package/crates/team-agent/src/leader/tests/terminal_and_replay_vs_rerender_invariants_red.rs +417 -0
  93. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +22 -103
  94. package/crates/team-agent/src/leader/types.rs +1 -28
  95. package/crates/team-agent/src/lib.rs +7 -0
  96. package/crates/team-agent/src/lifecycle/display.rs +7 -1
  97. package/crates/team-agent/src/lifecycle/helpers.rs +3 -1
  98. package/crates/team-agent/src/lifecycle/launch/add_agent.rs +2 -2
  99. package/crates/team-agent/src/lifecycle/launch/fork_agent/completion.rs +1 -0
  100. package/crates/team-agent/src/lifecycle/launch/fork_agent.rs +1 -1
  101. package/crates/team-agent/src/lifecycle/launch/fork_entry.rs +1 -0
  102. package/crates/team-agent/src/lifecycle/launch/fork_finalize.rs +1 -0
  103. package/crates/team-agent/src/lifecycle/launch/plan.rs +2 -2
  104. package/crates/team-agent/src/lifecycle/launch/quick_start.rs +4 -26
  105. package/crates/team-agent/src/lifecycle/launch/readiness.rs +0 -27
  106. package/crates/team-agent/src/lifecycle/launch/spawn.rs +2 -28
  107. package/crates/team-agent/src/lifecycle/launch/spec_state.rs +4 -32
  108. package/crates/team-agent/src/lifecycle/launch/state_projection.rs +1 -0
  109. package/crates/team-agent/src/lifecycle/launch.rs +11 -12
  110. package/crates/team-agent/src/lifecycle/mod.rs +2 -1
  111. package/crates/team-agent/src/lifecycle/restart/agent.rs +4 -4
  112. package/crates/team-agent/src/lifecycle/restart/common.rs +1 -1
  113. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +2 -2
  114. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +13 -5
  115. package/crates/team-agent/src/lifecycle/restart/remove.rs +1 -1
  116. package/crates/team-agent/src/lifecycle/restart/selection.rs +3 -3
  117. package/crates/team-agent/src/lifecycle/restart.rs +13 -11
  118. package/crates/team-agent/src/lifecycle/tests/acceptance_b_batch_red.rs +1007 -0
  119. package/crates/team-agent/src/lifecycle/tests/b0_legacy_snapshot_nonauthority_contract.rs +265 -0
  120. package/crates/team-agent/src/lifecycle/tests/b0_reader_hideone_audit_contract.rs +459 -0
  121. package/crates/team-agent/src/lifecycle/tests/behavioral_diff_264_red.rs +849 -0
  122. package/crates/team-agent/src/lifecycle/tests/claude_compatible_config_red.rs +323 -0
  123. package/crates/team-agent/src/lifecycle/tests/claude_profile_launch_red.rs +423 -0
  124. package/crates/team-agent/src/lifecycle/tests/clone_fork_copilot_perms_red.rs +737 -0
  125. package/crates/team-agent/src/lifecycle/tests/codex_mcp_approval_inheritance_red.rs +1187 -0
  126. package/crates/team-agent/src/lifecycle/tests/codex_weak_window_attribution_red.rs +683 -0
  127. package/crates/team-agent/src/lifecycle/tests/communication_mode_runtime_contract_red.rs +394 -0
  128. package/crates/team-agent/src/lifecycle/tests/copilot_provider_red.rs +1856 -0
  129. package/crates/team-agent/src/lifecycle/tests/core_034_real_red.rs +917 -0
  130. package/crates/team-agent/src/lifecycle/tests/display_adaptive_red.rs +481 -0
  131. package/crates/team-agent/src/lifecycle/tests/f032_startup_prompt_best_effort_red.rs +301 -0
  132. package/crates/team-agent/src/lifecycle/tests/harvest2_a_batch_red.rs +516 -0
  133. package/crates/team-agent/src/lifecycle/tests/host_cotenant_death_p0_contract.rs +986 -0
  134. package/crates/team-agent/src/lifecycle/tests/lifecycle_rollback_red.rs +651 -0
  135. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +2 -0
  136. package/crates/team-agent/src/lifecycle/tests/quick_start_worker_readiness_red.rs +332 -0
  137. package/crates/team-agent/src/lifecycle/tests/realmachine_clusters_1_6_red.rs +846 -0
  138. package/crates/team-agent/src/lifecycle/tests/realmachine_residual_g1_g4_red.rs +648 -0
  139. package/crates/team-agent/src/lifecycle/tests/restart_build_before_destroy_0540_contract.rs +910 -0
  140. package/crates/team-agent/src/lifecycle/tests/restart_liveness_red.rs +630 -0
  141. package/crates/team-agent/src/lifecycle/tests/restart_rebind_hotfix_252_red.rs +1083 -0
  142. package/crates/team-agent/src/lifecycle/tests/restart_session_capture_red.rs +1677 -0
  143. package/crates/team-agent/src/lifecycle/tests/resume_recover_red.rs +410 -0
  144. package/crates/team-agent/src/lifecycle/tests/stale_team_saveconflict_contract.rs +428 -0
  145. package/crates/team-agent/src/lifecycle/tests/startup_latency_contract.rs +1032 -0
  146. package/crates/team-agent/src/lifecycle/tests/status_credential_redaction_contract.rs +869 -0
  147. package/crates/team-agent/src/lifecycle/tests/subprep_codex_trust_roundtrip_red.rs +1249 -0
  148. package/crates/team-agent/src/lifecycle/tests/swallow_batch4_semantics_red.rs +342 -0
  149. package/crates/team-agent/src/lifecycle/tests/team_in_team_identity_scope_red.rs +542 -0
  150. package/crates/team-agent/src/lifecycle/tests/team_in_team_sibling_quick_start_red.rs +1103 -0
  151. package/crates/team-agent/src/lifecycle/tests/team_in_team_state_scope_red.rs +1276 -0
  152. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_roster_red.rs +145 -0
  153. package/crates/team-agent/src/lifecycle/tests/team_key_retirement_txn_red.rs +603 -0
  154. package/crates/team-agent/src/lifecycle/tests/test_isolation_escape_contract.rs +837 -0
  155. package/crates/team-agent/src/lifecycle/tests/upgrade_compat_0211_red.rs +928 -0
  156. package/crates/team-agent/src/lifecycle/tests/verify_rs031_window_consistency_red.rs +951 -0
  157. package/crates/team-agent/src/lifecycle/tests/worker_dangerous_bypass_argv_red.rs +327 -0
  158. package/crates/team-agent/src/lifecycle/tests/worker_spawn_env_red.rs +760 -0
  159. package/crates/team-agent/src/lifecycle/tests.rs +59 -0
  160. package/crates/team-agent/src/lifecycle/types.rs +0 -9
  161. package/crates/team-agent/src/lifecycle/worker_command_context.rs +29 -11
  162. package/crates/team-agent/src/mcp_server/helpers.rs +1 -0
  163. package/crates/team-agent/src/mcp_server/lifecycle_tools/mod.rs +1 -0
  164. package/crates/team-agent/src/mcp_server/lifecycle_tools/state_status.rs +1 -0
  165. package/crates/team-agent/src/mcp_server/mod.rs +1 -0
  166. package/crates/team-agent/src/mcp_server/normalize.rs +5 -3
  167. package/crates/team-agent/src/mcp_server/tests/send.rs +26 -0
  168. package/crates/team-agent/src/mcp_server/tests/wire.rs +1 -1
  169. package/crates/team-agent/src/mcp_server/tools.rs +101 -66
  170. package/crates/team-agent/src/mcp_server/types.rs +6 -18
  171. package/crates/team-agent/src/mcp_server/wire.rs +32 -26
  172. package/crates/team-agent/src/messaging/activity.rs +1 -0
  173. package/crates/team-agent/src/messaging/address.rs +1 -0
  174. package/crates/team-agent/src/messaging/delivery.rs +109 -41
  175. package/crates/team-agent/src/messaging/helpers.rs +2 -1
  176. package/crates/team-agent/src/messaging/leader_channel.rs +34 -10
  177. package/crates/team-agent/src/messaging/leader_receiver.rs +42 -24
  178. package/crates/team-agent/src/messaging/mod.rs +10 -14
  179. package/crates/team-agent/src/messaging/peers.rs +2 -0
  180. package/crates/team-agent/src/messaging/persist.rs +3 -1
  181. package/crates/team-agent/src/messaging/presentation.rs +111 -1
  182. package/crates/team-agent/src/messaging/results.rs +166 -17
  183. package/crates/team-agent/src/messaging/scheduler.rs +1 -0
  184. package/crates/team-agent/src/messaging/selftest.rs +1 -1
  185. package/crates/team-agent/src/messaging/send.rs +135 -89
  186. package/crates/team-agent/src/messaging/tests/leader_channel.rs +6 -6
  187. package/crates/team-agent/src/messaging/tests/leader_inject_acceptance.rs +187 -0
  188. package/crates/team-agent/src/messaging/tests/mod.rs +5 -0
  189. package/crates/team-agent/src/messaging/tests/runtime.rs +13 -6
  190. package/crates/team-agent/src/messaging/types.rs +7 -2
  191. package/crates/team-agent/src/messaging/watchers.rs +24 -13
  192. package/crates/team-agent/src/model/enums.rs +1 -0
  193. package/crates/team-agent/src/model/errors.rs +1 -0
  194. package/crates/team-agent/src/model/ids.rs +1 -0
  195. package/crates/team-agent/src/model/mod.rs +2 -0
  196. package/crates/team-agent/src/model/name_similarity.rs +1 -0
  197. package/crates/team-agent/src/model/pane_authority_refusal.rs +359 -0
  198. package/crates/team-agent/src/model/paths.rs +1 -0
  199. package/crates/team-agent/src/model/permissions.rs +3 -2
  200. package/crates/team-agent/src/model/routing.rs +1 -0
  201. package/crates/team-agent/src/model/spec.rs +17 -0
  202. package/crates/team-agent/src/model/task_graph.rs +1 -0
  203. package/crates/team-agent/src/model/yaml/tests.rs +1 -0
  204. package/crates/team-agent/src/model/yaml.rs +1 -0
  205. package/crates/team-agent/src/packaging/install.rs +9 -147
  206. package/crates/team-agent/src/packaging/migrate.rs +2 -0
  207. package/crates/team-agent/src/packaging/mod.rs +1 -0
  208. package/crates/team-agent/src/packaging/repair.rs +2 -0
  209. package/crates/team-agent/src/packaging/tests.rs +29 -218
  210. package/crates/team-agent/src/packaging/types.rs +8 -15
  211. package/crates/team-agent/src/platform/argv.rs +4 -0
  212. package/crates/team-agent/src/platform/errors.rs +10 -1
  213. package/crates/team-agent/src/platform/file_lock.rs +4 -157
  214. package/crates/team-agent/src/platform/mod.rs +0 -61
  215. package/crates/team-agent/src/platform/process.rs +1 -0
  216. package/crates/team-agent/src/provider/adapter.rs +1 -0
  217. package/crates/team-agent/src/provider/adapters/claude.rs +1 -0
  218. package/crates/team-agent/src/provider/adapters/claude_fork.rs +1 -0
  219. package/crates/team-agent/src/provider/adapters/codex.rs +1 -0
  220. package/crates/team-agent/src/provider/adapters/copilot.rs +1 -0
  221. package/crates/team-agent/src/provider/adapters/copilot_fork.rs +1 -0
  222. package/crates/team-agent/src/provider/adapters/fake.rs +1 -0
  223. package/crates/team-agent/src/provider/adapters/mod.rs +1 -0
  224. package/crates/team-agent/src/provider/approvals/mod.rs +1 -0
  225. package/crates/team-agent/src/provider/approvals/parsing.rs +2 -5
  226. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +6 -5
  227. package/crates/team-agent/src/provider/classify.rs +1 -0
  228. package/crates/team-agent/src/provider/faults.rs +1 -26
  229. package/crates/team-agent/src/provider/helpers.rs +1 -0
  230. package/crates/team-agent/src/provider/mod.rs +1 -1
  231. package/crates/team-agent/src/provider/session/capture.rs +41 -22
  232. package/crates/team-agent/src/provider/session/context_fork/claude.rs +1 -0
  233. package/crates/team-agent/src/provider/session/context_fork/codex.rs +1 -0
  234. package/crates/team-agent/src/provider/session/context_fork/outcome.rs +1 -0
  235. package/crates/team-agent/src/provider/session/context_fork.rs +1 -0
  236. package/crates/team-agent/src/provider/session/mod.rs +2 -4
  237. package/crates/team-agent/src/provider/session/resume.rs +2 -209
  238. package/crates/team-agent/src/provider/session_scan/claude.rs +93 -3
  239. package/crates/team-agent/src/provider/session_scan/codex.rs +1 -0
  240. package/crates/team-agent/src/provider/session_scan/common.rs +24 -7
  241. package/crates/team-agent/src/provider/session_scan/copilot.rs +1 -0
  242. package/crates/team-agent/src/provider/session_scan.rs +6 -42
  243. package/crates/team-agent/src/provider/startup_prompt.rs +1 -0
  244. package/crates/team-agent/src/provider/types.rs +1 -0
  245. package/crates/team-agent/src/provider/wire.rs +1 -0
  246. package/crates/team-agent/src/state/identity.rs +16 -5
  247. package/crates/team-agent/src/state/identity_keys.rs +1 -0
  248. package/crates/team-agent/src/state/mod.rs +2 -0
  249. package/crates/team-agent/src/state/owner_gate.rs +5 -0
  250. package/crates/team-agent/src/state/ownership.rs +10 -4
  251. package/crates/team-agent/src/state/paths.rs +12 -0
  252. package/crates/team-agent/src/state/persist.rs +9 -3
  253. package/crates/team-agent/src/state/projection.rs +15 -2
  254. package/crates/team-agent/src/state/repository/intent.rs +1 -0
  255. package/crates/team-agent/src/state/repository/tests.rs +1 -0
  256. package/crates/team-agent/src/state/repository.rs +7 -0
  257. package/crates/team-agent/src/state/selector.rs +1 -0
  258. package/crates/team-agent/src/tmux_backend/tests.rs +39 -0
  259. package/crates/team-agent/src/tmux_backend.rs +63 -0
  260. package/crates/team-agent/src/topology.rs +3 -0
  261. package/crates/team-agent/src/transport.rs +15 -0
  262. package/package.json +4 -4
  263. package/skills/team-agent/command-coverage.json +379 -0
  264. package/crates/team-agent/src/leader/inject.rs +0 -33
  265. package/crates/team-agent/src/provider/command.rs +0 -80
@@ -0,0 +1,760 @@
1
+ //! Worker provider spawn environment contracts.
2
+ //!
3
+ //! #229 B-layer root cause: spawned Codex workers must inherit the parent `team-agent`
4
+ //! process environment (proxy/CA/PATH/etc.) just like a user typing `codex` in the same terminal,
5
+ //! then overlay the Team Agent identity env. The contract is path-generic and covers the three
6
+ //! worker spawn surfaces that can launch Codex: quick-start/launch, restart, and add-agent.
7
+
8
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
9
+
10
+ #[path = "../../../tests/support/hermetic.rs"]
11
+ mod hermetic_guard;
12
+ #[allow(dead_code)]
13
+ fn _hermetic_boundary_marker(_: &hermetic_guard::HermeticTestEnv) {}
14
+
15
+ use std::collections::BTreeMap;
16
+ use std::path::{Path, PathBuf};
17
+ use std::sync::atomic::{AtomicU64, Ordering};
18
+ use std::sync::Mutex;
19
+
20
+ use serde_json::json;
21
+ use serial_test::serial;
22
+ use team_agent::lifecycle::{
23
+ add_agent_with_transport, fork_agent_with_transport, launch_with_transport,
24
+ restart_with_transport,
25
+ };
26
+ use team_agent::model::ids::AgentId;
27
+ use team_agent::transport::{
28
+ AttachOutcome, BackendKind, CaptureRange, CapturedText, InjectPayload, InjectReport,
29
+ InjectStage, InjectVerification, Key, PaneField, PaneId, PaneInfo, SessionName, SetEnvOutcome,
30
+ SpawnResult, SubmitVerification, Target, Transport, TransportError, TurnVerification,
31
+ WindowName,
32
+ };
33
+
34
+ const RUNTIME_TEAM_KEY: &str = "teamdir";
35
+
36
+ #[test]
37
+ #[serial(env)]
38
+ fn worker_spawn_inherits_parent_process_env_for_proxy_and_ca() {
39
+ let _guard = EnvGuard::set([
40
+ ("TA_SPAWN_ENV_CANARY", "FOO"),
41
+ ("HTTP_PROXY", "http://canary-proxy:1"),
42
+ ("NODE_EXTRA_CA_CERTS", "/canary/ca.pem"),
43
+ ("TMUX", "/tmp/tmux-canary/default,1"),
44
+ ("TMUX_PANE", "%canary"),
45
+ ]);
46
+
47
+ let launch_team = compiled_team_dir("launch", &[("worker_a", "Launch Worker")]);
48
+ let launch_transport = RecordingTransport::new().with_session_present(false);
49
+ launch_with_transport(
50
+ &launch_team.join("team.spec.yaml"),
51
+ false,
52
+ true,
53
+ true,
54
+ &launch_transport,
55
+ )
56
+ .expect("launch fixture should spawn worker");
57
+ let launch_command_line = launch_transport.single_spawn_command_line();
58
+
59
+ let restart_team = compiled_team_dir("restart", &[("worker_a", "Restart Worker")]);
60
+ seed_restart_state(&restart_team, "worker_a");
61
+ let restart_transport = RecordingTransport::new().with_session_present(false);
62
+ restart_with_transport(&restart_team, true, None, &restart_transport)
63
+ .expect("restart fixture should spawn worker");
64
+ let restart_command_line = restart_transport.single_spawn_command_line();
65
+
66
+ let add_team = compiled_team_dir("add-agent", &[("worker_a", "Existing Worker")]);
67
+ seed_running_add_agent_state(&add_team, "worker_a");
68
+ let role_file = add_team.parent().unwrap().join("worker_b.md");
69
+ std::fs::write(&role_file, role_doc("worker_b", "Added Worker")).unwrap();
70
+ let add_transport = RecordingTransport::new().with_session_present(true);
71
+ add_agent_with_transport(
72
+ &add_team,
73
+ &AgentId::new("worker_b"),
74
+ &role_file,
75
+ false,
76
+ None,
77
+ &add_transport,
78
+ )
79
+ .expect("add-agent fixture should spawn worker");
80
+ let add_command_line = add_transport.single_spawn_command_line();
81
+
82
+ let failures = [
83
+ ("quick-start launch", launch_command_line, "worker_a"),
84
+ ("restart", restart_command_line, "worker_a"),
85
+ ("add-agent", add_command_line, "worker_b"),
86
+ ]
87
+ .into_iter()
88
+ .flat_map(|(surface, command_line, agent_id)| {
89
+ spawn_env_contract_failures(surface, &command_line, agent_id)
90
+ })
91
+ .collect::<Vec<_>>();
92
+
93
+ assert!(
94
+ failures.is_empty(),
95
+ "worker spawn env contract failed:\n{}",
96
+ failures.join("\n")
97
+ );
98
+ }
99
+
100
+ #[test]
101
+ #[serial(env)]
102
+ fn worker_spawn_scrubs_cross_provider_process_identity_but_keeps_config_env() {
103
+ let _home = hermetic_guard::HermeticTestEnv::enter("worker-spawn-fork");
104
+ let _guard = EnvGuard::set([
105
+ ("CLAUDECODE", "1"),
106
+ ("CLAUDE_CODE_SESSION_ID", "leader-session"),
107
+ ("CLAUDE_CODE_SENTINEL", "foreign"),
108
+ ("CLAUDE_EFFORT", "high"),
109
+ ("CODEX_THREAD_ID", "thread-from-parent"),
110
+ ("OPENAI_API_KEY", "sk-test-config"),
111
+ ("CODEX_HOME", "/tmp/codex-home-config"),
112
+ ("TA_SPAWN_ENV_CANARY", "KEEP"),
113
+ ("HTTP_PROXY", "http://canary-proxy:1"),
114
+ ("NODE_EXTRA_CA_CERTS", "/canary/ca.pem"),
115
+ ]);
116
+
117
+ let launch_team = compiled_team_dir("cross-launch", &[("worker_a", "Launch Worker")]);
118
+ let launch_transport = RecordingTransport::new().with_session_present(false);
119
+ launch_with_transport(
120
+ &launch_team.join("team.spec.yaml"),
121
+ false,
122
+ true,
123
+ true,
124
+ &launch_transport,
125
+ )
126
+ .expect("launch fixture should spawn worker");
127
+
128
+ let restart_team = compiled_team_dir("cross-restart", &[("worker_a", "Restart Worker")]);
129
+ seed_restart_state(&restart_team, "worker_a");
130
+ let restart_transport = RecordingTransport::new().with_session_present(false);
131
+ restart_with_transport(&restart_team, true, None, &restart_transport)
132
+ .expect("restart fixture should spawn worker");
133
+
134
+ let add_team = compiled_team_dir("cross-add", &[("worker_a", "Existing Worker")]);
135
+ seed_running_add_agent_state(&add_team, "worker_a");
136
+ let role_file = add_team.parent().unwrap().join("worker_b.md");
137
+ std::fs::write(&role_file, role_doc("worker_b", "Added Worker")).unwrap();
138
+ let add_transport = RecordingTransport::new().with_session_present(true);
139
+ add_agent_with_transport(
140
+ &add_team,
141
+ &AgentId::new("worker_b"),
142
+ &role_file,
143
+ false,
144
+ None,
145
+ &add_transport,
146
+ )
147
+ .expect("add-agent fixture should spawn worker");
148
+
149
+ let fork_team = compiled_team_dir("cross-fork", &[("worker_a", "Fork Source")]);
150
+ seed_forkable_source_state(&fork_team, "worker_a");
151
+ let fork_transport = RecordingTransport::new().with_session_present(true);
152
+ fork_agent_with_transport(
153
+ &fork_team,
154
+ &AgentId::new("worker_a"),
155
+ &AgentId::new("worker_fork"),
156
+ None,
157
+ false,
158
+ None,
159
+ &fork_transport,
160
+ )
161
+ .expect("fork-agent fixture should spawn worker");
162
+
163
+ let failures = [
164
+ ("quick-start launch", launch_transport.single_spawn()),
165
+ ("restart", restart_transport.single_spawn()),
166
+ ("add-agent", add_transport.single_spawn()),
167
+ ("fork-agent", fork_transport.single_spawn()),
168
+ ]
169
+ .into_iter()
170
+ .flat_map(|(surface, spawn)| cross_provider_identity_failures(surface, &spawn))
171
+ .collect::<Vec<_>>();
172
+
173
+ assert!(
174
+ failures.is_empty(),
175
+ "cross-provider worker env isolation contract failed:\n{}",
176
+ failures.join("\n")
177
+ );
178
+ }
179
+
180
+ #[test]
181
+ #[serial(env)]
182
+ fn worker_spawn_stays_out_of_coordinator_tick_and_daemon_preserves_parent_env() {
183
+ let tick = std::fs::read_to_string(concat!(
184
+ env!("CARGO_MANIFEST_DIR"),
185
+ "/src/coordinator/tick.rs"
186
+ ))
187
+ .unwrap();
188
+ assert!(
189
+ !tick.contains("transport.spawn_first") && !tick.contains("transport.spawn_into"),
190
+ "coordinator tick must never spawn worker panes; worker spawn belongs to user-invoked lifecycle paths so it inherits the user's shell env"
191
+ );
192
+
193
+ let health = std::fs::read_to_string(concat!(
194
+ env!("CARGO_MANIFEST_DIR"),
195
+ "/src/coordinator/health.rs"
196
+ ))
197
+ .unwrap();
198
+ let start = health
199
+ .find("pub fn start_coordinator")
200
+ .map(|idx| &health[idx..])
201
+ .unwrap_or(&health);
202
+ let start = start
203
+ .find("pub fn stop_coordinator")
204
+ .map(|idx| &start[..idx])
205
+ .unwrap_or(start);
206
+ assert!(
207
+ !start.contains(".env_clear(") && !start.contains(".env("),
208
+ "start_coordinator must inherit the parent environment and must not clear/override daemon env; source={start}"
209
+ );
210
+ }
211
+
212
+ fn spawn_env_contract_failures(surface: &str, command_line: &str, agent_id: &str) -> Vec<String> {
213
+ let mut failures = Vec::new();
214
+ for expected in [
215
+ "TA_SPAWN_ENV_CANARY=FOO",
216
+ "HTTP_PROXY=http://canary-proxy:1",
217
+ "NODE_EXTRA_CA_CERTS=/canary/ca.pem",
218
+ "TEAM_AGENT_WORKSPACE=",
219
+ "TEAM_AGENT_OWNER_TEAM_ID=teamdir",
220
+ ] {
221
+ if !command_line.contains(expected) {
222
+ failures.push(format!(
223
+ "{surface}: missing `{expected}`; worker spawn must inherit parent env and overlay Team Agent identity; command_line={command_line:?}"
224
+ ));
225
+ }
226
+ }
227
+ if !command_line.contains(&format!("TEAM_AGENT_AGENT_ID={agent_id}")) {
228
+ failures.push(format!(
229
+ "{surface}: missing `TEAM_AGENT_AGENT_ID={agent_id}` overlay; command_line={command_line:?}"
230
+ ));
231
+ }
232
+ if command_line.contains("TMUX=") || command_line.contains("TMUX_PANE=") {
233
+ failures.push(format!(
234
+ "{surface}: worker spawn must filter tmux control env from inherited parent env; command_line={command_line:?}"
235
+ ));
236
+ }
237
+ if !command_line.split_whitespace().any(|part| part == "codex") {
238
+ failures.push(format!(
239
+ "{surface}: provider executable must remain the command name `codex`; command_line={command_line:?}"
240
+ ));
241
+ }
242
+ if command_line
243
+ .split_whitespace()
244
+ .any(|part| part.ends_with("/codex"))
245
+ {
246
+ failures.push(format!(
247
+ "{surface}: provider executable must not be hard-coded as an absolute codex path; command_line={command_line:?}"
248
+ ));
249
+ }
250
+ failures
251
+ }
252
+
253
+ fn cross_provider_identity_failures(surface: &str, spawn: &RecordedSpawn) -> Vec<String> {
254
+ let mut failures = Vec::new();
255
+ let identity_keys = [
256
+ "CLAUDECODE",
257
+ "CLAUDE_CODE_SESSION_ID",
258
+ "CLAUDE_CODE_SENTINEL",
259
+ "CLAUDE_EFFORT",
260
+ "CODEX_THREAD_ID",
261
+ ];
262
+ let present_identity = identity_keys
263
+ .iter()
264
+ .copied()
265
+ .filter(|key| spawn.env.contains_key(*key))
266
+ .collect::<Vec<_>>();
267
+ if !present_identity.is_empty() {
268
+ failures.push(format!(
269
+ "{surface}: worker env map must scrub process/session identity keys; present={present_identity:?}"
270
+ ));
271
+ }
272
+ let missing_unsets = identity_keys
273
+ .iter()
274
+ .copied()
275
+ .filter(|key| !spawn.env_unset.iter().any(|candidate| candidate == key))
276
+ .collect::<Vec<_>>();
277
+ if !missing_unsets.is_empty() {
278
+ failures.push(format!(
279
+ "{surface}: worker env_unset must clear stale tmux/server identity keys; missing={missing_unsets:?}; env_unset={:?}",
280
+ spawn.env_unset
281
+ ));
282
+ }
283
+ for (key, value) in [
284
+ ("OPENAI_API_KEY", "sk-test-config"),
285
+ ("CODEX_HOME", "/tmp/codex-home-config"),
286
+ ("TA_SPAWN_ENV_CANARY", "KEEP"),
287
+ ("HTTP_PROXY", "http://canary-proxy:1"),
288
+ ("NODE_EXTRA_CA_CERTS", "/canary/ca.pem"),
289
+ ] {
290
+ if spawn.env.get(key).map(String::as_str) != Some(value) {
291
+ failures.push(format!(
292
+ "{surface}: worker env must preserve config/generic key `{key}` with the expected test value; present={}",
293
+ spawn.env.contains_key(key)
294
+ ));
295
+ }
296
+ if spawn.env_unset.iter().any(|candidate| candidate == key) {
297
+ failures.push(format!(
298
+ "{surface}: worker env_unset must not remove config/generic key `{key}`; env_unset={:?}",
299
+ spawn.env_unset
300
+ ));
301
+ }
302
+ }
303
+ failures
304
+ }
305
+
306
+ fn compiled_team_dir(label: &str, agents: &[(&str, &str)]) -> PathBuf {
307
+ let team = tmp_dir(label).join("teamdir");
308
+ std::fs::create_dir_all(team.join("agents")).unwrap();
309
+ std::fs::write(
310
+ team.join("TEAM.md"),
311
+ "---\nname: envteam\nobjective: Worker spawn env contract.\nprovider: codex\n---\n\nTeam.\n",
312
+ )
313
+ .unwrap();
314
+ for (name, role) in agents {
315
+ std::fs::write(
316
+ team.join("agents").join(format!("{name}.md")),
317
+ role_doc(name, role),
318
+ )
319
+ .unwrap();
320
+ }
321
+ let spec = team_agent::compiler::compile_team(&team).unwrap();
322
+ std::fs::write(
323
+ team.join("team.spec.yaml"),
324
+ team_agent::model::yaml::dumps(&spec),
325
+ )
326
+ .unwrap();
327
+ team
328
+ }
329
+
330
+ fn role_doc(name: &str, role: &str) -> String {
331
+ format!(
332
+ "---\nname: {name}\nrole: {role}\nprovider: codex\nmodel: codex-test-model\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\n{role}.\n"
333
+ )
334
+ }
335
+
336
+ fn seed_restart_state(team: &Path, agent_id: &str) {
337
+ team_agent::state::persist::save_runtime_state(
338
+ team,
339
+ &json!({
340
+ "active_team_key": RUNTIME_TEAM_KEY,
341
+ "team_dir": team.to_string_lossy().to_string(),
342
+ "spec_path": team.join("team.spec.yaml").to_string_lossy().to_string(),
343
+ "session_name": "team-envteam",
344
+ "agents": {
345
+ agent_id: {
346
+ "status": "running",
347
+ "provider": "codex",
348
+ "role": "Restart Worker",
349
+ "tools": ["mcp_team"],
350
+ "window": agent_id,
351
+ "owner_team_id": RUNTIME_TEAM_KEY,
352
+ "session_id": "sess-worker-a",
353
+ "first_send_at": "2026-06-05T09:00:00+00:00"
354
+ }
355
+ }
356
+ }),
357
+ )
358
+ .unwrap();
359
+ seed_healthy_coordinator(team);
360
+ }
361
+
362
+ fn seed_running_add_agent_state(team: &Path, agent_id: &str) {
363
+ let workspace = team_agent::model::paths::team_workspace(team).unwrap();
364
+ team_agent::state::persist::save_runtime_state(
365
+ &workspace,
366
+ &json!({
367
+ "active_team_key": RUNTIME_TEAM_KEY,
368
+ "team_dir": team.to_string_lossy().to_string(),
369
+ "spec_path": team.join("team.spec.yaml").to_string_lossy().to_string(),
370
+ "session_name": "team-envteam",
371
+ "agents": {
372
+ agent_id: {
373
+ "status": "running",
374
+ "provider": "codex",
375
+ "role": "Existing Worker",
376
+ "tools": ["mcp_team"],
377
+ "window": agent_id,
378
+ "owner_team_id": RUNTIME_TEAM_KEY
379
+ }
380
+ }
381
+ }),
382
+ )
383
+ .unwrap();
384
+ seed_healthy_coordinator(&workspace);
385
+ }
386
+
387
+ fn seed_forkable_source_state(team: &Path, agent_id: &str) {
388
+ let workspace = team_agent::model::paths::team_workspace(team).unwrap();
389
+ let rollout_root = PathBuf::from(std::env::var_os("HOME").expect("hermetic HOME"))
390
+ .join(".codex/sessions/worker-spawn-source");
391
+ std::fs::create_dir_all(&rollout_root).unwrap();
392
+ let rollout = rollout_root.join("rollout-sess-worker-a.jsonl");
393
+ std::fs::write(
394
+ &rollout,
395
+ format!(
396
+ "{}\n{}\n",
397
+ json!({
398
+ "type": "session_meta",
399
+ "payload": {"id": "sess-worker-a", "cwd": workspace}
400
+ }),
401
+ json!({
402
+ "type": "response_item",
403
+ "payload": {"content": [{
404
+ "type": "input_text",
405
+ "text": format!("You are Team Agent worker `{agent_id}` with role `fixture`.")
406
+ }]}
407
+ })
408
+ ),
409
+ )
410
+ .unwrap();
411
+ team_agent::state::persist::save_runtime_state(
412
+ &workspace,
413
+ &json!({
414
+ "active_team_key": RUNTIME_TEAM_KEY,
415
+ "team_dir": team.to_string_lossy().to_string(),
416
+ "spec_path": team.join("team.spec.yaml").to_string_lossy().to_string(),
417
+ "session_name": "team-envteam",
418
+ "agents": {
419
+ agent_id: {
420
+ "status": "running",
421
+ "provider": "codex",
422
+ "auth_mode": "subscription",
423
+ "role": "Fork Source",
424
+ "window": agent_id,
425
+ "owner_team_id": RUNTIME_TEAM_KEY,
426
+ "session_id": "sess-worker-a",
427
+ "rollout_path": rollout.to_string_lossy().to_string(),
428
+ "captured_at": "2026-07-16T00:00:00+00:00",
429
+ "captured_via": "session.captured",
430
+ "tools": ["mcp_team"]
431
+ }
432
+ }
433
+ }),
434
+ )
435
+ .unwrap();
436
+ seed_healthy_coordinator(&workspace);
437
+ }
438
+
439
+ fn seed_healthy_coordinator(workspace: &Path) {
440
+ let workspace = team_agent::coordinator::WorkspacePath::new(workspace.to_path_buf());
441
+ std::fs::create_dir_all(team_agent::model::paths::runtime_dir(workspace.as_path())).unwrap();
442
+ let _ = team_agent::message_store::MessageStore::open(workspace.as_path()).unwrap();
443
+ let pid = team_agent::coordinator::Pid::new(std::process::id());
444
+ team_agent::coordinator::write_coordinator_metadata(
445
+ &workspace,
446
+ pid,
447
+ team_agent::coordinator::MetadataSource::Boot,
448
+ )
449
+ .unwrap();
450
+ std::fs::write(
451
+ team_agent::coordinator::coordinator_pid_path(&workspace),
452
+ pid.to_string(),
453
+ )
454
+ .unwrap();
455
+ }
456
+
457
+ fn tmp_dir(tag: &str) -> PathBuf {
458
+ static N: AtomicU64 = AtomicU64::new(0);
459
+ let dir = std::env::temp_dir().join(format!(
460
+ "ta-rs-worker-spawn-env-{tag}-{}-{}",
461
+ std::process::id(),
462
+ N.fetch_add(1, Ordering::Relaxed)
463
+ ));
464
+ let _ = std::fs::remove_dir_all(&dir);
465
+ std::fs::create_dir_all(&dir).unwrap();
466
+ std::fs::canonicalize(dir).unwrap()
467
+ }
468
+
469
+ struct EnvGuard {
470
+ previous: Vec<(&'static str, Option<String>)>,
471
+ }
472
+
473
+ impl EnvGuard {
474
+ fn set<const N: usize>(values: [(&'static str, &'static str); N]) -> Self {
475
+ let previous = values
476
+ .iter()
477
+ .map(|(key, _)| (*key, std::env::var(key).ok()))
478
+ .collect::<Vec<_>>();
479
+ for (key, value) in values {
480
+ unsafe {
481
+ std::env::set_var(key, value);
482
+ }
483
+ }
484
+ Self { previous }
485
+ }
486
+ }
487
+
488
+ impl Drop for EnvGuard {
489
+ fn drop(&mut self) {
490
+ for (key, value) in self.previous.drain(..).rev() {
491
+ unsafe {
492
+ if let Some(value) = value {
493
+ std::env::set_var(key, value);
494
+ } else {
495
+ std::env::remove_var(key);
496
+ }
497
+ }
498
+ }
499
+ }
500
+ }
501
+
502
+ #[derive(Debug, Clone)]
503
+ struct RecordedSpawn {
504
+ argv: Vec<String>,
505
+ env: BTreeMap<String, String>,
506
+ env_unset: Vec<String>,
507
+ session: SessionName,
508
+ window: WindowName,
509
+ pane_id: PaneId,
510
+ }
511
+
512
+ impl RecordedSpawn {
513
+ fn command_line(&self) -> String {
514
+ let mut parts = self
515
+ .env
516
+ .iter()
517
+ .map(|(key, value)| format!("{key}={value}"))
518
+ .collect::<Vec<_>>();
519
+ parts.extend(self.argv.iter().cloned());
520
+ parts.join(" ")
521
+ }
522
+ }
523
+
524
+ #[derive(Debug, Default)]
525
+ struct RecordingTransport {
526
+ spawns: Mutex<Vec<RecordedSpawn>>,
527
+ session_present: bool,
528
+ }
529
+
530
+ impl RecordingTransport {
531
+ fn new() -> Self {
532
+ Self::default()
533
+ }
534
+
535
+ fn with_session_present(mut self, present: bool) -> Self {
536
+ self.session_present = present;
537
+ self
538
+ }
539
+
540
+ fn single_spawn_command_line(&self) -> String {
541
+ self.single_spawn().command_line()
542
+ }
543
+
544
+ fn single_spawn(&self) -> RecordedSpawn {
545
+ let spawns = self.spawns.lock().unwrap();
546
+ assert_eq!(
547
+ spawns.len(),
548
+ 1,
549
+ "fixture should record exactly one worker spawn; spawns={spawns:?}"
550
+ );
551
+ spawns[0].clone()
552
+ }
553
+
554
+ fn spawn_result(
555
+ &self,
556
+ session: &SessionName,
557
+ window: &WindowName,
558
+ argv: &[String],
559
+ cwd: &Path,
560
+ env: &BTreeMap<String, String>,
561
+ env_unset: &[String],
562
+ ) -> SpawnResult {
563
+ emit_codex_fork_backing(argv, cwd);
564
+ let mut spawns = self.spawns.lock().unwrap();
565
+ let pane_id = PaneId::new(format!("%{}", spawns.len() + 1));
566
+ spawns.push(RecordedSpawn {
567
+ argv: argv.to_vec(),
568
+ env: env.clone(),
569
+ env_unset: env_unset.to_vec(),
570
+ session: session.clone(),
571
+ window: window.clone(),
572
+ pane_id: pane_id.clone(),
573
+ });
574
+ SpawnResult {
575
+ pane_id,
576
+ session: session.clone(),
577
+ window: window.clone(),
578
+ child_pid: None,
579
+ }
580
+ }
581
+ }
582
+
583
+ fn emit_codex_fork_backing(argv: &[String], cwd: &Path) {
584
+ if !argv
585
+ .windows(2)
586
+ .any(|pair| pair[0] == "codex" && pair[1] == "fork")
587
+ {
588
+ return;
589
+ }
590
+ static SESSION_SEQ: AtomicU64 = AtomicU64::new(0);
591
+ let session_id = format!(
592
+ "worker-spawn-fork-{}-{}",
593
+ std::process::id(),
594
+ SESSION_SEQ.fetch_add(1, Ordering::Relaxed)
595
+ );
596
+ let root = PathBuf::from(std::env::var_os("HOME").expect("hermetic HOME"))
597
+ .join(".codex/sessions/worker-spawn-env-fixture");
598
+ std::fs::create_dir_all(&root).unwrap();
599
+ let record = serde_json::json!({
600
+ "session_meta": {"payload": {
601
+ "id": session_id.clone(),
602
+ "cwd": cwd.to_string_lossy(),
603
+ "created_at": chrono::Utc::now().to_rfc3339(),
604
+ }}
605
+ });
606
+ std::fs::write(
607
+ root.join(format!("rollout-{session_id}.jsonl")),
608
+ format!("{record}\n"),
609
+ )
610
+ .unwrap();
611
+ }
612
+
613
+ impl Transport for RecordingTransport {
614
+ fn kind(&self) -> BackendKind {
615
+ BackendKind::Tmux
616
+ }
617
+
618
+ fn spawn_first(
619
+ &self,
620
+ session: &SessionName,
621
+ window: &WindowName,
622
+ argv: &[String],
623
+ cwd: &Path,
624
+ env: &BTreeMap<String, String>,
625
+ ) -> Result<SpawnResult, TransportError> {
626
+ Ok(self.spawn_result(session, window, argv, cwd, env, &[]))
627
+ }
628
+
629
+ fn spawn_into(
630
+ &self,
631
+ session: &SessionName,
632
+ window: &WindowName,
633
+ argv: &[String],
634
+ cwd: &Path,
635
+ env: &BTreeMap<String, String>,
636
+ ) -> Result<SpawnResult, TransportError> {
637
+ Ok(self.spawn_result(session, window, argv, cwd, env, &[]))
638
+ }
639
+
640
+ fn spawn_first_with_env_unset(
641
+ &self,
642
+ session: &SessionName,
643
+ window: &WindowName,
644
+ argv: &[String],
645
+ cwd: &Path,
646
+ env: &BTreeMap<String, String>,
647
+ env_unset: &[String],
648
+ ) -> Result<SpawnResult, TransportError> {
649
+ Ok(self.spawn_result(session, window, argv, cwd, env, env_unset))
650
+ }
651
+
652
+ fn spawn_into_with_env_unset(
653
+ &self,
654
+ session: &SessionName,
655
+ window: &WindowName,
656
+ argv: &[String],
657
+ cwd: &Path,
658
+ env: &BTreeMap<String, String>,
659
+ env_unset: &[String],
660
+ ) -> Result<SpawnResult, TransportError> {
661
+ Ok(self.spawn_result(session, window, argv, cwd, env, env_unset))
662
+ }
663
+
664
+ fn inject(
665
+ &self,
666
+ _target: &Target,
667
+ _payload: &InjectPayload,
668
+ _submit: Key,
669
+ _bracketed: bool,
670
+ ) -> Result<InjectReport, TransportError> {
671
+ Ok(InjectReport {
672
+ stage_reached: InjectStage::Submit,
673
+ inject_verification: InjectVerification::CaptureContainsToken,
674
+ submit_verification: SubmitVerification::EnterSentWithoutPlaceholderCheck,
675
+ turn_verification: TurnVerification::NotYetObserved,
676
+ attempts: 1,
677
+ submit_diagnostics: None,
678
+ })
679
+ }
680
+
681
+ fn send_keys(&self, _target: &Target, _keys: &[Key]) -> Result<(), TransportError> {
682
+ Ok(())
683
+ }
684
+
685
+ fn capture(
686
+ &self,
687
+ _target: &Target,
688
+ range: CaptureRange,
689
+ ) -> Result<CapturedText, TransportError> {
690
+ Ok(CapturedText {
691
+ text: String::new(),
692
+ range,
693
+ })
694
+ }
695
+
696
+ fn query(&self, _target: &Target, field: PaneField) -> Result<Option<String>, TransportError> {
697
+ match field {
698
+ PaneField::PaneWidth => Ok(Some("120".to_string())),
699
+ _ => Ok(None),
700
+ }
701
+ }
702
+
703
+ fn liveness(
704
+ &self,
705
+ _pane: &PaneId,
706
+ ) -> Result<team_agent::transport::PaneLiveness, TransportError> {
707
+ Ok(team_agent::transport::PaneLiveness::Live)
708
+ }
709
+
710
+ fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
711
+ Ok(self
712
+ .spawns
713
+ .lock()
714
+ .unwrap()
715
+ .iter()
716
+ .map(|spawn| PaneInfo {
717
+ pane_id: spawn.pane_id.clone(),
718
+ session: spawn.session.clone(),
719
+ window_index: None,
720
+ window_name: Some(spawn.window.clone()),
721
+ pane_index: None,
722
+ tty: None,
723
+ current_command: Some("codex".to_string()),
724
+ current_path: None,
725
+ active: false,
726
+ pane_pid: None,
727
+ leader_env: BTreeMap::new(),
728
+ })
729
+ .collect())
730
+ }
731
+
732
+ fn has_session(&self, _session: &SessionName) -> Result<bool, TransportError> {
733
+ Ok(self.session_present || !self.spawns.lock().unwrap().is_empty())
734
+ }
735
+
736
+ fn list_windows(&self, _session: &SessionName) -> Result<Vec<WindowName>, TransportError> {
737
+ Ok(Vec::new())
738
+ }
739
+
740
+ fn set_session_env(
741
+ &self,
742
+ _session: &SessionName,
743
+ _key: &str,
744
+ _value: &str,
745
+ ) -> Result<SetEnvOutcome, TransportError> {
746
+ Ok(SetEnvOutcome::Applied)
747
+ }
748
+
749
+ fn kill_session(&self, _session: &SessionName) -> Result<(), TransportError> {
750
+ Ok(())
751
+ }
752
+
753
+ fn kill_window(&self, _target: &Target) -> Result<(), TransportError> {
754
+ Ok(())
755
+ }
756
+
757
+ fn attach_session(&self, _session: &SessionName) -> Result<AttachOutcome, TransportError> {
758
+ Ok(AttachOutcome::Attached)
759
+ }
760
+ }