@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
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! step 5 · state — state.json 持久化 / identity / owner-gate / projection(真相源 `state.py`)。
2
3
  //!
3
4
  //! §6 step 5:原子写/锁/self-heal(**bug-084** 血泪)/ team projection / leader receiver identity /
@@ -32,6 +33,7 @@ pub mod selector;
32
33
  use serde_json::Value;
33
34
  use thiserror::Error;
34
35
 
36
+ ///
35
37
  /// Python 真值语义(`null`/`false`/`0`/`""`/`[]`/`{}` → false),`state` 模块共用。
36
38
  pub(crate) fn json_truthy(v: &Value) -> bool {
37
39
  match v {
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! owner-gate:trust own-vs-foreign(§11 / bug-064 / bug-082 血泪;真相源 `state.py`)。
2
3
  //!
3
4
  //! 三核心纯判定(集成 send_message/MCP 是 step 11/14):
@@ -37,6 +38,7 @@ pub struct CallerIdentity {
37
38
  pub leader_session_uuid_source: String,
38
39
  }
39
40
 
41
+ ///
40
42
  /// `check_team_owner`(`state.py:366`)的纯判定版:caller 身份 + 是否有 TEAM_AGENT_ID + liveness 注入。
41
43
  /// 返回 `None`=允许(own / 无 owner / 死 owner pane 可接管);`Some(dict)`=拒绝(team_owner_mismatch)。
42
44
  ///
@@ -123,6 +125,7 @@ fn caller_to_value(c: &CallerIdentity) -> Value {
123
125
  })
124
126
  }
125
127
 
128
+ ///
126
129
  /// `worker_sender_bypasses_owner_gate`(`state.py:400`):worker peer-send 绕过 owner 门。
127
130
  /// 返回 `Some(agent_id)`=绕过;`None`=不绕过(走 owner 门)。
128
131
  pub fn worker_sender_bypasses_owner_gate(
@@ -160,6 +163,7 @@ pub fn worker_sender_bypasses_owner_gate(
160
163
  Some(sender.to_string())
161
164
  }
162
165
 
166
+ ///
163
167
  /// §11 realpath own-vs-foreign(`state.py:449`):**两边 canonicalize** 后全等才判 own。
164
168
  /// macOS `/tmp → /private/tmp` 软链不对称由「两边 canonicalize」消除。canonicalize 失败(路径不存在)
165
169
  /// → 退化为词法绝对比较(此时无软链可解,等价)。**禁 basename/startswith/子串/反推**。
@@ -187,6 +191,7 @@ fn lexical_abs(p: &Path) -> std::path::PathBuf {
187
191
  }
188
192
  }
189
193
 
194
+ ///
190
195
  /// `os.path.realpath` 等价(对抗 P0-B):全路径存在 → `canonicalize`;否则对**最长存在前缀**
191
196
  /// canonicalize(解析其软链)再拼回缺失末段(规范化 `..`/`.`)。这复刻 Python「解析存在部分的
192
197
  /// 软链 + 保留不存在末段」,修掉「父目录是软链 + 末段不存在 → 漏判 own」的 §11 血泪。
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! Stage 2 of identity-boundary unified plan (architect direction 2026-06-23):
2
3
  //! `state::ownership` — single read entry point for `team_owner` lookups.
3
4
  //!
@@ -41,7 +42,7 @@ use crate::state::projection::{read_owner as projection_read_owner, team_state_k
41
42
  /// owner value. CLI status / diagnose use it to surface legacy duplicate
42
43
  /// warnings.
43
44
  #[derive(Debug, Clone, Copy, PartialEq, Eq)]
44
- pub enum OwnershipSource {
45
+ enum OwnershipSource {
45
46
  /// Stage 5 canonical path: `.team/runtime/<team_key>/state.json` (not
46
47
  /// yet emitted — placeholder for forward compatibility).
47
48
  CanonicalPerTeamState,
@@ -57,11 +58,12 @@ pub enum OwnershipSource {
57
58
  /// caller consumes (same shape as `state.get("team_owner")` returned today);
58
59
  /// the `source` is diagnostic.
59
60
  #[derive(Debug, Clone)]
60
- pub struct OwnershipRead<'a> {
61
+ struct OwnershipRead<'a> {
61
62
  pub value: &'a Value,
62
63
  pub source: OwnershipSource,
63
64
  }
64
65
 
66
+ ///
65
67
  /// Stage 2 entry point: read the team owner for the given `team_key` from an
66
68
  /// in-memory state value, honouring the legacy precedence. Returns `None` if
67
69
  /// no source carries a non-empty, valid owner record.
@@ -73,7 +75,7 @@ pub struct OwnershipRead<'a> {
73
75
  /// This deliberately reuses `projection::read_owner` so the pane-id validity
74
76
  /// check (`%N` or all-digits) stays in one place. The repository's value-add
75
77
  /// is the precedence ordering and the `OwnershipSource` tag.
76
- pub fn read_owner_for_team<'a>(state: &'a Value, team_key: &str) -> Option<OwnershipRead<'a>> {
78
+ fn read_owner_for_team<'a>(state: &'a Value, team_key: &str) -> Option<OwnershipRead<'a>> {
77
79
  // Step 1 (Stage 5): canonical per-team state. Stage 2 does not consult
78
80
  // a separate file yet — owner truth still lives in the in-memory state.
79
81
  // Once Stage 5 wires `.team/runtime/<team_key>/state.json` as canonical,
@@ -109,6 +111,7 @@ pub fn read_owner_for_team<'a>(state: &'a Value, team_key: &str) -> Option<Owner
109
111
  None
110
112
  }
111
113
 
114
+ ///
112
115
  /// Convenience: just the JSON value, no diagnostic source. Returns `None`
113
116
  /// when no owner is found. Stable across Stage 5 — only the lookup
114
117
  /// implementation will move.
@@ -116,6 +119,7 @@ pub fn read_owner_value<'a>(state: &'a Value, team_key: &str) -> Option<&'a Valu
116
119
  read_owner_for_team(state, team_key).map(|read| read.value)
117
120
  }
118
121
 
122
+ ///
119
123
  /// Stage 3 (identity-boundary unified plan, architect direction 2026-06-23):
120
124
  /// single write entry point for owner mutations. Pre-Stage-3, owner writes
121
125
  /// were spread across 13 sites (claim/attach/readopt/managed-leader/quick-
@@ -187,6 +191,7 @@ pub fn write_owner(state: &mut Value, team_key: &str, record: OwnershipWrite) {
187
191
  }
188
192
  }
189
193
 
194
+ ///
190
195
  /// Stage 3 top-level cleanup (architect direction 2026-06-24,
191
196
  /// .team/artifacts/stage3-toplevel-cleanup-fix.md): strip the legacy
192
197
  /// top-level `team_owner / leader_receiver / owner_epoch` fields from a
@@ -202,12 +207,13 @@ pub fn write_owner(state: &mut Value, team_key: &str, record: OwnershipWrite) {
202
207
  /// load-time path — legacy single-team states may still have only
203
208
  /// top-level owner and no canonical `teams.<key>` owner, and a blind load
204
209
  /// strip would lose ownership before migration.
205
- pub fn strip_top_level_ownership(root: &mut serde_json::Map<String, Value>) {
210
+ fn strip_top_level_ownership(root: &mut serde_json::Map<String, Value>) {
206
211
  for key in ["team_owner", "leader_receiver", "owner_epoch"] {
207
212
  root.remove(key);
208
213
  }
209
214
  }
210
215
 
216
+ ///
211
217
  /// Stage 3 save-output canonical-aware strip (architect direction
212
218
  /// 2026-06-24, .team/artifacts/stage3-save-strip-fix.md): conditional
213
219
  /// version of `strip_top_level_ownership` used by `state::persist`'s
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! Stage 0 of the identity-boundary unified plan (architect direction
2
3
  //! 2026-06-23): `TeamScope` + `TeamRuntimePaths` foundation.
3
4
  //!
@@ -42,6 +43,7 @@ pub struct TeamScope {
42
43
  }
43
44
 
44
45
  impl TeamScope {
46
+ ///
45
47
  /// Construct a TeamScope from an already-resolved workspace + team_key.
46
48
  /// Callers that have a display name must resolve it through the existing
47
49
  /// `state::selector` machinery first; this constructor refuses an empty
@@ -66,6 +68,7 @@ impl TeamScope {
66
68
  &self.team_key
67
69
  }
68
70
 
71
+ ///
69
72
  /// Convenience: derive the path helper from this scope.
70
73
  pub fn paths(&self) -> TeamRuntimePaths {
71
74
  TeamRuntimePaths::for_scope(self)
@@ -110,6 +113,7 @@ impl TeamRuntimePaths {
110
113
  &self.team_key
111
114
  }
112
115
 
116
+ ///
113
117
  /// `.team/runtime/teams/<team_key>/` — the team's future B3 runtime
114
118
  /// directory. It is not used as product authority until the B3 migration;
115
119
  /// current callers must keep using `runtime_state_path`.
@@ -119,6 +123,7 @@ impl TeamRuntimePaths {
119
123
  .join(&self.team_key)
120
124
  }
121
125
 
126
+ ///
122
127
  /// Transitional runtime spec path. Mirrors the existing
123
128
  /// `runtime_spec_path` helper while specs remain at
124
129
  /// `.team/runtime/<team_key>/team.spec.yaml`.
@@ -126,6 +131,7 @@ impl TeamRuntimePaths {
126
131
  runtime_spec_path(&self.workspace, &self.team_key)
127
132
  }
128
133
 
134
+ ///
129
135
  /// `.team/runtime/teams/<team_key>/state.json` — the canonical per-team
130
136
  /// state path that B3 will start writing. Not used by current product
131
137
  /// code; callers must continue using `runtime_state_path` until B3
@@ -134,11 +140,13 @@ impl TeamRuntimePaths {
134
140
  self.team_dir().join("state.json")
135
141
  }
136
142
 
143
+ ///
137
144
  /// `.team/runtime/teams/<team_key>/coordinator.pid` — future sidecar location.
138
145
  pub fn coordinator_pid_path(&self) -> PathBuf {
139
146
  self.team_dir().join("coordinator.pid")
140
147
  }
141
148
 
149
+ ///
142
150
  /// `.team/runtime/teams/<team_key>/coordinator.log` — future sidecar location.
143
151
  pub fn coordinator_log_path(&self) -> PathBuf {
144
152
  self.team_dir().join("coordinator.log")
@@ -180,6 +188,7 @@ pub enum CommandScope {
180
188
  }
181
189
 
182
190
  impl CommandScope {
191
+ ///
183
192
  /// Resolve the CLI's `--team` argument against the workspace state.
184
193
  /// On any I/O error the empty case is returned — destructive commands
185
194
  /// will run their own selector and surface the real error.
@@ -202,6 +211,7 @@ impl CommandScope {
202
211
  }
203
212
  }
204
213
 
214
+ ///
205
215
  /// Convert to an `Option<String>` for backward compatibility with
206
216
  /// existing `args.team.as_deref()` call sites.
207
217
  pub fn team_key(&self) -> Option<&str> {
@@ -211,11 +221,13 @@ impl CommandScope {
211
221
  }
212
222
  }
213
223
 
224
+ ///
214
225
  /// True iff there are 2+ alive teams and no explicit `--team`.
215
226
  pub fn is_ambiguous(&self) -> bool {
216
227
  matches!(self, Self::Ambiguous(_))
217
228
  }
218
229
 
230
+ ///
219
231
  /// The candidate list when ambiguous; empty otherwise.
220
232
  pub fn candidates(&self) -> &[String] {
221
233
  match self {
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! state.json 持久化(bug-084 韧性;真相源 `state.py:save_runtime_state` + `_self_heal_runtime_state`
2
3
  //! + `runtime.py:_runtime_lock`)。
3
4
  //!
@@ -71,6 +72,7 @@ static RUNTIME_STATE_CACHE: LazyLock<Mutex<HashMap<PathBuf, Value>>> =
71
72
  LazyLock::new(|| Mutex::new(HashMap::new()));
72
73
  static TMP_SEQ: AtomicU64 = AtomicU64::new(0);
73
74
 
75
+ ///
74
76
  /// `state.py:41`。
75
77
  pub fn runtime_state_path(workspace: &Path) -> PathBuf {
76
78
  runtime_dir(workspace).join("state.json")
@@ -202,6 +204,7 @@ impl Drop for RuntimeLock {
202
204
  }
203
205
  }
204
206
 
207
+ ///
205
208
  /// `save_runtime_state`(bug-084)。`state` 是 state.json 的内存 Value(插入序保留)。
206
209
  /// 注:Python 在此还调 `_migrate_state_identity`(identity slice 落地后接入;本 slice 不改 state 内容)。
207
210
  pub fn save_runtime_state(workspace: &Path, state: &Value) -> Result<(), StateError> {
@@ -1135,10 +1138,11 @@ fn self_heal(
1135
1138
  }
1136
1139
  }
1137
1140
 
1141
+ ///
1138
1142
  /// `load_runtime_state`(本 slice 最小:读+parse+缓存;normalize/migration 待 identity slice)。
1139
1143
  /// `normalize_agent_session_state`(`state.py:45`):为每个 agent dict 的 SESSION_STATE_FIELDS
1140
1144
  /// setdefault None(缺则末尾插)。
1141
- pub fn normalize_agent_session_state(state: &mut Value) {
1145
+ fn normalize_agent_session_state(state: &mut Value) {
1142
1146
  let Some(agents) = state.get_mut("agents").and_then(Value::as_object_mut) else {
1143
1147
  return;
1144
1148
  };
@@ -1151,12 +1155,13 @@ pub fn normalize_agent_session_state(state: &mut Value) {
1151
1155
  }
1152
1156
  }
1153
1157
 
1158
+ ///
1154
1159
  /// `_migrate_active_team_key`(`state.py:73`,0.2.6 Family B C6):legacy state 缺 active_team_key
1155
1160
  /// 时 seed 一次。返回是否有改动。
1156
1161
  ///
1157
1162
  /// 注:Python `seed if seed in teams or not teams else seed` 两支均为 `seed`(死三元),
1158
1163
  /// 此处直接 `= seed`,与可观测行为一致。
1159
- pub fn migrate_active_team_key(state: &mut Value) -> bool {
1164
+ fn migrate_active_team_key(state: &mut Value) -> bool {
1160
1165
  if state
1161
1166
  .as_object()
1162
1167
  .is_some_and(|o| o.contains_key("active_team_key"))
@@ -1191,6 +1196,7 @@ pub fn migrate_active_team_key(state: &mut Value) -> bool {
1191
1196
  true
1192
1197
  }
1193
1198
 
1199
+ ///
1194
1200
  /// RM-039-STAT-001 second-round compat normalizer (architect verdict
1195
1201
  /// 2026-06-22). When `active_team_key` names an existing `teams` entry
1196
1202
  /// but root `team_key` is missing, set root `team_key = active_team_key`
@@ -1210,7 +1216,7 @@ pub fn migrate_active_team_key(state: &mut Value) -> bool {
1210
1216
  /// * Does NOT touch any other field (no generic deep merge).
1211
1217
  ///
1212
1218
  /// Returns `true` if state was mutated and the caller should persist.
1213
- pub fn migrate_team_key_to_match_active_team(state: &mut Value) -> bool {
1219
+ fn migrate_team_key_to_match_active_team(state: &mut Value) -> bool {
1214
1220
  let Some(obj) = state.as_object() else {
1215
1221
  return false;
1216
1222
  };
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! projection:team 选择 / top-level 派生视图(真相源 `state.py`,0.2.6 Family B C6-C8)。
2
3
  //!
3
4
  //! `state.teams` 是唯一候选源(`status == "alive"`);顶层 `session_name`/`team_dir`/`agents`/`tasks`
@@ -24,6 +25,7 @@ use crate::state::persist::{
24
25
 
25
26
  pub(crate) const CURRENT_TEAM_ALIAS: &str = "current";
26
27
 
28
+ ///
27
29
  /// `team_state_key`(`state.py:93`):从 team_dir(.name)/spec_path(.parent.name)派生 team key,
28
30
  /// 跳过 `.team`/`runtime`;兜底 `session_name` 或 `"current"`。
29
31
  pub fn team_state_key(state: &Value) -> String {
@@ -184,6 +186,7 @@ fn legacy_owner_team_aliases(entry: &Value) -> impl Iterator<Item = String> + '_
184
186
  .map(str::to_string)
185
187
  }
186
188
 
189
+ ///
187
190
  /// `compact_team_state`(`state.py:105`):剔除 `teams`(team entry 不嵌套全量 teams),保序。
188
191
  pub fn compact_team_state(state: &Value) -> Value {
189
192
  match state.as_object() {
@@ -208,6 +211,7 @@ pub fn state_is_managed_leader(state: &Value) -> bool {
208
211
  !state_is_external_leader(state)
209
212
  }
210
213
 
214
+ ///
211
215
  /// `merge_workspace_team_state`(`state.py:111`):把新启动的 team 并入既有 workspace state。
212
216
  pub fn merge_workspace_team_state(existing: &Value, launched: &Value) -> Value {
213
217
  let launched_key = team_state_key(launched);
@@ -239,6 +243,7 @@ pub fn merge_workspace_team_state(existing: &Value, launched: &Value) -> Value {
239
243
  Value::Object(merged)
240
244
  }
241
245
 
246
+ ///
242
247
  /// `team_state_candidates`(`state.py:131`):唯一候选源 = `state.teams` 中
243
248
  /// `status=="alive"`(大小写不敏感;缺 status/空 视为 alive,但 0.5.26 起
244
249
  /// legacy shutdown 残留「无 status + 全体 agents 处于终态」不再算 alive)。
@@ -263,6 +268,7 @@ pub fn team_state_candidates(state: &Value) -> Map<String, Value> {
263
268
  out
264
269
  }
265
270
 
271
+ ///
266
272
  /// 0.5.26 (`.team/artifacts/stale-team-saveconflict-locate.md` §7.1): 唯一 alive
267
273
  /// 谓词。规则:
268
274
  /// - 非 object → 排除;
@@ -348,8 +354,9 @@ pub(crate) fn agent_status_is_terminal(status: &str) -> bool {
348
354
  )
349
355
  }
350
356
 
357
+ ///
351
358
  /// `format_team_candidates`(`state.py:148`):候选摘要串(key 排序;agents 排序逗号连,空→`-`)。
352
- pub fn format_team_candidates(team_states: &Map<String, Value>) -> String {
359
+ fn format_team_candidates(team_states: &Map<String, Value>) -> String {
353
360
  if team_states.is_empty() {
354
361
  return "No team state was found.".to_string();
355
362
  }
@@ -412,6 +419,7 @@ fn valid_owner_pane_id(pane_id: &str) -> bool {
412
419
  pane_id.starts_with('%') || pane_id.chars().all(|ch| ch.is_ascii_digit())
413
420
  }
414
421
 
422
+ ///
415
423
  /// `_project_top_level_view`(`state.py:167`,C8):把 `teams[team_key]` 投影成扁平顶层视图。
416
424
  pub fn project_top_level_view(state: &Value, team_key: &str) -> Value {
417
425
  // entry = _team_entry_from_state(...) or {} —— 空 dict 亦走 {} 分支(同内容)。
@@ -488,6 +496,7 @@ pub struct TeamScopeResolution {
488
496
  pub state: Value,
489
497
  }
490
498
 
499
+ ///
491
500
  /// Resolve a requested team selector once, returning both the canonical map key and its
492
501
  /// projected state. Callers must carry `canonical_team_key` forward rather than deriving team
493
502
  /// identity again from the projection or the original alias.
@@ -646,6 +655,7 @@ pub fn resolve_runtime_team_scope(
646
655
  ))
647
656
  }
648
657
 
658
+ ///
649
659
  /// `select_runtime_state`(`state.py:193`):compatibility projection wrapper.
650
660
  pub fn select_runtime_state(workspace: &Path, team: Option<&str>) -> Result<Value, StateError> {
651
661
  resolve_runtime_team_scope(workspace, team).map(|resolved| resolved.state)
@@ -677,8 +687,9 @@ fn team_selector_matches(team: &str, key: &str, value: &Value) -> bool {
677
687
  .is_some_and(|name| team == name)
678
688
  }
679
689
 
690
+ ///
680
691
  /// `ambiguous_team_target_result`(`state.py:226`):无显式 team 且多候选 → 拒绝 dict;否则 None。
681
- pub fn ambiguous_team_target_result(state: &Value) -> Option<Value> {
692
+ fn ambiguous_team_target_result(state: &Value) -> Option<Value> {
682
693
  let alive = team_state_candidates(state);
683
694
  let active = state
684
695
  .get("active_team_key")
@@ -704,6 +715,7 @@ pub fn ambiguous_team_target_result(state: &Value) -> Option<Value> {
704
715
  }))
705
716
  }
706
717
 
718
+ ///
707
719
  /// `resolve_team_scoped_state`(`state.py:243`):返回 `(state, refusal)`,二者恰一为 Some。
708
720
  /// `team=None` 且歧义 → refusal;否则 select,RuntimeError → `team_target_unresolved` refusal。
709
721
  pub fn resolve_team_scoped_state(
@@ -732,6 +744,7 @@ pub fn resolve_team_scoped_state(
732
744
  }
733
745
  }
734
746
 
747
+ ///
735
748
  /// `save_team_scoped_state`(`state.py:594`):把 team-scoped(投影后)state 写回 workspace,**保全**
736
749
  /// 多 team workspace 里其他 team 的持久态。单 team(磁盘无 `teams` 且 primary key == target)退化为
737
750
  /// 纯 `save_runtime_state`(字节等价);多 team 时把本 team 落到 `teams[target_key]=compact(...)`,顶层
@@ -1,3 +1,4 @@
1
+
1
2
  use super::StateWriteIntent;
2
3
 
3
4
  impl<'a> StateWriteIntent<'a> {
@@ -1,3 +1,4 @@
1
+
1
2
  use super::{reapply_scope, ReapplyScope, StateWriteIntent};
2
3
 
3
4
  #[test]
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! StateRepository facade.
2
3
  //!
3
4
  //! Ref:
@@ -73,16 +74,19 @@ pub struct StateRepository<'a> {
73
74
  }
74
75
 
75
76
  impl<'a> StateRepository<'a> {
77
+ ///
76
78
  /// Construct a repository bound to a workspace root.
77
79
  pub fn new(workspace: &'a Path) -> Self {
78
80
  Self { workspace }
79
81
  }
80
82
 
83
+ ///
81
84
  /// Load the raw workspace state document.
82
85
  pub fn load_workspace(&self) -> Result<Value, StateError> {
83
86
  helper_load_workspace(self.workspace)
84
87
  }
85
88
 
89
+ ///
86
90
  /// Load the canonical workspace document without running read-time
87
91
  /// migrations. `None` preserves the legacy raw-reader distinction between
88
92
  /// a missing file and a present empty/default document.
@@ -96,6 +100,7 @@ impl<'a> StateRepository<'a> {
96
100
  serde_json::from_str(&text).map(Some).map_err(StateError::from)
97
101
  }
98
102
 
103
+ ///
99
104
  /// Resolve a team-scoped projection using the existing projection selector.
100
105
  /// `team_key = None` selects the ambient team the same way as pre-S1a.
101
106
  pub fn load_team(&self, team_key: Option<&str>) -> Result<Value, StateError> {
@@ -110,6 +115,7 @@ impl<'a> StateRepository<'a> {
110
115
  })
111
116
  }
112
117
 
118
+ ///
113
119
  /// Persist `state` under the supplied intent. S1a dispatch is a pure
114
120
  /// forward call to the same legacy helper family that the caller used
115
121
  /// before S1a; the intent selects the family, not the merge semantics.
@@ -117,6 +123,7 @@ impl<'a> StateRepository<'a> {
117
123
  route_direct(self.workspace, intent, state)
118
124
  }
119
125
 
126
+ ///
120
127
  /// Persist `state` and let `reapply` re-materialize the write on
121
128
  /// SaveConflict, matching the pre-S1a `_reapplying_after_conflict` helper
122
129
  /// behavior for reapply-shaped intents.
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! Active team workspace selector.
2
3
 
3
4
  use std::path::{Path, PathBuf};
@@ -146,6 +146,45 @@ fn backend_with_stdin(
146
146
  )
147
147
  }
148
148
 
149
+ #[test]
150
+ fn a21_spawn_command_too_long_is_structured_before_transport() {
151
+ let (backend, recorded) = backend_with(MockResp::Out(ok("%0\n")), Vec::new());
152
+ let session = SessionName::new("a21-session");
153
+ let window = WindowName::new("worker-long-role");
154
+ let argv = vec!["codex".to_string(), "x".repeat(20_000)];
155
+
156
+ let error = backend
157
+ .spawn_first(
158
+ &session,
159
+ &window,
160
+ &argv,
161
+ Path::new("/tmp/a21-workspace"),
162
+ &BTreeMap::new(),
163
+ )
164
+ .expect_err("oversized spawn must fail before tmux transport");
165
+ let text = error.to_string();
166
+ assert!(
167
+ text.contains("command_too_long"),
168
+ "structured error: {text}"
169
+ );
170
+ assert!(
171
+ text.contains("worker-long-role"),
172
+ "seat must be named: {text}"
173
+ );
174
+ assert!(
175
+ text.contains("actual_bytes="),
176
+ "actual length must be visible: {text}"
177
+ );
178
+ assert!(
179
+ text.contains("limit_bytes="),
180
+ "limit must be visible: {text}"
181
+ );
182
+ assert!(
183
+ recorded.lock().unwrap().is_empty(),
184
+ "tmux must not be called: {text}"
185
+ );
186
+ }
187
+
149
188
  fn svec(items: &[&str]) -> Vec<String> {
150
189
  items.iter().map(|s| (*s).to_string()).collect()
151
190
  }
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! Concrete tmux `Transport` backend (SKELETON) — the real executor that runs `tmux <argv>`.
2
3
  //!
3
4
  //! step 9 shipped the [`crate::transport::Transport`] trait + the pure tmux argv-builders
@@ -77,6 +78,11 @@ const COMMAND_TIMEOUT: Duration = Duration::from_secs(5);
77
78
  const SPAWN_IDENTITY_TIMEOUT: Duration = Duration::from_millis(500);
78
79
  const SPAWN_IDENTITY_POLL_INTERVAL: Duration = Duration::from_millis(25);
79
80
 
81
+ /// Keep the final tmux argv below the ~16 KiB command envelope observed on macOS.
82
+ /// This is checked after shell quoting and socket arguments are applied, before
83
+ /// the external tmux process is called.
84
+ pub const TMUX_SPAWN_COMMAND_LIMIT_BYTES: usize = 16_000;
85
+
80
86
  impl CommandRunner for RealCommandRunner {
81
87
  fn run(&self, argv: &[String]) -> Result<CommandOutput, std::io::Error> {
82
88
  self.run_inner(argv, None)
@@ -390,6 +396,27 @@ impl TmuxBackend {
390
396
  if self.socket.is_none() {
391
397
  return;
392
398
  }
399
+ // A server-level kill is only safe after the caller has removed every
400
+ // resource it owns. A non-empty server may carry a pre-existing team
401
+ // (or a leader owned by another launcher), so fail closed instead of
402
+ // turning an endpoint cleanup into a shared-server teardown. An
403
+ // inconclusive probe is also not proof of ownership.
404
+ let server_is_empty = match self.list_targets() {
405
+ Ok(targets) => targets.is_empty(),
406
+ Err(_) => false,
407
+ };
408
+ if !server_is_empty {
409
+ if let Some(workspace) = &self.event_workspace {
410
+ let _ = crate::event_log::EventLog::new(workspace).write(
411
+ "tmux.kill_server_skipped_nonempty_or_unknown",
412
+ serde_json::json!({
413
+ "reason": "server_ownership_not_proven",
414
+ "endpoint": self.tmux_endpoint(),
415
+ }),
416
+ );
417
+ }
418
+ return;
419
+ }
393
420
  let argv = self.tmux_argv(&["tmux".to_string(), "kill-server".to_string()]);
394
421
  let _ = self.runner.run(&argv);
395
422
  }
@@ -787,6 +814,16 @@ impl TmuxBackend {
787
814
  first: bool,
788
815
  ) -> Result<SpawnResult, TransportError> {
789
816
  let spawn_argv = tmux_spawn_argv(session, window, command, first);
817
+ self.validate_spawn_command(
818
+ &spawn_argv,
819
+ if first {
820
+ "tmux.new-session"
821
+ } else {
822
+ "tmux.new-window"
823
+ },
824
+ session,
825
+ window,
826
+ )?;
790
827
  let output = self.run_spawn(&spawn_argv)?;
791
828
  let pane = output.stdout.trim();
792
829
  if pane.is_empty() {
@@ -897,6 +934,7 @@ impl TmuxBackend {
897
934
  "-lc".to_string(),
898
935
  command,
899
936
  ];
937
+ self.validate_spawn_command(&split_argv, "tmux.split-window", session, window)?;
900
938
  let output = self.run_spawn(&split_argv)?;
901
939
  let pane = output.stdout.trim();
902
940
  if pane.is_empty() {
@@ -932,6 +970,31 @@ impl TmuxBackend {
932
970
  }
933
971
  }
934
972
 
973
+ fn validate_spawn_command(
974
+ &self,
975
+ argv: &[String],
976
+ segment: &str,
977
+ session: &SessionName,
978
+ window: &WindowName,
979
+ ) -> Result<(), TransportError> {
980
+ let final_argv = self.tmux_argv(argv);
981
+ let actual_bytes = final_argv
982
+ .iter()
983
+ .map(|arg| arg.len().saturating_add(1))
984
+ .sum::<usize>();
985
+ if actual_bytes > TMUX_SPAWN_COMMAND_LIMIT_BYTES {
986
+ return Err(TransportError::CommandTooLong {
987
+ backend: BackendKind::Tmux,
988
+ segment: segment.to_string(),
989
+ session: session.as_str().to_string(),
990
+ window: window.as_str().to_string(),
991
+ actual_bytes,
992
+ limit_bytes: TMUX_SPAWN_COMMAND_LIMIT_BYTES,
993
+ });
994
+ }
995
+ Ok(())
996
+ }
997
+
935
998
  fn run_spawn(&self, argv: &[String]) -> Result<CommandOutput, TransportError> {
936
999
  let argv = self.tmux_argv(argv);
937
1000
  let output = self
@@ -399,6 +399,9 @@ fn append_worker_pane_binding_issues(
399
399
  let Some(agents) = state.get("agents").and_then(Value::as_object) else {
400
400
  return;
401
401
  };
402
+ if agents.is_empty() {
403
+ return;
404
+ }
402
405
  let Ok(live_targets) = backend.list_targets() else {
403
406
  return;
404
407
  };
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! step 9 · transport — 控制面 trait + typed payload(ROUND-0 SKELETON).
2
3
  //!
3
4
  //! 设计真相源:`docs/phase0/transport-backend-design.md` §1(Rust 草图:Target /
@@ -486,6 +487,20 @@ pub enum TransportError {
486
487
  code: Option<i32>,
487
488
  stderr: String,
488
489
  },
490
+ /// The final tmux spawn argv is too large for the transport command envelope.
491
+ /// Keep this separate from `Subprocess` so callers do not have to parse a
492
+ /// provider-sized argv dump to learn the actionable limit.
493
+ #[error(
494
+ "command_too_long: backend={backend:?}; segment={segment}; session={session}; window={window}; actual_bytes={actual_bytes}; limit_bytes={limit_bytes}"
495
+ )]
496
+ CommandTooLong {
497
+ backend: BackendKind,
498
+ segment: String,
499
+ session: String,
500
+ window: String,
501
+ actual_bytes: usize,
502
+ limit_bytes: usize,
503
+ },
489
504
  /// wezterm mux 连不上 / tmux server 不在。
490
505
  #[error("mux unavailable on {backend:?}: {detail}")]
491
506
  MuxUnavailable {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@team-agent/installer",
3
- "version": "0.5.60",
3
+ "version": "0.5.62",
4
4
  "description": "npx installer for Team Agent",
5
5
  "keywords": [
6
6
  "codex",
@@ -20,9 +20,9 @@
20
20
  "team-agent-installer": "npm/install.mjs"
21
21
  },
22
22
  "optionalDependencies": {
23
- "@team-agent/cli-darwin-arm64": "0.5.60",
24
- "@team-agent/cli-darwin-x64": "0.5.60",
25
- "@team-agent/cli-linux-x64": "0.5.60"
23
+ "@team-agent/cli-darwin-arm64": "0.5.62",
24
+ "@team-agent/cli-darwin-x64": "0.5.62",
25
+ "@team-agent/cli-linux-x64": "0.5.62"
26
26
  },
27
27
  "scripts": {
28
28
  "postinstall": "node npm/bincheck.mjs",