@team-agent/installer 0.2.11 → 0.3.1

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 +1204 -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 +1207 -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 +557 -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 +1084 -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 +526 -0
  59. package/crates/team-agent/src/leader/rediscover.rs +1101 -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 +237 -0
  64. package/crates/team-agent/src/leader/tests/identity.rs +206 -0
  65. package/crates/team-agent/src/leader/tests/idle.rs +272 -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 +410 -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 +489 -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 +2109 -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 +985 -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 +710 -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 +187 -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 +468 -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 +743 -0
  110. package/crates/team-agent/src/messaging/helpers.rs +209 -0
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +329 -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 +553 -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 +578 -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 +659 -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 +765 -0
  172. package/crates/team-agent/src/tmux_backend.rs +810 -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 +118 -112
  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 -119
  203. package/src/team_agent/coordinator/lifecycle.py +0 -411
  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 -218
  226. package/src/team_agent/idle_takeover.py +0 -59
  227. package/src/team_agent/idle_takeover_wiring.py +0 -114
  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 -262
  255. package/src/team_agent/messaging/delivery.py +0 -504
  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 -503
  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 -91
  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 -1243
  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 -144
  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 -693
  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,265 @@
1
+ use super::*;
2
+ use super::agent_ops::lanea_team_ws;
3
+ use super::lane_ops::{LaneSpawns, LaneTransport};
4
+ use super::launch_spawn::{
5
+ restart_ws_two_resumable_workers, seed_healthy_coordinator, DELEG_ROLE_ALPHA, DELEG_ROLE_BRAVO,
6
+ };
7
+
8
+ type RecordedSpawns = LaneSpawns;
9
+
10
+ // ═════════════════════════════════════════════════════════════════════════
11
+ // BUG-1 [highest · regression] — respawn into a DEAD tmux session/`-L` server
12
+ // must RE-CREATE the session (new-session / spawn_first), NOT add a window to a
13
+ // server that no longer exists (new-window / spawn_into → "no server running" →
14
+ // orphaned agent). `reset --discard-session` kills the agent's last window; tmux
15
+ // then destroys the whole session AND its CP-1 `-L` server. The respawn that
16
+ // follows must therefore new-session.
17
+ //
18
+ // Golden runtime.py:1017 `_tmux_start_command_for_agent_window`:
19
+ // if _tmux_session_exists(session_name): # live `tmux has-session -t <name>`
20
+ // return ["tmux","new-window",...], "new-window"
21
+ // return ["tmux","new-session","-d",...], "new-session"
22
+ // The new-session-vs-new-window choice is driven by a LIVE `has-session` probe,
23
+ // NOT by whether state["session_name"] is a non-empty string.
24
+ //
25
+ // Today `start_agent_with_transport` computes `into_existing_session =
26
+ // session_name_present(&state)` (a pure state-string check; restart.rs:129) and
27
+ // NEVER consults `transport.has_session(...)` — so a discarded/dead session
28
+ // (state string still set) takes the spawn_into branch. The socket fix
29
+ // (0f886a9/4435f9a) covered status/shutdown but missed this lifecycle respawn path.
30
+ // ═════════════════════════════════════════════════════════════════════════
31
+ /// Recording transport with a CONFIGURABLE `has_session` — models the real
32
+ /// `tmux has-session` probe the respawn spawn-decision MUST consult. Records
33
+ /// every spawn (kind + argv) like `RecordingLaunchTransport`.
34
+ struct SessionProbeRecordingTransport {
35
+ spawns: RecordedSpawns,
36
+ session_exists: bool,
37
+ }
38
+ impl crate::transport::Transport for SessionProbeRecordingTransport {
39
+ fn kind(&self) -> crate::transport::BackendKind {
40
+ crate::transport::BackendKind::Tmux
41
+ }
42
+ fn spawn_first(&self, session: &crate::transport::SessionName, window: &crate::transport::WindowName, argv: &[String], _cwd: &std::path::Path, _env: &std::collections::BTreeMap<String, String>) -> Result<crate::transport::SpawnResult, crate::transport::TransportError> {
43
+ self.spawns.lock().unwrap().push(("spawn_first".to_string(), argv.to_vec()));
44
+ Ok(crate::transport::SpawnResult { pane_id: crate::transport::PaneId::new("%0"), session: session.clone(), window: window.clone(), child_pid: None })
45
+ }
46
+ fn spawn_into(&self, session: &crate::transport::SessionName, window: &crate::transport::WindowName, argv: &[String], _cwd: &std::path::Path, _env: &std::collections::BTreeMap<String, String>) -> Result<crate::transport::SpawnResult, crate::transport::TransportError> {
47
+ self.spawns.lock().unwrap().push(("spawn_into".to_string(), argv.to_vec()));
48
+ Ok(crate::transport::SpawnResult { pane_id: crate::transport::PaneId::new(format!("%{}", window.as_str())), session: session.clone(), window: window.clone(), child_pid: None })
49
+ }
50
+ fn inject(&self, _t: &crate::transport::Target, _p: &crate::transport::InjectPayload, _s: crate::transport::Key, _b: bool) -> Result<crate::transport::InjectReport, crate::transport::TransportError> {
51
+ unimplemented!("not reached by start_agent respawn")
52
+ }
53
+ fn send_keys(&self, _t: &crate::transport::Target, _k: &[crate::transport::Key]) -> Result<(), crate::transport::TransportError> {
54
+ unimplemented!("not reached")
55
+ }
56
+ fn capture(&self, _t: &crate::transport::Target, _r: crate::transport::CaptureRange) -> Result<crate::transport::CapturedText, crate::transport::TransportError> {
57
+ unimplemented!("not reached")
58
+ }
59
+ fn query(&self, _t: &crate::transport::Target, _f: crate::transport::PaneField) -> Result<Option<String>, crate::transport::TransportError> {
60
+ unimplemented!("not reached")
61
+ }
62
+ fn liveness(&self, _p: &crate::transport::PaneId) -> Result<crate::model::enums::PaneLiveness, crate::transport::TransportError> {
63
+ unimplemented!("not reached")
64
+ }
65
+ fn list_targets(&self) -> Result<Vec<crate::transport::PaneInfo>, crate::transport::TransportError> {
66
+ unimplemented!("not reached")
67
+ }
68
+ fn has_session(&self, _s: &crate::transport::SessionName) -> Result<bool, crate::transport::TransportError> {
69
+ Ok(self.session_exists)
70
+ }
71
+ fn list_windows(&self, _s: &crate::transport::SessionName) -> Result<Vec<crate::transport::WindowName>, crate::transport::TransportError> {
72
+ // a dead server has no windows; a live one has the agent window.
73
+ Ok(if self.session_exists { vec![crate::transport::WindowName::new("alpha")] } else { Vec::new() })
74
+ }
75
+ fn set_session_env(&self, _s: &crate::transport::SessionName, _k: &str, _v: &str) -> Result<crate::transport::SetEnvOutcome, crate::transport::TransportError> {
76
+ unimplemented!("not reached")
77
+ }
78
+ fn kill_session(&self, _s: &crate::transport::SessionName) -> Result<(), crate::transport::TransportError> {
79
+ unimplemented!("not reached")
80
+ }
81
+ fn kill_window(&self, _t: &crate::transport::Target) -> Result<(), crate::transport::TransportError> {
82
+ Ok(())
83
+ }
84
+ fn attach_session(&self, _s: &crate::transport::SessionName) -> Result<crate::transport::AttachOutcome, crate::transport::TransportError> {
85
+ unimplemented!("not reached")
86
+ }
87
+ }
88
+ fn respawn_ws_one_resumable_worker() -> PathBuf {
89
+ let ws = temp_ws().join("respawn_dead_session");
90
+ std::fs::create_dir_all(&ws).unwrap();
91
+ crate::state::persist::save_runtime_state(
92
+ &ws,
93
+ &json!({
94
+ "session_name": "team-sa",
95
+ "agents": {"alpha": {"status": "running", "provider": "codex", "session_id": "sess-a", "first_send_at": "2026-05-27T10:00:00+00:00"}}
96
+ }),
97
+ )
98
+ .unwrap();
99
+ seed_healthy_coordinator(&ws);
100
+ ws
101
+ }
102
+ // RED — the respawn-time spawn decision must consult the LIVE session probe. With
103
+ // `has_session=false` (the `-L` server destroyed by --discard-session killing the
104
+ // last window), the respawn MUST new-session (spawn_first). Today `into_existing_session
105
+ // = session_name_present(&state)` is true (state string still "team-sa") so the code
106
+ // records spawn_into against a dead server -> assertion fails -> RED. NOT a panic:
107
+ // the transport implements has_session, so the only failure is the wrong spawn kind.
108
+ #[test]
109
+ fn start_agent_respawn_into_dead_session_uses_new_session_not_new_window() {
110
+ let ws = respawn_ws_one_resumable_worker();
111
+ let spawns = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
112
+ // session_exists=false models the dead CP-1 `-L` server after --discard-session.
113
+ let transport = SessionProbeRecordingTransport {
114
+ spawns: std::sync::Arc::clone(&spawns),
115
+ session_exists: false,
116
+ };
117
+ let _ = start_agent_with_transport(&ws, &AgentId::new("alpha"), false, false, false, None, &transport);
118
+ let recorded = spawns.lock().unwrap().clone();
119
+ assert_eq!(recorded.len(), 1, "exactly one respawn for alpha; got {recorded:?}");
120
+ assert_eq!(
121
+ recorded[0].0, "spawn_first",
122
+ "golden runtime.py:1017: when `tmux has-session` is FALSE (dead -L server after \
123
+ --discard-session destroyed the last window), the respawn MUST new-session \
124
+ (spawn_first), NOT new-window (spawn_into) — spawn_into a dead server crashes with \
125
+ 'no server running' and orphans the agent. The decision must come from \
126
+ transport.has_session(), not session_name_present(&state); got {recorded:?}"
127
+ );
128
+ }
129
+ // ═════════════════════════════════════════════════════════════════════════
130
+ // reset/remove CANONICAL-WORKSPACE regression-locks (bug-A class: e17096c/0f886a9/4435f9a).
131
+ // rt-host-a @ c262bf7: `reset`/`remove --workspace <teamdir/.team>` resolved to the SUBPATH's own
132
+ // .team/runtime instead of the LIVE parent run-workspace -> they operated on a detached context
133
+ // (live roster/windows untouched; respawn went nowhere -> post-reset dispatch died). `start` dodged it
134
+ // by being invoked with $ws. FIX (porter-a): reset/remove canonicalize the run-workspace
135
+ // (lifecycle_paths -> run_workspace = canonical_run_workspace(input)), read spec from the teamdir, and
136
+ // reset reuses start's live-session-probe respawn. These LOCK the canonical resolution so it can't
137
+ // re-regress.
138
+ // ═════════════════════════════════════════════════════════════════════════
139
+ // item ① — reset --discard-session must REBUILD the worker's window via the SAME respawn path as start
140
+ // (reset_agent_at_paths -> start_agent_with_transport). Into a LIVE session (has_session=true) the
141
+ // rebuild is a new-window (spawn_into). The regression had reset operate on a detached runtime so the
142
+ // window was never rebuilt. OS-safe: SessionProbeRecordingTransport (no real tmux) + seeded coordinator.
143
+ #[test]
144
+ fn reset_agent_discard_session_rebuilds_window_via_start_respawn() {
145
+ let ws = restart_ws_two_resumable_workers(); // compiled spec + state(alpha,bravo running) + seeded coordinator
146
+ let spawns = std::sync::Arc::new(std::sync::Mutex::new(Vec::new()));
147
+ // live session present (other workers keep it alive) but alpha's window was discarded.
148
+ let transport = SessionProbeRecordingTransport {
149
+ spawns: std::sync::Arc::clone(&spawns),
150
+ session_exists: true,
151
+ };
152
+ let _ = reset_agent_with_transport(&ws, &AgentId::new("alpha"), true, false, None, &transport);
153
+ let recorded = spawns.lock().unwrap().clone();
154
+ assert!(
155
+ !recorded.is_empty(),
156
+ "reset --discard-session must RESPAWN alpha's window (reuse start's respawn path); the bug-A \
157
+ regression operated on a detached runtime so nothing was rebuilt -> ZERO spawns. got {recorded:?}"
158
+ );
159
+ assert_eq!(
160
+ recorded.last().unwrap().0, "spawn_into",
161
+ "into a LIVE session (has_session=true) the window rebuild is a new-window (spawn_into); got {recorded:?}"
162
+ );
163
+ }
164
+ // item ② — remove invoked with a NON-canonical input (the workspace's own `.team` subpath) must resolve
165
+ // the CANONICAL live run-workspace and apply the removal to the LIVE roster — not the subpath's detached
166
+ // runtime. canonical_run_workspace($ws/.team) == $ws. The regression made remove "succeed" against a
167
+ // detached context, leaving the live roster [alpha,bravo] intact. (lanea_team_ws re-points routing to
168
+ // bravo so removing alpha validates cleanly.)
169
+ #[test]
170
+ fn remove_agent_via_team_subpath_applies_to_canonical_live_roster() {
171
+ let ws = lanea_team_ws("stopped"); // canonical workspace: spec + state roster [alpha,bravo]
172
+ let subpath = ws.join(".team"); // non-canonical input; canonicalizes back to $ws
173
+ let tx = LaneTransport::new("team-laneateam", &[]);
174
+ let outcome = remove_agent_with_transport(&subpath, &aid("alpha"), true, true, None, &tx);
175
+ assert!(
176
+ matches!(outcome, Ok(RemoveAgentOutcome::Removed { .. })),
177
+ "remove via the .team subpath must resolve the canonical live workspace and succeed; got {outcome:?}"
178
+ );
179
+ let state = crate::state::persist::load_runtime_state(&ws).expect("load LIVE $ws state");
180
+ assert!(
181
+ state.get("agents").and_then(serde_json::Value::as_object).is_some_and(|a| !a.contains_key("alpha")),
182
+ "remove must apply to the LIVE $ws roster (canonical resolution), removing alpha; the bug-A \
183
+ regression operated on the subpath's detached runtime, leaving the live roster intact. state={state}"
184
+ );
185
+ }
186
+ /// bug-B [✦ DIVERGENCE] fixture — a NORMAL compiled 2-agent team with routing/tasks LEFT EXACTLY AS
187
+ /// COMPILED (route-alpha + route-bravo, default_assignee=alpha, tasks[0].assignee=alpha). Unlike
188
+ /// lanea_team_ws / lanea_ws_agents (which deliberately re-point refs away so a remove validates), this
189
+ /// is the REAL "remove a normal worker" case — the one golden cannot handle (every compiled agent is
190
+ /// routed, compiler.py:57). Both workers seeded `stopped` (no tmux); LaneTransport reports no windows.
191
+ fn bugb_routed_team_ws() -> PathBuf {
192
+ let ws = temp_ws().join("bugb_routed");
193
+ std::fs::create_dir_all(ws.join("agents")).unwrap();
194
+ std::fs::write(ws.join("TEAM.md"), "---\nname: laneateam\nobjective: bug-B routed-remove probe.\nprovider: codex\n---\n\nteam.\n").unwrap();
195
+ std::fs::write(ws.join("agents").join("alpha.md"), DELEG_ROLE_ALPHA).unwrap();
196
+ std::fs::write(ws.join("agents").join("bravo.md"), DELEG_ROLE_BRAVO).unwrap();
197
+ let spec = crate::compiler::compile_team(&ws).expect("compile bug-B team");
198
+ let yaml = crate::model::yaml::dumps(&spec);
199
+ // sanity: the UNMODIFIED compiled spec really routes bravo — that route-bravo rule is the dangling
200
+ // ref the cascade-prune must drop (and which makes golden's validate_spec raise + rollback).
201
+ assert!(yaml.contains("assign_to: \"bravo\""), "fixture: compiled spec must carry route-bravo (assign_to bravo); got:\n{yaml}");
202
+ std::fs::write(ws.join("team.spec.yaml"), yaml).unwrap();
203
+ crate::state::persist::save_runtime_state(
204
+ &ws,
205
+ &json!({
206
+ "session_name": "team-laneateam",
207
+ "agents": {
208
+ "alpha": {"status": "stopped", "provider": "codex", "window": "alpha"},
209
+ "bravo": {"status": "stopped", "provider": "codex", "window": "bravo"}
210
+ }
211
+ }),
212
+ )
213
+ .unwrap();
214
+ ws
215
+ }
216
+ // ── bug-B [✦ DIVERGENCE — REVERSES the previously-DECLINED byte-parity decision (ticket:154)] ─────────
217
+ // Removing a NORMAL routed worker must SUCCEED and surgically PRUNE that worker's dangling routing rule,
218
+ // NOT refuse+rollback. Golden agents.py:88-94 removes the agent only from `agents` + `runtime.startup_order`
219
+ // (routing UNTOUCHED), then validate_spec RAISES `/routing/rules/N/assign_to: unknown agent 'bravo'`
220
+ // (spec.py:341-346) -> remove catches -> rollback (rollback_ok=true). Because compile_team auto-wires a
221
+ // `route-<id>` rule (assign_to:<id>) for EVERY agent (compiler.py:57), NO compiled worker is removable in
222
+ // golden. Probed live (/tmp/probe_removeB2.py): validate_spec of the bravo-removed spec raises exactly
223
+ // "/routing/rules/1/assign_to: unknown agent 'bravo'". The advisor/leader requests the cascade-prune
224
+ // parity-IMPROVEMENT (ticket:155,197): remove must drop routing rules whose assign_to == the removed
225
+ // agent (and reset default_assignee if it pointed at it) so the spec stays valid. Current Rust faithfully
226
+ // mirrors golden (restart.rs:897-899 validate_spec -> Err(Compile) -> rollback) -> RED.
227
+ //
228
+ // NOTE for the porter: this REVERSES the documented byte-parity intent at restart.rs:931-937 and the
229
+ // lanea_team_ws/lanea_ws_agents fixtures' re-point workaround. Implementing the prune is correct ONLY
230
+ // if the leader's ✦ divergence ruling stands — do not "fix" by reverting this test.
231
+ //
232
+ // Removing `bravo` (the SECOND agent) isolates the routing-rule prune cleanly: only `route-bravo` dangles;
233
+ // default_assignee (alpha) and tasks[0].assignee (alpha) stay valid, so this test does NOT entangle the
234
+ // default_assignee / task-reassignment policy.
235
+ #[test]
236
+ fn remove_routed_worker_prunes_dangling_routing_rule_divergence() {
237
+ let ws = bugb_routed_team_ws();
238
+ let tx = LaneTransport::new("team-laneateam", &[]); // bravo not running (no windows)
239
+ let outcome = remove_agent_with_transport(&ws, &aid("bravo"), true, true, None, &tx); // from_spec + force
240
+ assert!(
241
+ matches!(outcome, Ok(RemoveAgentOutcome::Removed { .. })),
242
+ "✦ divergence: removing the routed worker 'bravo' must SUCCEED (Removed), not refuse+rollback. \
243
+ Golden + current Rust raise /routing/rules/.../assign_to: unknown agent 'bravo' -> \
244
+ StatePersist rollback_ok=true; got {outcome:?}"
245
+ );
246
+ let yaml = std::fs::read_to_string(ws.join("team.spec.yaml")).expect("read spec after remove");
247
+ assert!(
248
+ !yaml.contains("assign_to: \"bravo\""),
249
+ "✦ divergence: remove must PRUNE the dangling route-bravo rule (assign_to: \"bravo\"); spec still has it:\n{yaml}"
250
+ );
251
+ assert!(
252
+ yaml.contains("assign_to: \"alpha\""),
253
+ "the prune must be SURGICAL — route-alpha (the STAYING agent) must remain; got:\n{yaml}"
254
+ );
255
+ assert!(
256
+ yaml.contains("default_assignee: \"alpha\""),
257
+ "default_assignee (alpha, still present) must be untouched by the prune; got:\n{yaml}"
258
+ );
259
+ let state = crate::state::persist::load_runtime_state(&ws).expect("load state");
260
+ assert!(
261
+ state.get("agents").and_then(serde_json::Value::as_object).is_some_and(|a| !a.contains_key("bravo")),
262
+ "bravo must also be removed from state.agents; got {state:?}"
263
+ );
264
+ let _ = std::fs::remove_dir_all(&ws);
265
+ }
@@ -0,0 +1,27 @@
1
+ #![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
2
+ use super::*;
3
+ use serde_json::json;
4
+ use std::sync::atomic::{AtomicU32, Ordering};
5
+
6
+ static SEQ: AtomicU32 = AtomicU32::new(0);
7
+
8
+ /// 隔离的空 workspace。仿 sibling(event_log.rs)风格,不依赖 tempfile dev-dep。
9
+ fn temp_ws() -> PathBuf {
10
+ let n = SEQ.fetch_add(1, Ordering::Relaxed);
11
+ let ws = std::env::temp_dir().join(format!("ta_rs_lc_{}_{}", std::process::id(), n));
12
+ std::fs::create_dir_all(&ws).unwrap();
13
+ ws
14
+ }
15
+
16
+ fn aid(s: &str) -> AgentId {
17
+ AgentId::new(s)
18
+ }
19
+ fn sess(s: &str) -> SessionName {
20
+ SessionName(s.to_string())
21
+ }
22
+
23
+ mod core;
24
+ mod launch_spawn;
25
+ mod agent_ops;
26
+ mod lane_ops;
27
+ mod main_preserved;