@team-agent/installer 0.2.10 → 0.3.0

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 (326) hide show
  1. package/Cargo.lock +744 -0
  2. package/Cargo.toml +34 -0
  3. package/crates/team-agent/Cargo.toml +33 -0
  4. package/crates/team-agent/src/cli/adapters.rs +1343 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +554 -0
  6. package/crates/team-agent/src/cli/emit.rs +1077 -0
  7. package/crates/team-agent/src/cli/helpers.rs +88 -0
  8. package/crates/team-agent/src/cli/leader.rs +216 -0
  9. package/crates/team-agent/src/cli/mod.rs +1141 -0
  10. package/crates/team-agent/src/cli/profile.rs +306 -0
  11. package/crates/team-agent/src/cli/send.rs +215 -0
  12. package/crates/team-agent/src/cli/status.rs +179 -0
  13. package/crates/team-agent/src/cli/status_port.rs +502 -0
  14. package/crates/team-agent/src/cli/tests/base.rs +616 -0
  15. package/crates/team-agent/src/cli/tests/compile.rs +96 -0
  16. package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
  21. package/crates/team-agent/src/cli/tests/mod.rs +97 -0
  22. package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
  23. package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
  25. package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
  26. package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
  27. package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
  28. package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
  29. package/crates/team-agent/src/cli/types.rs +605 -0
  30. package/crates/team-agent/src/compiler/tests.rs +701 -0
  31. package/crates/team-agent/src/compiler.rs +489 -0
  32. package/crates/team-agent/src/coordinator/backoff.rs +153 -0
  33. package/crates/team-agent/src/coordinator/health.rs +436 -0
  34. package/crates/team-agent/src/coordinator/mod.rs +80 -0
  35. package/crates/team-agent/src/coordinator/orphan.rs +179 -0
  36. package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
  37. package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
  38. package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
  39. package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
  40. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
  41. package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
  42. package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
  43. package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
  44. package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
  45. package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
  46. package/crates/team-agent/src/coordinator/tick.rs +2032 -0
  47. package/crates/team-agent/src/coordinator/types.rs +584 -0
  48. package/crates/team-agent/src/db/migration.rs +716 -0
  49. package/crates/team-agent/src/db/mod.rs +23 -0
  50. package/crates/team-agent/src/db/schema.rs +378 -0
  51. package/crates/team-agent/src/event_log.rs +375 -0
  52. package/crates/team-agent/src/fake_worker.rs +253 -0
  53. package/crates/team-agent/src/leader/helpers.rs +190 -0
  54. package/crates/team-agent/src/leader/inject.rs +33 -0
  55. package/crates/team-agent/src/leader/lease.rs +1063 -0
  56. package/crates/team-agent/src/leader/mod.rs +99 -0
  57. package/crates/team-agent/src/leader/owner_bind.rs +292 -0
  58. package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
  59. package/crates/team-agent/src/leader/rediscover.rs +1099 -0
  60. package/crates/team-agent/src/leader/start.rs +273 -0
  61. package/crates/team-agent/src/leader/takeover.rs +235 -0
  62. package/crates/team-agent/src/leader/tests/basics.rs +183 -0
  63. package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
  64. package/crates/team-agent/src/leader/tests/identity.rs +206 -0
  65. package/crates/team-agent/src/leader/tests/idle.rs +271 -0
  66. package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
  67. package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
  68. package/crates/team-agent/src/leader/tests/mod.rs +125 -0
  69. package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
  70. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
  71. package/crates/team-agent/src/leader/types.rs +487 -0
  72. package/crates/team-agent/src/lib.rs +85 -0
  73. package/crates/team-agent/src/lifecycle/display.rs +228 -0
  74. package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
  75. package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
  76. package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
  77. package/crates/team-agent/src/lifecycle/mod.rs +62 -0
  78. package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
  79. package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
  80. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
  81. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
  82. package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
  83. package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
  84. package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
  85. package/crates/team-agent/src/lifecycle/restart.rs +76 -0
  86. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
  88. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
  89. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
  90. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
  91. package/crates/team-agent/src/lifecycle/tests.rs +27 -0
  92. package/crates/team-agent/src/lifecycle/types.rs +685 -0
  93. package/crates/team-agent/src/main.rs +41 -0
  94. package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
  95. package/crates/team-agent/src/mcp_server/mod.rs +183 -0
  96. package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
  97. package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
  98. package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
  99. package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
  100. package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
  101. package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
  102. package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
  103. package/crates/team-agent/src/mcp_server/tests.rs +38 -0
  104. package/crates/team-agent/src/mcp_server/tools.rs +603 -0
  105. package/crates/team-agent/src/mcp_server/types.rs +421 -0
  106. package/crates/team-agent/src/mcp_server/wire.rs +388 -0
  107. package/crates/team-agent/src/message_store.rs +767 -0
  108. package/crates/team-agent/src/messaging/activity.rs +433 -0
  109. package/crates/team-agent/src/messaging/delivery.rs +542 -0
  110. package/crates/team-agent/src/messaging/helpers.rs +209 -0
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
  112. package/crates/team-agent/src/messaging/mod.rs +147 -0
  113. package/crates/team-agent/src/messaging/peers.rs +32 -0
  114. package/crates/team-agent/src/messaging/results.rs +537 -0
  115. package/crates/team-agent/src/messaging/scheduler.rs +344 -0
  116. package/crates/team-agent/src/messaging/selftest.rs +100 -0
  117. package/crates/team-agent/src/messaging/send.rs +582 -0
  118. package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
  119. package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
  121. package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
  122. package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
  123. package/crates/team-agent/src/messaging/trust.rs +192 -0
  124. package/crates/team-agent/src/messaging/types.rs +355 -0
  125. package/crates/team-agent/src/messaging/watchers.rs +591 -0
  126. package/crates/team-agent/src/model/enums.rs +311 -0
  127. package/crates/team-agent/src/model/errors.rs +17 -0
  128. package/crates/team-agent/src/model/ids.rs +155 -0
  129. package/crates/team-agent/src/model/mod.rs +22 -0
  130. package/crates/team-agent/src/model/paths.rs +228 -0
  131. package/crates/team-agent/src/model/permissions.rs +567 -0
  132. package/crates/team-agent/src/model/routing.rs +340 -0
  133. package/crates/team-agent/src/model/spec.rs +680 -0
  134. package/crates/team-agent/src/model/task_graph.rs +380 -0
  135. package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
  136. package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
  137. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
  138. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
  139. package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
  140. package/crates/team-agent/src/model/yaml/tests.rs +288 -0
  141. package/crates/team-agent/src/model/yaml.rs +800 -0
  142. package/crates/team-agent/src/packaging/install.rs +305 -0
  143. package/crates/team-agent/src/packaging/migrate.rs +30 -0
  144. package/crates/team-agent/src/packaging/mod.rs +82 -0
  145. package/crates/team-agent/src/packaging/repair.rs +24 -0
  146. package/crates/team-agent/src/packaging/tests.rs +829 -0
  147. package/crates/team-agent/src/packaging/types.rs +369 -0
  148. package/crates/team-agent/src/provider/adapter.rs +801 -0
  149. package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
  150. package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
  151. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
  152. package/crates/team-agent/src/provider/classify.rs +456 -0
  153. package/crates/team-agent/src/provider/faults.rs +136 -0
  154. package/crates/team-agent/src/provider/helpers.rs +41 -0
  155. package/crates/team-agent/src/provider/mod.rs +53 -0
  156. package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
  157. package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
  158. package/crates/team-agent/src/provider/tests/classify.rs +240 -0
  159. package/crates/team-agent/src/provider/tests/faults.rs +120 -0
  160. package/crates/team-agent/src/provider/tests/idle.rs +208 -0
  161. package/crates/team-agent/src/provider/tests/wire.rs +213 -0
  162. package/crates/team-agent/src/provider/tests.rs +31 -0
  163. package/crates/team-agent/src/provider/types.rs +424 -0
  164. package/crates/team-agent/src/state/identity.rs +656 -0
  165. package/crates/team-agent/src/state/mod.rs +58 -0
  166. package/crates/team-agent/src/state/owner_gate.rs +423 -0
  167. package/crates/team-agent/src/state/persist.rs +712 -0
  168. package/crates/team-agent/src/state/projection.rs +657 -0
  169. package/crates/team-agent/src/state/selector.rs +105 -0
  170. package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
  171. package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
  172. package/crates/team-agent/src/tmux_backend.rs +758 -0
  173. package/crates/team-agent/src/transport/test_support.rs +252 -0
  174. package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
  175. package/crates/team-agent/src/transport/tests/mod.rs +199 -0
  176. package/crates/team-agent/src/transport/tests/wire.rs +527 -0
  177. package/crates/team-agent/src/transport.rs +774 -0
  178. package/npm/install.mjs +90 -106
  179. package/package.json +15 -13
  180. package/crates/team-agent-core/Cargo.toml +0 -12
  181. package/crates/team-agent-core/src/lib.rs +0 -332
  182. package/crates/team-agent-core/src/main.rs +0 -152
  183. package/pyproject.toml +0 -18
  184. package/scripts/install.py +0 -88
  185. package/scripts/run_regression_tests.py +0 -83
  186. package/src/team_agent/__init__.py +0 -3
  187. package/src/team_agent/__main__.py +0 -5
  188. package/src/team_agent/_legacy_pane_discovery.py +0 -186
  189. package/src/team_agent/abnormal_track.py +0 -253
  190. package/src/team_agent/approvals/__init__.py +0 -65
  191. package/src/team_agent/approvals/constants.py +0 -6
  192. package/src/team_agent/approvals/parsing.py +0 -176
  193. package/src/team_agent/approvals/runtime_prompts.py +0 -171
  194. package/src/team_agent/approvals/status.py +0 -176
  195. package/src/team_agent/cli/__init__.py +0 -137
  196. package/src/team_agent/cli/commands.py +0 -481
  197. package/src/team_agent/cli/e2e.py +0 -202
  198. package/src/team_agent/cli/helpers.py +0 -226
  199. package/src/team_agent/cli/parser.py +0 -540
  200. package/src/team_agent/compiler.py +0 -334
  201. package/src/team_agent/coordinator/__init__.py +0 -53
  202. package/src/team_agent/coordinator/__main__.py +0 -83
  203. package/src/team_agent/coordinator/lifecycle.py +0 -363
  204. package/src/team_agent/coordinator/metadata.py +0 -61
  205. package/src/team_agent/coordinator/paths.py +0 -17
  206. package/src/team_agent/diagnose/__init__.py +0 -48
  207. package/src/team_agent/diagnose/checks.py +0 -101
  208. package/src/team_agent/diagnose/comms.py +0 -213
  209. package/src/team_agent/diagnose/health.py +0 -241
  210. package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
  211. package/src/team_agent/diagnose/preflight.py +0 -194
  212. package/src/team_agent/diagnose/quick_start.py +0 -324
  213. package/src/team_agent/display/__init__.py +0 -92
  214. package/src/team_agent/display/adaptive.py +0 -511
  215. package/src/team_agent/display/backend.py +0 -46
  216. package/src/team_agent/display/close.py +0 -154
  217. package/src/team_agent/display/ghostty.py +0 -77
  218. package/src/team_agent/display/rebuild.py +0 -102
  219. package/src/team_agent/display/tiling.py +0 -156
  220. package/src/team_agent/display/worker_window.py +0 -114
  221. package/src/team_agent/display/workspace.py +0 -382
  222. package/src/team_agent/errors.py +0 -10
  223. package/src/team_agent/events.py +0 -84
  224. package/src/team_agent/fake_worker.py +0 -80
  225. package/src/team_agent/idle_predicate.py +0 -200
  226. package/src/team_agent/idle_takeover.py +0 -59
  227. package/src/team_agent/idle_takeover_wiring.py +0 -111
  228. package/src/team_agent/launch/__init__.py +0 -41
  229. package/src/team_agent/launch/bootstrap.py +0 -85
  230. package/src/team_agent/launch/config.py +0 -106
  231. package/src/team_agent/launch/core.py +0 -301
  232. package/src/team_agent/launch/requirements.py +0 -57
  233. package/src/team_agent/leader/__init__.py +0 -926
  234. package/src/team_agent/leader_binding.py +0 -183
  235. package/src/team_agent/lifecycle/__init__.py +0 -5
  236. package/src/team_agent/lifecycle/agents.py +0 -278
  237. package/src/team_agent/lifecycle/operations.py +0 -411
  238. package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
  239. package/src/team_agent/lifecycle/start.py +0 -363
  240. package/src/team_agent/mcp_server/__init__.py +0 -42
  241. package/src/team_agent/mcp_server/__main__.py +0 -7
  242. package/src/team_agent/mcp_server/contracts.py +0 -148
  243. package/src/team_agent/mcp_server/normalize.py +0 -257
  244. package/src/team_agent/mcp_server/server.py +0 -150
  245. package/src/team_agent/mcp_server/tools.py +0 -352
  246. package/src/team_agent/message_store/__init__.py +0 -23
  247. package/src/team_agent/message_store/agent_health.py +0 -113
  248. package/src/team_agent/message_store/core.py +0 -497
  249. package/src/team_agent/message_store/leader_notification_log.py +0 -198
  250. package/src/team_agent/message_store/result_watchers.py +0 -251
  251. package/src/team_agent/message_store/schema.py +0 -308
  252. package/src/team_agent/message_store/schema_migration.py +0 -448
  253. package/src/team_agent/messaging/__init__.py +0 -1
  254. package/src/team_agent/messaging/activity_detector.py +0 -254
  255. package/src/team_agent/messaging/delivery.py +0 -473
  256. package/src/team_agent/messaging/deps.py +0 -247
  257. package/src/team_agent/messaging/idle_alerts.py +0 -423
  258. package/src/team_agent/messaging/internal_delivery.py +0 -46
  259. package/src/team_agent/messaging/leader.py +0 -497
  260. package/src/team_agent/messaging/leader_api_errors.py +0 -216
  261. package/src/team_agent/messaging/leader_panes.py +0 -673
  262. package/src/team_agent/messaging/owner_bypass.py +0 -29
  263. package/src/team_agent/messaging/result_delivery.py +0 -539
  264. package/src/team_agent/messaging/results.py +0 -447
  265. package/src/team_agent/messaging/scheduler.py +0 -450
  266. package/src/team_agent/messaging/send.py +0 -532
  267. package/src/team_agent/messaging/session_drift.py +0 -94
  268. package/src/team_agent/messaging/tmux_io.py +0 -506
  269. package/src/team_agent/messaging/tmux_prompt.py +0 -338
  270. package/src/team_agent/messaging/trust_auto_answer.py +0 -52
  271. package/src/team_agent/orchestrator/__init__.py +0 -376
  272. package/src/team_agent/orchestrator/plan.py +0 -122
  273. package/src/team_agent/orchestrator/state.py +0 -128
  274. package/src/team_agent/paths.py +0 -45
  275. package/src/team_agent/permissions.py +0 -123
  276. package/src/team_agent/profiles/__init__.py +0 -82
  277. package/src/team_agent/profiles/constants.py +0 -19
  278. package/src/team_agent/profiles/core.py +0 -407
  279. package/src/team_agent/profiles/helpers.py +0 -69
  280. package/src/team_agent/profiles/provider_env.py +0 -188
  281. package/src/team_agent/profiles/smoke.py +0 -201
  282. package/src/team_agent/provider_cli/__init__.py +0 -43
  283. package/src/team_agent/provider_cli/adapter.py +0 -172
  284. package/src/team_agent/provider_cli/base.py +0 -48
  285. package/src/team_agent/provider_cli/claude.py +0 -457
  286. package/src/team_agent/provider_cli/codex.py +0 -336
  287. package/src/team_agent/provider_cli/copilot.py +0 -8
  288. package/src/team_agent/provider_cli/fake.py +0 -39
  289. package/src/team_agent/provider_cli/gemini.py +0 -95
  290. package/src/team_agent/provider_cli/opencode.py +0 -8
  291. package/src/team_agent/provider_cli/prompt.py +0 -62
  292. package/src/team_agent/provider_cli/registry.py +0 -18
  293. package/src/team_agent/provider_cli/unsupported.py +0 -32
  294. package/src/team_agent/provider_state/README.md +0 -78
  295. package/src/team_agent/provider_state/__init__.py +0 -86
  296. package/src/team_agent/provider_state/claude.py +0 -86
  297. package/src/team_agent/provider_state/codex.py +0 -84
  298. package/src/team_agent/provider_state/common.py +0 -207
  299. package/src/team_agent/provider_state/registry.py +0 -118
  300. package/src/team_agent/providers.py +0 -163
  301. package/src/team_agent/quality_gates.py +0 -104
  302. package/src/team_agent/restart/__init__.py +0 -34
  303. package/src/team_agent/restart/orchestration.py +0 -554
  304. package/src/team_agent/restart/selection.py +0 -89
  305. package/src/team_agent/restart/snapshot.py +0 -70
  306. package/src/team_agent/routing.py +0 -84
  307. package/src/team_agent/runtime.py +0 -1239
  308. package/src/team_agent/rust_core.py +0 -327
  309. package/src/team_agent/sessions/__init__.py +0 -25
  310. package/src/team_agent/sessions/capture.py +0 -143
  311. package/src/team_agent/sessions/inventory.py +0 -44
  312. package/src/team_agent/sessions/resume.py +0 -135
  313. package/src/team_agent/simple_yaml.py +0 -236
  314. package/src/team_agent/spec.py +0 -370
  315. package/src/team_agent/state.py +0 -602
  316. package/src/team_agent/status/__init__.py +0 -63
  317. package/src/team_agent/status/approvals.py +0 -52
  318. package/src/team_agent/status/compact.py +0 -158
  319. package/src/team_agent/status/constants.py +0 -18
  320. package/src/team_agent/status/inbox.py +0 -58
  321. package/src/team_agent/status/peek.py +0 -117
  322. package/src/team_agent/status/queries.py +0 -199
  323. package/src/team_agent/task_graph.py +0 -80
  324. package/src/team_agent/terminal.py +0 -57
  325. package/src/team_agent/wake.py +0 -58
  326. package/src/team_agent/watch/__init__.py +0 -145
@@ -0,0 +1,302 @@
1
+ use super::*;
2
+
3
+ const REPAIR_SPEC_TEMPLATE: &str = r#"version: 1
4
+ team:
5
+ name: "repair-team"
6
+ mode: "supervisor_worker"
7
+ objective: "Exercise repair-state."
8
+ workspace: "__WS__"
9
+ leader:
10
+ id: "leader"
11
+ role: "leader"
12
+ provider: "fake"
13
+ model: null
14
+ tools:
15
+ - "fs_read"
16
+ - "fs_list"
17
+ - "mcp_team"
18
+ context_policy:
19
+ keep_user_thread: true
20
+ receive_worker_outputs: "structured_only"
21
+ max_worker_result_tokens: 2000
22
+ agents:
23
+ - id: "fake_impl"
24
+ role: "implementation_engineer"
25
+ provider: "fake"
26
+ model: null
27
+ working_directory: "__WS__"
28
+ system_prompt:
29
+ inline: "Handle fake implementation tasks."
30
+ file: null
31
+ tools:
32
+ - "fs_read"
33
+ - "mcp_team"
34
+ permission_mode: "restricted"
35
+ preferred_for:
36
+ - "implementation"
37
+ avoid_for: []
38
+ output_contract:
39
+ format: "result_envelope_v1"
40
+ required_fields:
41
+ - "task_id"
42
+ - "status"
43
+ - "summary"
44
+ - "artifacts"
45
+ routing:
46
+ default_assignee: "leader"
47
+ rules:
48
+ - id: "implementation-to-fake"
49
+ match:
50
+ type:
51
+ - "implementation"
52
+ assign_to: "fake_impl"
53
+ priority: 10
54
+ communication:
55
+ protocol: "mcp_inbox"
56
+ topology: "leader_centered"
57
+ worker_to_worker: true
58
+ ack_timeout_sec: 2
59
+ result_format: "result_envelope_v1"
60
+ message_store:
61
+ sqlite: ".team/runtime/team.db"
62
+ mirror_files: ".team/messages"
63
+ runtime:
64
+ backend: "tmux"
65
+ display_backend: "none"
66
+ session_name: "team-agent-repair"
67
+ auto_launch: true
68
+ require_user_approval_before_launch: false
69
+ max_active_agents: 1
70
+ startup_order:
71
+ - "fake_impl"
72
+ context:
73
+ state_file: "team_state.md"
74
+ artifact_dir: ".team/artifacts"
75
+ log_dir: ".team/logs"
76
+ summarization:
77
+ worker_full_logs: "retain_outside_leader_context"
78
+ state_update: "after_each_result"
79
+ tasks:
80
+ - id: "task_impl"
81
+ title: "Fake implementation"
82
+ type: "implementation"
83
+ assignee: null
84
+ deps: []
85
+ acceptance:
86
+ - "fake result collected"
87
+ status: "pending"
88
+ "#;
89
+
90
+ fn seed_repair_workspace(ws: &std::path::Path) {
91
+ std::fs::create_dir_all(ws.join(".team").join("logs")).unwrap();
92
+ std::fs::create_dir_all(ws.join(".team").join("runtime")).unwrap();
93
+ let spec_path = ws.join("team.spec.yaml");
94
+ std::fs::write(
95
+ &spec_path,
96
+ REPAIR_SPEC_TEMPLATE.replace("__WS__", &ws.to_string_lossy()),
97
+ )
98
+ .unwrap();
99
+ crate::state::persist::save_runtime_state(
100
+ ws,
101
+ &json!({
102
+ "spec_path": spec_path.to_string_lossy(),
103
+ "session_name": "team-agent-repair",
104
+ "leader": {"id": "leader"},
105
+ "agents": {"fake_impl": {"status": "stopped", "provider": "fake"}},
106
+ "tasks": [{
107
+ "id": "task_impl",
108
+ "title": "Fake implementation",
109
+ "type": "implementation",
110
+ "assignee": null,
111
+ "deps": [],
112
+ "acceptance": ["fake result collected"],
113
+ "status": "pending"
114
+ }]
115
+ }),
116
+ )
117
+ .unwrap();
118
+ }
119
+
120
+ fn repair_args(
121
+ ws: &std::path::Path,
122
+ status: &str,
123
+ summary: Option<&str>,
124
+ json: bool,
125
+ ) -> RepairStateArgs {
126
+ RepairStateArgs {
127
+ workspace: ws.to_path_buf(),
128
+ task_id: "task_impl".to_string(),
129
+ assignee: Some("fake_impl".to_string()),
130
+ status: status.to_string(),
131
+ summary: summary.map(str::to_string),
132
+ json,
133
+ }
134
+ }
135
+
136
+ // Golden source:
137
+ // - cli/parser.py:303-310 registers `repair-state --workspace --task --assignee --status --summary --json`.
138
+ // - cli/commands.py:196-203 delegates all five args to `runtime.repair_state`.
139
+ // - diagnose/quick_start.py:285-324:
140
+ // * loads state + spec, validates assignee against spec agents plus leader id,
141
+ // * validates status against task_graph.py TASK_STATUSES,
142
+ // * before/after are three-field projections `{assignee,status,last_result_summary}`,
143
+ // * summary writes `last_result_summary` (not `summary`),
144
+ // * writes EventLog event `repair_state.task` with task_id/before/after,
145
+ // * returns `{ok,task_id,before,after,state_file}` in that insertion order.
146
+ // - task_graph.py:5-14 legal statuses are exactly:
147
+ // blocked,cancelled,done,failed,needs_retry,pending,ready,running.
148
+ //
149
+ // Golden probe:
150
+ // PYTHONPATH=/Users/alauda/Documents/code/team-agent-public/src python3 /tmp/probe_repair_state_cli.py
151
+ #[test]
152
+ fn repair_state_success_json_human_and_event_byte_shape() {
153
+ let ws = tmp_workspace();
154
+ seed_repair_workspace(&ws);
155
+
156
+ let json_result = cmd_repair_state(&repair_args(&ws, "done", Some("patched"), true))
157
+ .expect("repair-state success should return ok");
158
+ assert_eq!(json_result.exit, ExitCode::Ok);
159
+ assert_eq!(
160
+ emit(&json_result.output, true).unwrap(),
161
+ format!(
162
+ "{{\n \"after\": {{\n \"assignee\": \"fake_impl\",\n \"last_result_summary\": \"patched\",\n \"status\": \"done\"\n }},\n \"before\": {{\n \"assignee\": null,\n \"last_result_summary\": null,\n \"status\": \"pending\"\n }},\n \"ok\": true,\n \"state_file\": \"{}\",\n \"task_id\": \"task_impl\"\n}}",
163
+ ws.join("team_state.md").to_string_lossy()
164
+ ),
165
+ "repair-state --json must match Python pretty sorted JSON byte shape",
166
+ );
167
+
168
+ let event_line = std::fs::read_to_string(ws.join(".team/logs/events.jsonl"))
169
+ .expect("repair-state must write events.jsonl")
170
+ .lines()
171
+ .last()
172
+ .expect("repair-state must append an event")
173
+ .to_string();
174
+ assert!(
175
+ event_line.starts_with("{\"after\": {\"assignee\": \"fake_impl\", \"last_result_summary\": \"patched\", \"status\": \"done\"}, \"before\": {\"assignee\": null, \"last_result_summary\": null, \"status\": \"pending\"}, \"event\": \"repair_state.task\", \"task_id\": \"task_impl\", \"ts\": \""),
176
+ "repair_state.task event must be Python sort_keys JSON with after/before/event/task_id/ts order; got {event_line}",
177
+ );
178
+ assert!(event_line.ends_with("\"}"), "event line must end with timestamp string; got {event_line}");
179
+
180
+ let ws_human = tmp_workspace();
181
+ seed_repair_workspace(&ws_human);
182
+ let human_result = cmd_repair_state(&repair_args(&ws_human, "done", Some("patched"), false))
183
+ .expect("repair-state human success should return ok");
184
+ assert_eq!(
185
+ emit(&human_result.output, false).unwrap(),
186
+ format!(
187
+ "ok: True\ntask_id: task_impl\nbefore: {{\"assignee\": null, \"status\": \"pending\", \"last_result_summary\": null}}\nafter: {{\"assignee\": \"fake_impl\", \"status\": \"done\", \"last_result_summary\": \"patched\"}}\nstate_file: {}",
188
+ ws_human.join("team_state.md").to_string_lossy()
189
+ ),
190
+ "repair-state human output must preserve Python returned-dict and nested-dict insertion order",
191
+ );
192
+
193
+ let state = crate::state::persist::load_runtime_state(&ws).unwrap();
194
+ assert_eq!(
195
+ state["tasks"][0]["last_result_summary"],
196
+ json!("patched"),
197
+ "golden writes summary into last_result_summary, not summary",
198
+ );
199
+ assert!(
200
+ state["tasks"][0].get("summary").is_none(),
201
+ "golden does not create a task.summary field during repair-state",
202
+ );
203
+ let _ = std::fs::remove_dir_all(&ws);
204
+ let _ = std::fs::remove_dir_all(&ws_human);
205
+ }
206
+
207
+ #[test]
208
+ fn repair_state_rejects_status_outside_task_statuses() {
209
+ let ws = tmp_workspace();
210
+ seed_repair_workspace(&ws);
211
+ let legal_statuses = vec![
212
+ "blocked",
213
+ "cancelled",
214
+ "done",
215
+ "failed",
216
+ "needs_retry",
217
+ "pending",
218
+ "ready",
219
+ "running",
220
+ ];
221
+ assert_eq!(
222
+ legal_statuses,
223
+ vec![
224
+ "blocked",
225
+ "cancelled",
226
+ "done",
227
+ "failed",
228
+ "needs_retry",
229
+ "pending",
230
+ "ready",
231
+ "running"
232
+ ],
233
+ "golden TASK_STATUSES from task_graph.py:5 must stay locked",
234
+ );
235
+
236
+ let err = cmd_repair_state(&repair_args(&ws, "assigned", None, true))
237
+ .expect_err("status outside TASK_STATUSES must error");
238
+ assert_eq!(err.to_string(), "unknown task status for repair: assigned");
239
+ let payload = err.to_payload(&ws.join(".team/logs/cli-error-123.log"), "repair-state");
240
+ assert_eq!(
241
+ serde_json::to_string(&payload).unwrap(),
242
+ format!(
243
+ "{{\"ok\":false,\"error\":\"unknown task status for repair: assigned\",\"action\":\"run `team-agent doctor` or inspect the log path shown here\",\"log\":\"{}\"}}",
244
+ ws.join(".team/logs/cli-error-123.log").to_string_lossy()
245
+ ),
246
+ "repair-state --json error envelope must match Python compact key order and text",
247
+ );
248
+ let _ = std::fs::remove_dir_all(&ws);
249
+ }
250
+
251
+ // CONTRACT (real-machine repair_state product FAIL): rt-host-d ran `repair-state --task rp1 --status done`
252
+ // in a quick-start workspace whose spec lives in a teamdir (state.spec_path -> <ws>/teamdir/team.spec.yaml),
253
+ // NOT at <ws>/team.spec.yaml. Rust cmd_repair_state calls load_team_spec(workspace) which HARDCODES
254
+ // workspace.join("team.spec.yaml") (adapters.rs:982) -> read_to_string fails 'No such file or directory'.
255
+ // golden (quick_start.py:295) resolves spec_path = state.get("spec_path", workspace/"team.spec.yaml") and
256
+ // SUCCEEDS. NB: golden ALSO writes team_state.md (write_team_state -> state.py mkdir+write) and returns
257
+ // state_file=team_state.md, so the fix is NOT to drop write_team_state (that diverges from golden and breaks
258
+ // repair_state_success_json_human_and_event_byte_shape) — it is to resolve the spec from state.spec_path
259
+ // (Rust already has load_team_spec_optional(workspace, state) that does exactly this).
260
+ #[test]
261
+ fn contract_repair_state_resolves_spec_from_state_spec_path_teamdir_layout() {
262
+ let ws = tmp_workspace();
263
+ std::fs::create_dir_all(ws.join(".team").join("logs")).unwrap();
264
+ std::fs::create_dir_all(ws.join(".team").join("runtime")).unwrap();
265
+ let teamdir = ws.join("teamdir");
266
+ std::fs::create_dir_all(&teamdir).unwrap();
267
+ let spec_path = teamdir.join("team.spec.yaml");
268
+ std::fs::write(
269
+ &spec_path,
270
+ REPAIR_SPEC_TEMPLATE.replace("__WS__", &ws.to_string_lossy()),
271
+ )
272
+ .unwrap();
273
+ // NOTE: NO <ws>/team.spec.yaml (the hardcoded path) and NO pre-existing team_state.md.
274
+ crate::state::persist::save_runtime_state(
275
+ &ws,
276
+ &json!({
277
+ "spec_path": spec_path.to_string_lossy(),
278
+ "session_name": "team-agent-repair",
279
+ "leader": {"id": "leader"},
280
+ "agents": {"fake_impl": {"status": "running", "provider": "fake"}},
281
+ "tasks": [{
282
+ "id": "task_impl", "title": "x", "type": "implementation",
283
+ "assignee": null, "deps": [], "acceptance": ["a"], "status": "pending"
284
+ }]
285
+ }),
286
+ )
287
+ .unwrap();
288
+
289
+ let cmd = cmd_repair_state(&repair_args(&ws, "done", Some("patched"), true)).expect(
290
+ "CONTRACT: repair-state --status done must resolve the spec from state.spec_path (golden \
291
+ quick_start.py:295), not the hardcoded <ws>/team.spec.yaml (adapters.rs:982) which is absent in \
292
+ the teamdir layout -> currently io 'No such file or directory'",
293
+ );
294
+ assert_eq!(cmd.exit, ExitCode::Ok);
295
+ let state = crate::state::persist::load_runtime_state(&ws).unwrap();
296
+ assert_eq!(
297
+ state["tasks"][0]["status"],
298
+ json!("done"),
299
+ "the task must persist status=done after repair"
300
+ );
301
+ let _ = std::fs::remove_dir_all(&ws);
302
+ }
@@ -0,0 +1,305 @@
1
+ use super::*;
2
+
3
+ struct TeamSocketGuard {
4
+ ws: std::path::PathBuf,
5
+ }
6
+
7
+ impl Drop for TeamSocketGuard {
8
+ fn drop(&mut self) {
9
+ crate::tmux_backend::TmuxBackend::for_workspace(&self.ws).kill_server();
10
+ if let Some(uid) = current_uid() {
11
+ let socket = crate::tmux_backend::socket_name_for_workspace(&self.ws);
12
+ for root in ["/private/tmp", "/tmp"] {
13
+ let path = std::path::Path::new(root).join(format!("tmux-{uid}")).join(&socket);
14
+ let _ = std::fs::remove_file(path);
15
+ }
16
+ }
17
+ }
18
+ }
19
+
20
+ fn current_uid() -> Option<String> {
21
+ let output = std::process::Command::new("id").arg("-u").output().ok()?;
22
+ if !output.status.success() {
23
+ return None;
24
+ }
25
+ String::from_utf8(output.stdout).ok().map(|s| s.trim().to_string()).filter(|s| !s.is_empty())
26
+ }
27
+
28
+ // =========================================================================
29
+ // (A) CLI dispatch — run(argv, cwd) parses each subcommand and routes to its (already-tested)
30
+ // cmd_*, mapping the exit code. Today run() only handles codex/claude passthrough + help, else
31
+ // ExitCode::Error. These prove run DISPATCHES (observable per-command effect + exit). Golden
32
+ // cli/parser.py:main. OS-safe handlers only; spawn-bearing routes are #[ignore] real-machine.
33
+ // =========================================================================
34
+
35
+ #[test]
36
+ fn run_dispatches_status_to_handler_returns_ok() {
37
+ // run([status --workspace <seeded> --json]) -> cmd_status -> status_port::status (reads state)
38
+ // -> ExitCode::Ok. Today: Error (no dispatch). OS-safe (status only reads runtime state).
39
+ let ws = seed_status_workspace();
40
+ let argv = vec![
41
+ "status".to_string(),
42
+ "--workspace".to_string(),
43
+ ws.to_string_lossy().to_string(),
44
+ "--json".to_string(),
45
+ ];
46
+ assert_eq!(
47
+ run(&argv, Path::new(".")),
48
+ ExitCode::Ok,
49
+ "run must dispatch `status` to cmd_status and return Ok (not today's hardcoded Error)"
50
+ );
51
+ }
52
+
53
+ #[test]
54
+ fn run_dispatches_send_to_handler_returns_ok() {
55
+ // run([send w1 hello --sender leader --workspace <seeded>]) -> cmd_send -> messaging::send_message
56
+ // -> ok (w1 is an in-team agent) -> ExitCode::Ok. w1 must be seeded: golden refuses non-team targets.
57
+ //
58
+ // OLD seed: flat `{"agents": {"w1": ...}}`.
59
+ // NEW seed (Bug 1/2 — team-in-team state scope, see
60
+ // tests/team_in_team_state_scope_red.rs): send_message projects the raw state
61
+ // through the active team_key, so the in-team agent now lives under
62
+ // `teams[<key>].agents`. Behavior under test (dispatch + ok exit) unchanged.
63
+ let ws = deleg_uniq_dir("run-send");
64
+ let _ = crate::message_store::MessageStore::open(&ws).unwrap();
65
+ crate::state::persist::save_runtime_state(
66
+ &ws,
67
+ &serde_json::json!({
68
+ "active_team_key": "current",
69
+ "teams": {"current": {"agents": {"w1": {"provider": "codex"}}}}
70
+ }),
71
+ )
72
+ .unwrap();
73
+ let argv = vec![
74
+ "send".to_string(),
75
+ "w1".to_string(),
76
+ "hello".to_string(),
77
+ "--sender".to_string(),
78
+ "leader".to_string(),
79
+ "--workspace".to_string(),
80
+ ws.to_string_lossy().to_string(),
81
+ ];
82
+ assert_eq!(
83
+ run(&argv, Path::new(".")),
84
+ ExitCode::Ok,
85
+ "run must dispatch `send` to cmd_send and derive ExitCode::Ok from the delivery outcome"
86
+ );
87
+ }
88
+
89
+ #[test]
90
+ fn run_unknown_subcommand_is_usage() {
91
+ // An unknown subcommand routes to argparse-style usage (exit 2), never a handler.
92
+ assert_eq!(
93
+ run(&["totally-not-a-subcommand".to_string()], Path::new(".")),
94
+ ExitCode::Usage,
95
+ "an unknown subcommand must map to ExitCode::Usage"
96
+ );
97
+ }
98
+
99
+ #[test]
100
+ #[ignore = "real-machine: `coordinator --once` dispatches to run_daemon which drives a real \
101
+ Coordinator (TmuxBackend); in-process needs a run_daemon_with_coordinator seam"]
102
+ fn run_dispatches_coordinator_once_writes_meta() {
103
+ // run([coordinator --workspace <ws> --once]) -> run_daemon(DaemonArgs{once:true}) -> writes the
104
+ // coordinator boot metadata + ExitCode::Ok. (real machine — drives a real backend.)
105
+ let ws = seed_status_workspace();
106
+ let argv = vec![
107
+ "coordinator".to_string(),
108
+ "--workspace".to_string(),
109
+ ws.to_string_lossy().to_string(),
110
+ "--once".to_string(),
111
+ ];
112
+ let exit = run(&argv, Path::new("."));
113
+ let wp = crate::coordinator::WorkspacePath::new(ws);
114
+ assert!(
115
+ crate::coordinator::coordinator_meta_path(&wp).exists(),
116
+ "coordinator --once must dispatch to run_daemon and write the coordinator metadata"
117
+ );
118
+ assert_eq!(exit, ExitCode::Ok);
119
+ }
120
+
121
+ #[test]
122
+ #[ignore = "real-machine: quick-start --yes spawns a real team (tmux) + coordinator daemon"]
123
+ fn run_dispatches_quick_start_compiles_spec() {
124
+ // The full quick-start path spawns workers + the coordinator; on a real machine we assert it
125
+ // dispatched to cmd_quick_start (team.spec.yaml compiled under the team dir) + ExitCode::Ok.
126
+ let dir = std::env::temp_dir().join(format!("ta-cli-qs-{}", std::process::id()));
127
+ std::fs::create_dir_all(dir.join("agents")).unwrap();
128
+ std::fs::write(dir.join("TEAM.md"), "---\nname: t\nobjective: o\nprovider: codex\n---\n\nteam.\n").unwrap();
129
+ std::fs::write(
130
+ dir.join("agents").join("implementer.md"),
131
+ "---\nname: implementer\nrole: Impl\nprovider: codex\nmodel: gpt-5.5\nauth_mode: subscription\ntools:\n - mcp_team\n---\n\nImpl.\n",
132
+ )
133
+ .unwrap();
134
+ let argv = vec!["quick-start".to_string(), dir.to_string_lossy().to_string(), "--yes".to_string()];
135
+ let exit = run(&argv, Path::new("."));
136
+ assert!(dir.join("team.spec.yaml").exists(), "quick-start must compile the spec under the team dir");
137
+ assert_eq!(exit, ExitCode::Ok);
138
+ }
139
+
140
+ // =========================================================================
141
+ // (DELEGATION) cli *_port handlers must DELEGATE TO THE REAL SUBSYSTEMS (crate::lifecycle /
142
+ // crate::coordinator / crate::message_store), not the placeholder stubs in cli/mod.rs (which do
143
+ // `let _ = (...)` and fabricate canned Values). Each test asserts the REAL subsystem's effect
144
+ // (spec compiled, real error text, real coordinator_health, real DB counts) appears in the
145
+ // handler's CmdResult/Value — distinguishable from the placeholder's canned output. OS edge mocked;
146
+ // real tmux spawn / real daemon = #[ignore]. Golden: lifecycle/launch.rs+restart.rs, compiler.rs,
147
+ // coordinator/health.rs, message_store.
148
+ // =========================================================================
149
+ #[test]
150
+ fn cli_quick_start_invokes_real_lifecycle_compiles_spec() {
151
+ let team = deleg_team_dir_with_healthy_coordinator();
152
+ let ws = crate::model::paths::team_workspace(&team).unwrap();
153
+ let _guard = TeamSocketGuard { ws };
154
+ let args = QuickStartArgs {
155
+ agents_dir: team.clone(),
156
+ name: None,
157
+ team_id: None,
158
+ yes: true,
159
+ fresh: false,
160
+ json: true,
161
+ };
162
+ let _ = cmd_quick_start(&args); // real quick_start compiles the spec before any coordinator/launch step
163
+ assert!(
164
+ team.join("team.spec.yaml").exists(),
165
+ "cmd_quick_start must delegate to crate::lifecycle::quick_start, which compiles team.spec.yaml \
166
+ under the team dir; the placeholder never writes it"
167
+ );
168
+ }
169
+
170
+ // 2 [P0] — cmd_quick_start over an INVALID role doc surfaces the REAL compiler error (missing
171
+ // `provider`), proving delegation to crate::lifecycle (compile fails before any spawn). The
172
+ // placeholder returns a canned {ok:true} with no error -> RED. OS-safe (compile fails early).
173
+ #[test]
174
+ fn cli_quick_start_invalid_spec_surfaces_real_compile_error() {
175
+ let team = deleg_uniq_dir("qsbad");
176
+ std::fs::create_dir_all(team.join("agents")).unwrap();
177
+ std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
178
+ std::fs::write(team.join("agents").join("broken.md"), DELEG_INVALID_ROLE).unwrap();
179
+ let args = QuickStartArgs {
180
+ agents_dir: team,
181
+ name: None,
182
+ team_id: None,
183
+ yes: false,
184
+ fresh: false,
185
+ json: true,
186
+ };
187
+ let text = outcome_text(cmd_quick_start(&args));
188
+ assert!(
189
+ text.contains("provider"),
190
+ "cmd_quick_start must surface the REAL compiler error for a role doc missing `provider` \
191
+ (proving delegation to crate::lifecycle, not the placeholder's canned ok:true); got {text}"
192
+ );
193
+ }
194
+
195
+ // 3 [P1] — cmd_restart on a no-spec workspace surfaces the REAL crate::lifecycle::restart error
196
+ // "missing spec for restart" (restart.rs:147). The placeholder returns {ok:true, allow_fresh, team}
197
+ // -> RED. OS-safe (restart errors on the missing spec before any spawn).
198
+ #[test]
199
+ fn cli_restart_missing_spec_surfaces_real_teamselect() {
200
+ let ws = deleg_uniq_dir("restart"); // no team.spec.yaml
201
+ let args = RestartArgs { workspace: ws, team: None, allow_fresh: false, json: true };
202
+ let text = outcome_text(cmd_restart(&args));
203
+ assert!(
204
+ text.contains("missing spec for restart"),
205
+ "cmd_restart must surface the REAL crate::lifecycle::restart 'missing spec for restart' error \
206
+ (not the placeholder's canned ok:true); got {text}"
207
+ );
208
+ }
209
+
210
+ // 4 [P1] — cmd_add_agent with a DUPLICATE agent id surfaces the REAL crate::lifecycle::add_agent
211
+ // "agent id already exists" error (launch.rs:252). The placeholder returns {ok:true} -> RED.
212
+ // OS-safe (the dup check fires before compile/spawn).
213
+ #[test]
214
+ fn cli_add_agent_duplicate_id_surfaces_real_error() {
215
+ let team = deleg_uniq_dir("addagent");
216
+ std::fs::create_dir_all(team.join("agents")).unwrap();
217
+ std::fs::write(team.join("TEAM.md"), DELEG_TEAM_MD).unwrap();
218
+ std::fs::write(team.join("agents").join("implementer.md"), DELEG_VALID_ROLE).unwrap(); // 'implementer' already exists
219
+ let dup_role = team.join("dup-role.md");
220
+ std::fs::write(&dup_role, DELEG_VALID_ROLE).unwrap(); // role file must exist
221
+ let args = AddAgentArgs {
222
+ agent: "implementer".to_string(),
223
+ workspace: team,
224
+ team: None,
225
+ role_file: dup_role.to_string_lossy().to_string(),
226
+ no_display: false,
227
+ json: true,
228
+ };
229
+ let text = outcome_text(cmd_add_agent(&args));
230
+ assert!(
231
+ text.contains("already exists"),
232
+ "cmd_add_agent must surface the REAL crate::lifecycle::add_agent 'agent id already exists' error \
233
+ (not the placeholder's canned ok:true); got {text}"
234
+ );
235
+ }
236
+
237
+ // 5 [P1] — cmd_status --json reflects the REAL coordinator_health + real DB message/result counts.
238
+ // The placeholder status_port::status hardcodes coordinator={status:stopped, schema_ok:false} and
239
+ // messages/results={count:0}. Seed: state.json + a real team.db (2 messages + 1 result) + a healthy
240
+ // coordinator (this pid + metadata) -> the real path must surface running-coordinator + non-zero
241
+ // counts. OS-safe (all reads/seeds, no spawn).
242
+ #[test]
243
+ fn cli_status_reflects_real_coordinator_health_and_db_counts() {
244
+ let ws = seed_status_workspace(); // writes .team/runtime/state.json
245
+ let store = crate::message_store::MessageStore::open(&ws).unwrap();
246
+ let _ = store.create_message(Some("t1"), "leader", "a1", "hi", None, true, None).unwrap();
247
+ let _ = store.create_message(Some("t1"), "leader", "a1", "again", None, true, None).unwrap();
248
+ let conn = crate::db::schema::open_db(store.db_path()).unwrap();
249
+ conn.execute(
250
+ "insert into results(result_id, owner_team_id, task_id, agent_id, envelope, status, created_at) \
251
+ values ('r1', null, 't1', 'a1', '{}', 'success', '2026-01-01T00:00:00Z')",
252
+ [],
253
+ )
254
+ .unwrap();
255
+ drop(store);
256
+ // a HEALTHY coordinator at the status workspace (this process's pid + matching metadata).
257
+ let wp = crate::coordinator::WorkspacePath::new(ws.clone());
258
+ let me = crate::coordinator::Pid::new(std::process::id());
259
+ crate::coordinator::write_coordinator_metadata(&wp, me, crate::coordinator::MetadataSource::Boot).unwrap();
260
+ std::fs::write(crate::coordinator::coordinator_pid_path(&wp), me.to_string()).unwrap();
261
+
262
+ let args = StatusArgs { agent: None, workspace: ws, detail: true, summary: false, json: true };
263
+ let r = cmd_status(&args).expect("cmd_status");
264
+ let value = match r.output {
265
+ CmdOutput::Json(v) => v,
266
+ other => panic!("status --json must yield a Json CmdOutput; got {other:?}"),
267
+ };
268
+ assert_ne!(
269
+ value["coordinator"],
270
+ json!({"status": "stopped", "schema_ok": false}),
271
+ "status `coordinator` must reflect the REAL coordinator_health (seeded running), not the \
272
+ placeholder's hardcoded {{status:stopped, schema_ok:false}}; got {}",
273
+ value["coordinator"]
274
+ );
275
+ assert_ne!(
276
+ value["messages"],
277
+ json!({"count": 0}),
278
+ "status `messages` must reflect the REAL DB rows (2 seeded), not the placeholder's {{count:0}}; got {}",
279
+ value["messages"]
280
+ );
281
+ assert_ne!(
282
+ value["results"],
283
+ json!({"count": 0}),
284
+ "status `results` must reflect the REAL DB result row, not the placeholder's {{count:0}}; got {}",
285
+ value["results"]
286
+ );
287
+ }
288
+
289
+ // 6 [P1] — cmd_shutdown delegates to the REAL crate::coordinator::stop_coordinator (+ tmux
290
+ // kill-session). The placeholder lifecycle_port::shutdown returns only {ok:true, keep_logs, team}.
291
+ // #[ignore] real-machine: the real shutdown spawns `tmux kill-session` (and SIGTERMs the coordinator);
292
+ // on a no-coordinator workspace the real stop_coordinator outcome (stopped/missing) must surface.
293
+ #[test]
294
+ #[ignore = "real-machine: cmd_shutdown runs the real tmux kill-session + SIGTERMs the coordinator daemon"]
295
+ fn cli_shutdown_invokes_real_stop_coordinator() {
296
+ let ws = seed_status_workspace();
297
+ let args = ShutdownArgs { workspace: ws, team: None, keep_logs: true, json: true };
298
+ let text = outcome_text(cmd_shutdown(&args));
299
+ let lower = text.to_lowercase();
300
+ assert!(
301
+ lower.contains("coordinator") || lower.contains("stopped") || lower.contains("missing"),
302
+ "cmd_shutdown must delegate to the real coordinator::stop_coordinator and surface its outcome \
303
+ (no coordinator -> stopped/missing); the placeholder returns only {{ok,keep_logs,team}}; got {text}"
304
+ );
305
+ }