@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
  use super::mailbox::maybe_enqueue_offline_leader_mailbox;
2
3
  use super::persist::persist_resolved_target;
3
4
  use crate::cli::{CliError, SendArgs};
@@ -207,7 +208,7 @@ pub(super) fn send_to_logical_to(
207
208
  &args.workspace,
208
209
  name,
209
210
  content,
210
- args.sender.as_str(),
211
+ args.sender.display_name(),
211
212
  args.task.as_deref(),
212
213
  &error,
213
214
  )? {
@@ -359,3 +360,207 @@ pub(super) fn send_to_resolved_name(
359
360
  }
360
361
  Ok(value)
361
362
  }
363
+
364
+ #[cfg(test)]
365
+ mod a13_red_tests {
366
+ #![allow(clippy::expect_used, clippy::unwrap_used, clippy::panic)]
367
+
368
+ use super::send_to_resolved_name;
369
+ use crate::cli::named_address::{NamedTargetKind, ResolvedNamedAddress};
370
+ use crate::cli::SendArgs;
371
+ use crate::message_store::MessageStore;
372
+ use crate::messaging::TrustedSender;
373
+ use crate::model::ids::AgentId;
374
+ use serde_json::{json, Value};
375
+ use std::path::{Path, PathBuf};
376
+ use std::sync::atomic::{AtomicU64, Ordering};
377
+
378
+ fn fresh_workspace(tag: &str) -> PathBuf {
379
+ static NEXT: AtomicU64 = AtomicU64::new(0);
380
+ let workspace = std::env::temp_dir().join(format!(
381
+ "team-agent-a13-{tag}-{}-{}",
382
+ std::process::id(),
383
+ NEXT.fetch_add(1, Ordering::Relaxed)
384
+ ));
385
+ let _ = std::fs::remove_dir_all(&workspace);
386
+ std::fs::create_dir_all(workspace.join(".team/runtime")).expect("create fixture");
387
+ workspace
388
+ }
389
+
390
+ fn seed_team(workspace: &Path, team: &str) {
391
+ let session = format!("team-{team}");
392
+ let state = json!({
393
+ "active_team_key": team,
394
+ "status": "alive",
395
+ "session_name": session,
396
+ "agents": {},
397
+ "teams": {
398
+ team: {
399
+ "status": "alive",
400
+ "session_name": session,
401
+ "agents": {},
402
+ "leader_receiver": {"status": "unbound"}
403
+ }
404
+ }
405
+ });
406
+ crate::state::persist::save_runtime_state(workspace, &state).expect("save fixture state");
407
+ MessageStore::open(workspace).expect("create fixture store");
408
+ }
409
+
410
+ fn send_args(workspace: &Path) -> SendArgs {
411
+ SendArgs {
412
+ target: None,
413
+ message: vec!["payload".to_string()],
414
+ targets: None,
415
+ workspace: workspace.to_path_buf(),
416
+ team: None,
417
+ task: None,
418
+ sender: TrustedSender::from_runtime_identity_with_source(
419
+ AgentId::new("worker-1"),
420
+ workspace.display(),
421
+ "sender-team",
422
+ ),
423
+ no_ack: false,
424
+ no_wait: true,
425
+ watch_result: false,
426
+ timeout: 0.0,
427
+ confirm_human: false,
428
+ json: true,
429
+ message_id: None,
430
+ presentation: crate::messaging::presentation::PresentationRequest::default(),
431
+ pane: None,
432
+ to_name: Some("other-team/leader".to_string()),
433
+ to_leader: None,
434
+ }
435
+ }
436
+
437
+ fn leader_address(sender_workspace: &Path, target_workspace: &Path) -> ResolvedNamedAddress {
438
+ ResolvedNamedAddress {
439
+ raw_name: format!("{}::other-team/leader", target_workspace.display()),
440
+ target_kind: NamedTargetKind::Leader,
441
+ sender_workspace: sender_workspace.to_path_buf(),
442
+ target_workspace: target_workspace.to_path_buf(),
443
+ team_key: Some("other-team".to_string()),
444
+ agent_id: None,
445
+ pane_id: String::new(),
446
+ session_name: Some("team-other-team".to_string()),
447
+ window_name: None,
448
+ tmux_endpoint: None,
449
+ transport_kind: Some("direct_tmux".to_string()),
450
+ app_server: None,
451
+ state_pane_id: None,
452
+ state_pane_stale: false,
453
+ agent_status: None,
454
+ warning: None,
455
+ }
456
+ }
457
+
458
+ fn message_count(workspace: &Path) -> i64 {
459
+ let store = MessageStore::open(workspace).expect("open fixture store");
460
+ crate::db::schema::open_db(store.db_path())
461
+ .expect("open fixture database")
462
+ .query_row("select count(*) from messages", [], |row| row.get(0))
463
+ .expect("count fixture messages")
464
+ }
465
+
466
+ #[test]
467
+ fn a13_worker_cross_workspace_leader_send_preserves_target_scope() {
468
+ let sender_workspace = fresh_workspace("sender");
469
+ let target_workspace = fresh_workspace("target");
470
+ seed_team(&target_workspace, "other-team");
471
+ let mut args = send_args(&sender_workspace);
472
+ let to_name = format!("{}::other-team/leader", target_workspace.display());
473
+ args.to_name = Some(to_name.clone());
474
+ let value = super::send_to_logical_to(&args, &to_name, "payload")
475
+ .expect("cross-workspace leader send must preserve a structured outcome");
476
+ assert_eq!(
477
+ value["to_name"],
478
+ json!(to_name),
479
+ "cross-workspace receipt must retain the original target: {value}"
480
+ );
481
+ assert_eq!(
482
+ value["sender"],
483
+ json!(format!(
484
+ "{}::sender-team/worker-1",
485
+ sender_workspace.display()
486
+ )),
487
+ "cross-workspace receipt must retain the full sender: {value}"
488
+ );
489
+ assert_eq!(
490
+ message_count(&sender_workspace),
491
+ 0,
492
+ "cross-workspace send must not fold into the sender workspace"
493
+ );
494
+ assert_eq!(
495
+ message_count(&target_workspace),
496
+ 1,
497
+ "cross-workspace send must persist in the target workspace"
498
+ );
499
+
500
+ let _ = std::fs::remove_dir_all(sender_workspace);
501
+ let _ = std::fs::remove_dir_all(target_workspace);
502
+ }
503
+
504
+ #[test]
505
+ fn a13_worker_cross_workspace_identity_is_preserved_in_row_and_render() {
506
+ let sender_workspace = fresh_workspace("identity-sender");
507
+ let target_workspace = fresh_workspace("identity-target");
508
+ seed_team(&target_workspace, "other-team");
509
+ let mut args = send_args(&sender_workspace);
510
+ args.to_name = Some(format!("{}::other-team/leader", target_workspace.display()));
511
+ let resolved = leader_address(&sender_workspace, &target_workspace);
512
+
513
+ send_to_resolved_name(&args, &resolved, "identity payload")
514
+ .expect("cross-workspace identity send must persist");
515
+ let store = MessageStore::open(&target_workspace).expect("open target store");
516
+ let connection = crate::db::schema::open_db(store.db_path()).expect("open target db");
517
+ let sender: String = connection
518
+ .query_row("select sender from messages limit 1", [], |row| row.get(0))
519
+ .expect("read sender");
520
+ let expected = format!("{}::sender-team/worker-1", sender_workspace.display());
521
+ assert_eq!(
522
+ sender, expected,
523
+ "durable row must retain the full sender identity"
524
+ );
525
+ let rendered = crate::messaging::delivery::render_message(
526
+ &sender,
527
+ None,
528
+ "identity payload",
529
+ "msg-a13",
530
+ );
531
+ assert!(
532
+ rendered.contains(&expected),
533
+ "rendered pane must show full sender: {rendered}"
534
+ );
535
+ assert!(
536
+ !rendered.contains("from leader"),
537
+ "worker must not render as leader: {rendered}"
538
+ );
539
+
540
+ let _ = std::fs::remove_dir_all(sender_workspace);
541
+ let _ = std::fs::remove_dir_all(target_workspace);
542
+ }
543
+
544
+ #[test]
545
+ fn a13_worker_in_workspace_leader_send_remains_allowed() {
546
+ let workspace = fresh_workspace("in-workspace");
547
+ seed_team(&workspace, "other-team");
548
+ let args = send_args(&workspace);
549
+ let resolved = leader_address(&workspace, &workspace);
550
+
551
+ let value = send_to_resolved_name(&args, &resolved, "payload")
552
+ .expect("in-workspace leader send should preserve the existing path");
553
+ assert_ne!(
554
+ value["reason"],
555
+ Value::String("target_out_of_scope".to_string()),
556
+ "in-workspace send must not be classified as cross-workspace: {value}"
557
+ );
558
+ assert_eq!(
559
+ message_count(&workspace),
560
+ 1,
561
+ "allowed send must persist once"
562
+ );
563
+
564
+ let _ = std::fs::remove_dir_all(workspace);
565
+ }
566
+ }
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! cli · send — `cmd_send` + target 解析(`_send_target`)+ `SendArgs`→`SendOptions` 翻译
2
3
  //! (`send_options_from_args`,旗标取反语义)。
3
4
 
@@ -180,7 +181,7 @@ pub fn send_to_canonical_leader_target(
180
181
  }
181
182
 
182
183
  /// `_send_target`(`commands.py:181-184`):`--to` comma-split fanout / `target` 单值 / None。
183
- pub fn send_target(targets: Option<&str>, target: Option<&str>) -> MessageTarget {
184
+ pub(super) fn send_target(targets: Option<&str>, target: Option<&str>) -> MessageTarget {
184
185
  if let Some(targets) = targets.filter(|s| !s.is_empty()) {
185
186
  let recipients: Vec<String> = targets
186
187
  .split(',')
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! C1 command registry: one catalog for help visibility, known-command gates,
2
3
  //! suggestion candidates, and command-tier governance.
3
4
 
@@ -76,6 +77,7 @@ pub(crate) enum DispatchKind {
76
77
  InstallSkill,
77
78
  Profile,
78
79
  Collect,
80
+ Results,
79
81
  Diagnose,
80
82
  Preflight,
81
83
  WaitReady,
@@ -120,6 +122,7 @@ pub(crate) const ALL_DISPATCH_KINDS: &[DispatchKind] = &[
120
122
  DispatchKind::InstallSkill,
121
123
  DispatchKind::Profile,
122
124
  DispatchKind::Collect,
125
+ DispatchKind::Results,
123
126
  DispatchKind::Diagnose,
124
127
  DispatchKind::Preflight,
125
128
  DispatchKind::WaitReady,
@@ -155,9 +158,10 @@ pub(crate) struct CommandSpec {
155
158
  #[rustfmt::skip]
156
159
  pub(crate) const COMMAND_SPECS: &[CommandSpec] = &[
157
160
  CommandSpec { name: "quick-start", tier: CommandTier::Core, category: CommandCategory::Start, kind: CommandKind::Dispatch(DispatchKind::QuickStart), summary: "start or attach a team from TEAM.md", usage: "usage: team-agent quick-start [TEAMDIR] [--workspace WORKSPACE] [--name NAME] [--team-id TEAM|--team TEAM] [--yes] [--no-display] [--backend tmux|conpty] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
158
- CommandSpec { name: "send", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Send), summary: "persist a message for an in-team short name or fully-qualified logical recipient", usage: "usage: team-agent send TO MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--presentation-sink leader|casefile|silent --message-class CLASS [--case-id CASE]] [--json]\nTO forms are co-equal: an in-team short name (for example `team-agent send reviewer \"Review\"`) or `<workspace>::<team>/<agent>`.", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: Some("next compatibility release"), action: Some("use positional logical TO and the returned message id"), governance: None },
161
+ CommandSpec { name: "send", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Send), summary: "persist a message for an in-team short name or fully-qualified logical recipient", usage: "usage: team-agent send TO MESSAGE... [--workspace WORKSPACE] [--team TEAM] [--mailbox] [--json]\nTO forms are co-equal: an in-team short name (for example `team-agent send reviewer \"Review\"`) or `<workspace>::<team>/<agent>`.", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: Some("next compatibility release"), action: Some("use positional logical TO and the returned message id"), governance: None },
159
162
  CommandSpec { name: "status", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Status), summary: "show current team status", usage: "usage: team-agent status [AGENT] [--workspace WORKSPACE] [--team TEAM] [--summary|--json] [--detail]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
160
163
  CommandSpec { name: "collect", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Collect), summary: "collect reported results", usage: "usage: team-agent collect [--workspace WORKSPACE] [--team TEAM] [--result-file FILE] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
164
+ CommandSpec { name: "results", tier: CommandTier::Core, category: CommandCategory::Daily, kind: CommandKind::Dispatch(DispatchKind::Results), summary: "read reported results for a case", usage: "usage: team-agent results --case CASE_ID [--workspace WORKSPACE] [--team TEAM] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
161
165
  CommandSpec { name: "restart", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Restart), summary: "restart the selected team", usage: "usage: team-agent restart [WORKSPACE] [--team TEAM] [--allow-fresh] [--session-converge-deadline SECONDS] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
162
166
  CommandSpec { name: "shutdown", tier: CommandTier::Core, category: CommandCategory::TeamLifecycle, kind: CommandKind::Dispatch(DispatchKind::Shutdown), summary: "stop the selected team", usage: "usage: team-agent shutdown [--workspace WORKSPACE] [--team TEAM] [--keep-logs] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::No, alias_of: None, sunset: None, action: None, governance: None },
163
167
  CommandSpec { name: "add-agent", tier: CommandTier::Core, category: CommandCategory::WorkerLifecycle, kind: CommandKind::Dispatch(DispatchKind::AddAgent), summary: "add or force-recreate a worker", usage: "usage: team-agent add-agent AGENT --role-file FILE [--force] [--workspace WORKSPACE] [--team TEAM] [--no-display] [--json]", default_help: true, command_help: true, suggestion_index: true, token_usage: TokenUsage::Conditional, alias_of: None, sunset: None, action: None, governance: None },
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! cli · status — 五行 triage summary 渲染 + agent 分类计数(commands.py
2
3
  //! `_format_status_summary` / `_agent_summary_counts` / `_interaction_counts` /
3
4
  //! `_latest_result_line` + `cmd_status` 的 compact 不变量)。
@@ -100,7 +101,7 @@ fn stale_agent_bucket(agent: &Value) -> Option<SummaryBucket> {
100
101
  }
101
102
 
102
103
  /// `_interaction_counts`(`commands.py:292-306`):遍历 agents 的 `interacted` 字段。
103
- pub fn interaction_counts(agents: &Value) -> InteractionCounts {
104
+ pub(super) fn interaction_counts(agents: &Value) -> InteractionCounts {
104
105
  let mut counts = InteractionCounts::default();
105
106
  let Some(agent_map) = agents.as_object() else {
106
107
  return counts;
@@ -1,3 +1,4 @@
1
+ //!
1
2
  use super::*;
2
3
 
3
4
  pub(super) fn compact_status(full: Value) -> Value {
@@ -1,3 +1,4 @@
1
+ //!
1
2
  use crate::cli::CliError;
2
3
  use serde_json::{json, Value};
3
4
 
@@ -1,3 +1,4 @@
1
+ //!
1
2
  use super::*;
2
3
 
3
4
  pub fn inbox(
@@ -393,12 +393,16 @@ pub(super) fn count_undelivered_backlog(
393
393
  conn: &rusqlite::Connection,
394
394
  owner_team_id: Option<&str>,
395
395
  ) -> Result<i64, CliError> {
396
- // Backlog statuses chosen to mirror what `deliver_pending` would pick up.
396
+ // Delivery debt is broader than requeue eligibility: a physically injected
397
+ // row awaiting a future receipt remains undelivered even though it must not
398
+ // be claimed or injected again.
397
399
  let sql = match owner_team_id {
398
400
  Some(_) => "select count(*) from messages
399
- where owner_team_id = ?1 and status in ('accepted','pending','queued','queued_until_trust')",
401
+ where owner_team_id = ?1 and status in
402
+ ('accepted','pending','queued','queued_until_trust','injected_awaiting_receipt')",
400
403
  None => "select count(*) from messages
401
- where status in ('accepted','pending','queued','queued_until_trust')",
404
+ where status in
405
+ ('accepted','pending','queued','queued_until_trust','injected_awaiting_receipt')",
402
406
  };
403
407
  let count: i64 = match owner_team_id {
404
408
  Some(team) => conn
@@ -135,6 +135,17 @@ fn assert_kind(
135
135
  assert!(n38.contains("Log: "), "{n38}");
136
136
  }
137
137
 
138
+ fn assert_broad_leader_not_attached(err: NamedAddressError) {
139
+ assert_eq!(err.kind, NamedAddressErrorKind::LeaderNotAttached);
140
+ assert_eq!(err.channel_reason, None);
141
+ let value = err.to_json();
142
+ assert_eq!(value["reason"], json!("leader_not_attached"));
143
+ assert!(
144
+ value.get("channel_reason").is_none(),
145
+ "broad refusal must not expose a typed channel reason: {value}"
146
+ );
147
+ }
148
+
138
149
  fn named_send_args(
139
150
  ws: &Path,
140
151
  to_name: Option<&str>,
@@ -372,6 +383,60 @@ fn resolve_leader_name_not_live() {
372
383
  let _ = std::fs::remove_dir_all(&ws);
373
384
  }
374
385
 
386
+ #[test]
387
+ fn resolve_leader_receiver_not_attached_stays_broad() {
388
+ let ws = named_ws("leader-receiver-not-attached");
389
+ let mut team = leader_team("alpha-session", "%leader", "/tmp/leader.sock");
390
+ team["leader_receiver"]["status"] = json!("rebind_required");
391
+ seed_state(
392
+ &ws,
393
+ state_with_teams(json!({
394
+ "alpha": team
395
+ })),
396
+ );
397
+ let transport = OfflineTransport::new()
398
+ .with_tmux_endpoint("/tmp/leader.sock")
399
+ .with_targets(vec![pane("alpha-session-leader", "codex", "%leader")]);
400
+
401
+ let err = resolve_name_with_transport(&ws, "alpha/leader", &transport).unwrap_err();
402
+ assert_broad_leader_not_attached(err);
403
+ let _ = std::fs::remove_dir_all(&ws);
404
+ }
405
+
406
+ #[test]
407
+ fn resolve_leader_pane_not_live_stays_broad() {
408
+ let ws = named_ws("leader-pane-not-live");
409
+ seed_state(
410
+ &ws,
411
+ state_with_teams(json!({
412
+ "alpha": leader_team("alpha-session", "%missing", "/tmp/leader.sock")
413
+ })),
414
+ );
415
+ let transport = OfflineTransport::new().with_tmux_endpoint("/tmp/leader.sock");
416
+
417
+ let err = resolve_name_with_transport(&ws, "alpha/leader", &transport).unwrap_err();
418
+ assert_broad_leader_not_attached(err);
419
+ let _ = std::fs::remove_dir_all(&ws);
420
+ }
421
+
422
+ #[test]
423
+ fn resolve_leader_probe_failure_stays_broad() {
424
+ let ws = named_ws("leader-probe-failed");
425
+ seed_state(
426
+ &ws,
427
+ state_with_teams(json!({
428
+ "alpha": leader_team("alpha-session", "%leader", "/tmp/leader.sock")
429
+ })),
430
+ );
431
+ let transport = OfflineTransport::new()
432
+ .with_tmux_endpoint("/tmp/leader.sock")
433
+ .with_list_targets_error("injected leader pane probe failure");
434
+
435
+ let err = resolve_name_with_transport(&ws, "alpha/leader", &transport).unwrap_err();
436
+ assert_broad_leader_not_attached(err);
437
+ let _ = std::fs::remove_dir_all(&ws);
438
+ }
439
+
375
440
  #[test]
376
441
  fn resolve_leader_name_after_rebind() {
377
442
  let ws = named_ws("leader-rebind");
@@ -122,6 +122,44 @@ fn profile_init_routes_and_creates_secret_boundary_files() {
122
122
  let _ = std::fs::remove_dir_all(&ws);
123
123
  }
124
124
 
125
+ #[test]
126
+ fn compatible_api_profile_init_template_contains_common_five_fields() {
127
+ let ws = tmp_workspace();
128
+ let code = run(
129
+ &profile_argv(&[
130
+ "profile",
131
+ "init",
132
+ "compat",
133
+ "--workspace",
134
+ ".",
135
+ "--auth-mode",
136
+ "compatible_api",
137
+ "--json",
138
+ ]),
139
+ &ws,
140
+ );
141
+ assert_eq!(
142
+ code,
143
+ ExitCode::Ok,
144
+ "compatible_api profile init must route and exit 0"
145
+ );
146
+
147
+ let expected =
148
+ "AUTH_MODE=compatible_api\nPROFILE_NAME=compat\nBASE_URL=\nAPI_KEY=\nMODEL=\n";
149
+ let dir = profiles_dir(&ws);
150
+ assert_eq!(
151
+ std::fs::read_to_string(dir.join("compat.env")).unwrap(),
152
+ expected,
153
+ "compatible_api profile must expose the documented five-field template"
154
+ );
155
+ assert_eq!(
156
+ std::fs::read_to_string(dir.join("compat.example.env")).unwrap(),
157
+ expected,
158
+ "compatible_api example must expose the documented five-field template"
159
+ );
160
+ let _ = std::fs::remove_dir_all(&ws);
161
+ }
162
+
125
163
  // Golden source:
126
164
  // - profiles/core.py:95-119 doctor_profile returns ok=true for existing profiles and
127
165
  // ok=false for missing profiles; parser.py:506-508 maps result.ok false to exit 1.
@@ -1,3 +1,4 @@
1
+ //!
1
2
  //! cli · types — 错误信封 / 退出码 / 命令结果载体 / leader passthrough 参数 /
2
3
  //! 每子命令的 clap-style arg 结构 / 五行 summary 计数桶。
3
4
 
@@ -13,7 +14,7 @@ use crate::transport::PaneId;
13
14
  /// `{ok:false, error, action, log, reason?, session_name?, next_actions?}`(字节级保留)。
14
15
  /// 人读时打 `error:`/`action:`/`log:` 三行到 stderr(`helpers.py:132-134`)。
15
16
  #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
16
- pub struct CliErrorPayload {
17
+ pub(crate) struct CliErrorPayload {
17
18
  /// 恒 `false`(错误信封)。
18
19
  pub ok: bool,
19
20
  pub error: String,
@@ -70,7 +71,7 @@ impl From<crate::messaging::MessagingError> for CliError {
70
71
  impl CliError {
71
72
  /// `_cli_error_payload`(`helpers.py:137-155`):落盘日志后构造稳定信封。tmux session
72
73
  /// 冲突时按 command 富化 reason/session_name/next_actions(`helpers.py:158-187`)。
73
- pub fn to_payload(&self, log_path: &Path, command: &str) -> CliErrorPayload {
74
+ pub(crate) fn to_payload(&self, log_path: &Path, command: &str) -> CliErrorPayload {
74
75
  let error = self.to_string();
75
76
  let mut payload = CliErrorPayload {
76
77
  ok: false,
@@ -107,6 +108,10 @@ impl CliError {
107
108
  payload.action = String::from(
108
109
  "move the Team Agent launcher flag before `--`; only provider flags belong after `--`",
109
110
  );
111
+ } else if error.contains("managed launcher refuses a different ambient tmux server") {
112
+ payload.action = String::from(
113
+ "leave the current tmux client and retry, or pass `--allow-nested-attach` before `--` to opt in to a nested managed attach",
114
+ );
110
115
  }
111
116
  payload
112
117
  }
@@ -160,6 +165,8 @@ pub struct CmdResult {
160
165
  pub exit: ExitCode,
161
166
  /// `--json` 旗标(决定 `emit` 与 inbox 摘要落 stderr vs stdout,`parser.py:505-506`)。
162
167
  pub as_json: bool,
168
+ /// Preserve nested object insertion order for byte-faithful stored envelopes.
169
+ pub preserve_json_order: bool,
163
170
  }
164
171
 
165
172
  impl CmdResult {
@@ -174,6 +181,20 @@ impl CmdResult {
174
181
  output: CmdOutput::Json(value),
175
182
  exit,
176
183
  as_json,
184
+ preserve_json_order: false,
185
+ }
186
+ }
187
+ pub fn from_ordered_json(value: Value) -> Self {
188
+ let exit = if value.get("ok").and_then(Value::as_bool) == Some(false) {
189
+ ExitCode::Error
190
+ } else {
191
+ ExitCode::Ok
192
+ };
193
+ Self {
194
+ output: CmdOutput::Json(value),
195
+ exit,
196
+ as_json: true,
197
+ preserve_json_order: true,
177
198
  }
178
199
  }
179
200
  pub fn human(text: impl Into<String>) -> Self {
@@ -181,6 +202,7 @@ impl CmdResult {
181
202
  output: CmdOutput::Human(text.into()),
182
203
  exit: ExitCode::Ok,
183
204
  as_json: false,
205
+ preserve_json_order: false,
184
206
  }
185
207
  }
186
208
  pub fn none() -> Self {
@@ -188,6 +210,7 @@ impl CmdResult {
188
210
  output: CmdOutput::None,
189
211
  exit: ExitCode::Ok,
190
212
  as_json: false,
213
+ preserve_json_order: false,
191
214
  }
192
215
  }
193
216
  }
@@ -210,6 +233,8 @@ pub struct LeaderLauncherArgs {
210
233
  pub attach_session: Option<String>,
211
234
  /// 0.3.16 topology opt-out: keep the old external/current-pane leader launcher path.
212
235
  pub external_leader: bool,
236
+ /// N41 explicit opt-in for attaching a managed leader from another tmux server.
237
+ pub allow_nested_attach: bool,
213
238
  }
214
239
 
215
240
  // =============================================================================
@@ -643,6 +668,15 @@ pub struct CollectArgs {
643
668
  pub team: Option<String>,
644
669
  }
645
670
 
671
+ /// Read-only lookup of all stored result envelopes for one case.
672
+ #[derive(Debug, Clone, PartialEq, Eq)]
673
+ pub struct ResultsArgs {
674
+ pub case_id: String,
675
+ pub workspace: PathBuf,
676
+ pub team: Option<String>,
677
+ pub json: bool,
678
+ }
679
+
646
680
  /// `diagnose`(`parser.py:298`) runtime health report, distinct from `doctor`.
647
681
  #[derive(Debug, Clone, PartialEq, Eq)]
648
682
  pub struct DiagnoseArgs {
@@ -0,0 +1,54 @@
1
+ //!
2
+ //! Official communication modes shared by configuration and projection consumers.
3
+
4
+ /// The complete product-supported communication-mode catalog.
5
+ #[derive(Debug, Clone, Copy, PartialEq, Eq)]
6
+ pub enum CommunicationMode {
7
+ LeaderCentric,
8
+ Orchestrated,
9
+ }
10
+
11
+ const LEADER_CENTRIC_RUNTIME_CONTRACT: &str = r#"# Team Agent communication contract: leader_centric
12
+
13
+ - Progress, blockers, questions: team_orchestrator.send_message(to='leader', content='...')
14
+
15
+ When you receive a message from the leader or a teammate, you MUST respond
16
+ through MCP tools. Writing a reply in your terminal does nothing — the sender
17
+ will never see it."#;
18
+
19
+ const ORCHESTRATED_RUNTIME_CONTRACT: &str = r#"# Team Agent communication contract: orchestrated
20
+
21
+ - Send progress only through the declared channel for the assigned task.
22
+ - Respond to task-related messages through Team Agent MCP tools.
23
+ - A pure ACK, unrelated status, or non-task message does not require a response."#;
24
+
25
+ impl CommunicationMode {
26
+ pub const ALL: &[Self] = &[Self::LeaderCentric, Self::Orchestrated];
27
+
28
+ pub const fn as_str(self) -> &'static str {
29
+ match self {
30
+ Self::LeaderCentric => "leader_centric",
31
+ Self::Orchestrated => "orchestrated",
32
+ }
33
+ }
34
+
35
+ pub fn parse(value: &str) -> Option<Self> {
36
+ Self::ALL
37
+ .iter()
38
+ .copied()
39
+ .find(|mode| mode.as_str() == value)
40
+ }
41
+
42
+ pub const fn runtime_contract(self) -> &'static str {
43
+ match self {
44
+ Self::LeaderCentric => LEADER_CENTRIC_RUNTIME_CONTRACT,
45
+ Self::Orchestrated => ORCHESTRATED_RUNTIME_CONTRACT,
46
+ }
47
+ }
48
+ }
49
+
50
+ impl Default for CommunicationMode {
51
+ fn default() -> Self {
52
+ Self::LeaderCentric
53
+ }
54
+ }