@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
@@ -1,364 +0,0 @@
1
- """Stage 14 (Gap 37a) — `team-agent doctor --cleanup-orphans` implementation.
2
-
3
- Scans `ps` for processes matching `team_agent.coordinator --workspace <path>` and
4
- classifies any whose workspace path no longer exists (or matches the test-tempdir
5
- pattern) as an orphan. Dry-run by default; --confirm sends SIGTERM.
6
-
7
- Mac mini 2026-05-26 evidence: 35 orphan coordinator processes alive simultaneously
8
- pointing at /var/folders/.../T/team-agent-watcher-dedupe-* paths that had been removed
9
- hours earlier. Each holds a long-lived Python interpreter + SQLite connection.
10
- """
11
- from __future__ import annotations
12
-
13
- import os
14
- import re
15
- import signal
16
- import subprocess
17
- import time
18
- from datetime import datetime, timezone
19
- from typing import Any
20
-
21
- # Pattern: argv contains "team_agent.coordinator --workspace <path>" anywhere.
22
- _COORDINATOR_ARGV_RE = re.compile(
23
- r"team_agent\.coordinator(?:\.__main__)?(?:\s+|.*?)\s--workspace\s+(\S+)"
24
- )
25
- # Test-tempdir patterns that indicate the workspace is ephemeral and almost certainly orphan.
26
- _EPHEMERAL_PATH_HINTS = (
27
- "team-agent-watcher-dedupe-",
28
- "team-agent-gap",
29
- "team-agent-stage",
30
- "team-agent-orchestrator-",
31
- "team-agent-rm-",
32
- "team-agent-claim-",
33
- "team-agent-hotfix",
34
- "team-agent-multi",
35
- "team-agent-progress-",
36
- "team-agent-fanout-",
37
- "team-agent-in-flight-",
38
- "team-agent-test-",
39
- )
40
- _SIGTERM_WAIT_SECONDS = 3.0
41
- _SIGKILL_WAIT_SECONDS = 2.0
42
-
43
-
44
- def find_coordinator_processes(*, runner=subprocess.run) -> list[dict[str, Any]]:
45
- """Return list of {pid, etime, cmdline, workspace} dicts for every running
46
- team_agent.coordinator process visible to ps. workspace is None when the cmdline
47
- doesn't parse — those are noted but not auto-classified as orphan."""
48
- try:
49
- proc = runner(
50
- ["ps", "-Awwo", "pid=,etime=,command="],
51
- text=True,
52
- capture_output=True,
53
- timeout=5,
54
- check=False,
55
- )
56
- except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
57
- return []
58
- if proc.returncode != 0 or not proc.stdout:
59
- return []
60
- rows: list[dict[str, Any]] = []
61
- for line in proc.stdout.splitlines():
62
- parts = line.strip().split(None, 2)
63
- if len(parts) < 3:
64
- continue
65
- pid_s, etime, cmdline = parts[0], parts[1], parts[2]
66
- if "team_agent.coordinator" not in cmdline:
67
- continue
68
- if "ps -Awwo" in cmdline:
69
- continue
70
- try:
71
- pid = int(pid_s)
72
- except ValueError:
73
- continue
74
- if pid == os.getpid():
75
- continue
76
- match = _COORDINATOR_ARGV_RE.search(cmdline)
77
- workspace = match.group(1) if match else None
78
- rows.append({
79
- "pid": pid,
80
- "etime": etime,
81
- "cmdline": cmdline,
82
- "workspace": workspace,
83
- })
84
- return rows
85
-
86
-
87
- def classify_orphan(entry: dict[str, Any]) -> tuple[bool, str]:
88
- """Return (is_orphan, reason). An entry is orphan when its workspace path no longer
89
- exists on disk OR matches a known ephemeral-tempdir pattern (test workspaces should
90
- NEVER spawn long-lived coordinators)."""
91
- workspace = entry.get("workspace")
92
- if not workspace:
93
- return False, "cmdline_unparsed"
94
- if not os.path.exists(workspace):
95
- return True, "workspace_path_missing"
96
- for hint in _EPHEMERAL_PATH_HINTS:
97
- if hint in workspace:
98
- return True, f"ephemeral_tempdir_pattern:{hint}"
99
- return False, "workspace_alive"
100
-
101
-
102
- def cleanup_orphan_coordinators(
103
- *,
104
- confirm: bool = False,
105
- runner=subprocess.run,
106
- killer=os.kill,
107
- pg_killer=None,
108
- pgid_getter=None,
109
- sleeper=time.sleep,
110
- sigterm_wait_seconds: float = _SIGTERM_WAIT_SECONDS,
111
- sigkill_wait_seconds: float = _SIGKILL_WAIT_SECONDS,
112
- ) -> dict[str, Any]:
113
- """Scan for orphan coordinators. Without confirm: dry-run (just classify and report).
114
- With confirm: SIGTERM each orphan, wait up to _SIGTERM_WAIT_SECONDS for graceful
115
- exit; if still alive, escalate to SIGKILL and wait _SIGKILL_WAIT_SECONDS. Only
116
- report status='failed' (with error='alive_after_sigkill') when the process
117
- survives BOTH signals — that's extremely rare and almost always indicates a
118
- zombie/uninterruptible-sleep kernel state.
119
-
120
- Mac mini 2026-05-26 evidence: real orphan coordinators have been observed alive
121
- 40+ hours; many of them never exit on SIGTERM (signal handler suppressed during
122
- long sqlite reads, or the python interpreter is hosting an async loop that
123
- swallows the term signal). SIGKILL escalation is required for production.
124
-
125
- pg_killer / pgid_getter default to os.killpg / os.getpgid; mock them in tests.
126
- If pgid_getter succeeds AND returns a pgid > 1 AND the pgid != pid (i.e. the
127
- process leads its own process group with children), we signal the WHOLE group;
128
- otherwise we signal the pid directly. This catches orphan coordinators that
129
- spawned subprocess.Popen children which would otherwise survive a pid-only
130
- SIGTERM."""
131
- now = datetime.now(timezone.utc).isoformat()
132
- if pg_killer is None:
133
- pg_killer = getattr(os, "killpg", None)
134
- if pgid_getter is None:
135
- pgid_getter = getattr(os, "getpgid", None)
136
- entries = find_coordinator_processes(runner=runner)
137
- classified: list[dict[str, Any]] = []
138
- orphans: list[dict[str, Any]] = []
139
- for entry in entries:
140
- is_orphan, reason = classify_orphan(entry)
141
- annotated = {**entry, "is_orphan": is_orphan, "reason": reason}
142
- classified.append(annotated)
143
- if is_orphan:
144
- orphans.append(annotated)
145
- if not confirm:
146
- return {
147
- "ok": True,
148
- "scanned": len(classified),
149
- "orphans": orphans,
150
- "dry_run": True,
151
- "scanned_at": now,
152
- "action_required": "re-run with --confirm to send SIGTERM",
153
- }
154
- killed: list[dict[str, Any]] = []
155
- failed: list[dict[str, Any]] = []
156
- for entry in orphans:
157
- outcome = _terminate_orphan(
158
- entry["pid"], killer=killer, pg_killer=pg_killer,
159
- pgid_getter=pgid_getter, sleeper=sleeper,
160
- sigterm_wait_seconds=sigterm_wait_seconds,
161
- sigkill_wait_seconds=sigkill_wait_seconds,
162
- )
163
- annotated = {**entry, **outcome}
164
- if outcome.get("status") == "killed":
165
- killed.append(annotated)
166
- elif outcome.get("status") == "missing":
167
- killed.append(annotated)
168
- else:
169
- failed.append(annotated)
170
- return {
171
- "ok": True,
172
- "scanned": len(classified),
173
- "orphans": orphans,
174
- "killed": killed,
175
- "failed": failed,
176
- "dry_run": False,
177
- "scanned_at": now,
178
- }
179
-
180
-
181
- def _terminate_orphan(
182
- pid: int,
183
- *,
184
- killer,
185
- pg_killer,
186
- pgid_getter,
187
- sleeper,
188
- sigterm_wait_seconds: float = _SIGTERM_WAIT_SECONDS,
189
- sigkill_wait_seconds: float = _SIGKILL_WAIT_SECONDS,
190
- ) -> dict[str, Any]:
191
- """SIGTERM → wait 3s → SIGKILL → wait 2s escalation. Returns one of:
192
- {status: 'killed', sigkill_required: False, signaled: 'pid'|'pgid'}
193
- {status: 'killed', sigkill_required: True, signaled: 'pid'|'pgid'}
194
- {status: 'missing', error: '<exc>'} — process gone before SIGTERM
195
- {status: 'failed', error: 'alive_after_sigkill'} — process survived both
196
- {status: 'failed', error: '<exc>'} — permission denied / OS error
197
- """
198
- pgid, pgid_error = _safe_getpgid(pid, pgid_getter)
199
- use_group = bool(pg_killer and pgid is not None and pgid > 1 and pgid != pid)
200
- signaled = "pgid" if use_group else "pid"
201
-
202
- def send(sig: int) -> tuple[bool, str | None]:
203
- try:
204
- if use_group:
205
- pg_killer(pgid, sig)
206
- else:
207
- killer(pid, sig)
208
- except ProcessLookupError:
209
- return False, "process_lookup_error"
210
- except (PermissionError, OSError) as exc:
211
- return False, str(exc)
212
- return True, None
213
-
214
- ok, err = send(signal.SIGTERM)
215
- if not ok:
216
- if err == "process_lookup_error":
217
- return {"status": "missing", "signaled": signaled, "pgid": pgid}
218
- return {"status": "failed", "error": err, "signaled": signaled, "pgid": pgid}
219
- if _wait_for_exit(pid, sigterm_wait_seconds, killer=killer, sleeper=sleeper):
220
- return {
221
- "status": "killed",
222
- "sigkill_required": False,
223
- "signaled": signaled,
224
- "pgid": pgid,
225
- "pgid_error": pgid_error,
226
- }
227
- # SIGTERM did not work — escalate.
228
- ok, err = send(signal.SIGKILL)
229
- if not ok:
230
- if err == "process_lookup_error":
231
- # Race: died between checks.
232
- return {
233
- "status": "killed",
234
- "sigkill_required": False,
235
- "signaled": signaled,
236
- "pgid": pgid,
237
- "pgid_error": pgid_error,
238
- }
239
- return {
240
- "status": "failed",
241
- "error": err,
242
- "signaled": signaled,
243
- "pgid": pgid,
244
- "sigkill_attempted": True,
245
- }
246
- if _wait_for_exit(pid, sigkill_wait_seconds, killer=killer, sleeper=sleeper):
247
- return {
248
- "status": "killed",
249
- "sigkill_required": True,
250
- "signaled": signaled,
251
- "pgid": pgid,
252
- "pgid_error": pgid_error,
253
- }
254
- return {
255
- "status": "failed",
256
- "error": "alive_after_sigkill",
257
- "signaled": signaled,
258
- "pgid": pgid,
259
- "sigkill_required": True,
260
- }
261
-
262
-
263
- def _safe_getpgid(pid: int, pgid_getter) -> tuple[int | None, str | None]:
264
- if pgid_getter is None:
265
- return None, "getpgid_unavailable"
266
- try:
267
- return pgid_getter(pid), None
268
- except (ProcessLookupError, PermissionError, OSError) as exc:
269
- return None, str(exc)
270
-
271
-
272
- def _wait_for_exit(pid: int, timeout: float, *, killer, sleeper) -> bool:
273
- deadline = time.monotonic() + max(timeout, 0.0)
274
- while time.monotonic() < deadline:
275
- try:
276
- killer(pid, 0)
277
- except ProcessLookupError:
278
- return True
279
- except (PermissionError, OSError):
280
- return True
281
- sleeper(0.1)
282
- # Final check after the deadline elapses.
283
- try:
284
- killer(pid, 0)
285
- except ProcessLookupError:
286
- return True
287
- except (PermissionError, OSError):
288
- return True
289
- return False
290
-
291
-
292
- def orphan_gate(
293
- *,
294
- fix: bool = False,
295
- confirm: bool = False,
296
- runner=subprocess.run,
297
- killer=os.kill,
298
- pg_killer=None,
299
- pgid_getter=None,
300
- sleeper=time.sleep,
301
- sigterm_wait_seconds: float = _SIGTERM_WAIT_SECONDS,
302
- sigkill_wait_seconds: float = _SIGKILL_WAIT_SECONDS,
303
- ) -> dict[str, Any]:
304
- if fix and not confirm:
305
- return {
306
- "ok": False,
307
- "gate": "orphans",
308
- "status": "refused",
309
- "reason": "fix_requires_confirm",
310
- "action": "re-run with --gate orphans --fix --confirm",
311
- }
312
- result = cleanup_orphan_coordinators(
313
- confirm=fix and confirm,
314
- runner=runner,
315
- killer=killer,
316
- pg_killer=pg_killer,
317
- pgid_getter=pgid_getter,
318
- sleeper=sleeper,
319
- sigterm_wait_seconds=sigterm_wait_seconds,
320
- sigkill_wait_seconds=sigkill_wait_seconds,
321
- )
322
- orphans = result.get("orphans") or []
323
- failed = result.get("failed") or []
324
- passed = not orphans if not fix else not failed
325
- envelope = {
326
- **result,
327
- "ok": passed,
328
- "gate": "orphans",
329
- "status": "passed" if passed else "failed",
330
- "fix": bool(fix),
331
- }
332
- if not fix and orphans:
333
- envelope["action_required"] = "re-run with --gate orphans --fix --confirm"
334
- return envelope
335
-
336
-
337
- def format_cleanup_orphans(result: dict[str, Any]) -> str:
338
- lines = [
339
- f"Coordinator orphan scan @ {result.get('scanned_at')}",
340
- f" scanned: {result.get('scanned', 0)} coordinator processes",
341
- f" orphans: {len(result.get('orphans') or [])}",
342
- ]
343
- if result.get("dry_run"):
344
- lines.append(" mode: DRY-RUN (no SIGTERM sent; re-run with --confirm)")
345
- else:
346
- killed_entries = result.get("killed") or []
347
- escalated = sum(1 for k in killed_entries if k.get("sigkill_required"))
348
- lines.append(f" killed: {len(killed_entries)} (sigkill_required: {escalated})")
349
- lines.append(f" failed: {len(result.get('failed') or [])}")
350
- for orphan in result.get("orphans") or []:
351
- lines.append(
352
- f" PID {orphan['pid']} etime={orphan['etime']} "
353
- f"workspace={orphan.get('workspace') or '?'} reason={orphan.get('reason')}"
354
- )
355
- return "\n".join(lines)
356
-
357
-
358
- __all__ = [
359
- "cleanup_orphan_coordinators",
360
- "classify_orphan",
361
- "find_coordinator_processes",
362
- "format_cleanup_orphans",
363
- "orphan_gate",
364
- ]
@@ -1,194 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import time
5
- from pathlib import Path
6
- from typing import Any
7
-
8
- from team_agent.diagnose.checks import (
9
- compact_model_checks,
10
- model_checks_for_agents,
11
- profile_checks_for_agents,
12
- profile_smoke_checks_for_agents,
13
- )
14
- from team_agent.events import EventLog
15
- from team_agent.paths import logs_dir, team_workspace
16
- from team_agent.profiles import compact_profile_check
17
- from team_agent.rust_core import core_binary
18
- from team_agent.simple_yaml import dumps
19
-
20
-
21
- def preflight(team_dir: Path) -> dict[str, Any]:
22
- from team_agent.compiler import compile_team
23
- from team_agent.profiles import profile_dir
24
- from team_agent.runtime import (
25
- GHOSTTY_DISPLAY_BACKENDS,
26
- _attach_team_profile_dirs,
27
- _ghostty_command,
28
- ensure_workspace_dirs,
29
- shutil_which,
30
- )
31
-
32
- team_dir = team_dir.resolve()
33
- workspace = team_workspace(team_dir)
34
- ensure_workspace_dirs(workspace)
35
- ensure_profiles_for_roles(team_dir)
36
- event_log = EventLog(workspace)
37
- checks: list[dict[str, Any]] = []
38
- ok = True
39
- spec = None
40
- try:
41
- compiled = compile_team(team_dir)
42
- spec = compiled["spec"]
43
- _attach_team_profile_dirs(spec, team_dir / "team.spec.yaml", workspace, team_dir)
44
- checks.append({"name": "compile", "ok": True, "agents": [a["id"] for a in spec.get("agents", [])]})
45
- except Exception as exc:
46
- ok = False
47
- checks.append({"name": "compile", "ok": False, "error": str(exc)})
48
- tmux_path = shutil_which("tmux")
49
- checks.append({"name": "tmux", "ok": bool(tmux_path), "path": tmux_path})
50
- ok = ok and bool(tmux_path)
51
- ghostty = _ghostty_command()
52
- ghostty_check = {"name": "ghostty", "ok": bool(ghostty), "path": ghostty, "required": False}
53
- if spec and spec.get("runtime", {}).get("display_backend") in GHOSTTY_DISPLAY_BACKENDS:
54
- ghostty_check["required"] = True
55
- ok = ok and bool(ghostty)
56
- checks.append(ghostty_check)
57
- if spec:
58
- profile_checks = profile_checks_for_agents(workspace, spec.get("agents", []))
59
- profile_failures = [item for item in profile_checks if item.get("ok") is False]
60
- checks.append({"name": "profiles", "ok": not profile_failures, "checks": [compact_profile_check(item) for item in profile_checks]})
61
- ok = ok and not profile_failures
62
- smoke_checks = profile_smoke_checks_for_agents(workspace, spec.get("agents", []))
63
- smoke_failures = [item for item in smoke_checks if item.get("ok") is False]
64
- checks.append({"name": "profile_smoke", "ok": not smoke_failures, "checks": [compact_profile_check(item) for item in smoke_checks]})
65
- ok = ok and not smoke_failures
66
- model_checks = model_checks_for_agents(spec.get("agents", []), workspace)
67
- model_failures = [item for item in model_checks if item.get("ok") is False]
68
- checks.append({"name": "models", "ok": not model_failures, "checks": compact_model_checks(model_checks)})
69
- ok = ok and not model_failures
70
- core = core_binary()
71
- checks.append(
72
- {
73
- "name": "rust_core",
74
- "ok": True,
75
- "required": False,
76
- "available": bool(core),
77
- "path": str(core) if core else None,
78
- "status": "available" if core else "python_fallback",
79
- }
80
- )
81
- checks.append({"name": "profile_dir", "ok": profile_dir(workspace).exists() or (team_dir / "profiles").exists()})
82
- details_log = logs_dir(workspace) / f"preflight-{int(time.time())}.json"
83
- details = {"team_dir": str(team_dir), "checks": checks}
84
- details_log.write_text(json.dumps(details, indent=2, ensure_ascii=False), encoding="utf-8")
85
- event_log.write("preflight.complete", ok=ok, details_log=str(details_log), checks=checks)
86
- blockers = [] if ok else preflight_blockers(checks)
87
- return {
88
- "ok": ok,
89
- "summary": "preflight passed" if ok else "preflight found blockers: " + "; ".join(blockers[:3]),
90
- "next_actions": [f"team-agent start --team {team_dir} --yes --json"] if ok else preflight_next_actions(blockers),
91
- "details_log": str(details_log),
92
- "checks": checks,
93
- "blockers": blockers,
94
- }
95
-
96
-
97
- def start(team_dir: Path, yes: bool = False) -> dict[str, Any]:
98
- from team_agent.compiler import compile_team
99
- from team_agent.runtime import launch
100
-
101
- team_dir = team_dir.resolve()
102
- workspace = team_workspace(team_dir)
103
- spec_path = team_dir / "team.spec.yaml"
104
- compiled = compile_team(team_dir, spec_path)
105
- if compiled["spec"].get("context", {}).get("state_file") == "team_state.md":
106
- state_file = str(team_dir.relative_to(workspace) / "team_state.md") if team_dir.is_relative_to(workspace) else "team_state.md"
107
- compiled["spec"]["context"]["state_file"] = state_file
108
- spec_path.write_text(dumps(compiled["spec"]), encoding="utf-8")
109
- launched = launch(spec_path, auto_approve=yes)
110
- details_log = logs_dir(workspace) / f"start-{int(time.time())}.json"
111
- details_log.write_text(json.dumps({"compile": compiled, "launch": launched}, indent=2, ensure_ascii=False), encoding="utf-8")
112
- return {
113
- "ok": bool(launched.get("ok")),
114
- "summary": f"compiled {team_dir} and launched {len(launched.get('agents', []))} agents",
115
- "next_actions": ["team-agent wait-ready --workspace . --timeout 120 --json"],
116
- "details_log": str(details_log),
117
- "spec": str(spec_path),
118
- "launch": launched,
119
- }
120
-
121
-
122
- def preflight_blockers(checks: list[dict[str, Any]]) -> list[str]:
123
- blockers: list[str] = []
124
- for check in checks:
125
- if check.get("ok", True):
126
- continue
127
- name = check.get("name") or "check"
128
- if name == "compile":
129
- blockers.append(f"compile: {check.get('error')}")
130
- continue
131
- for item in check.get("checks", []) or []:
132
- agent = item.get("agent_id") or item.get("profile") or "-"
133
- reason = item.get("reason") or item.get("status") or "failed"
134
- detail = f"{name}: {agent} {reason}"
135
- if item.get("endpoint"):
136
- detail += f" endpoint={item['endpoint']}"
137
- if item.get("proxy_configured"):
138
- detail += f" proxy={item.get('proxy_url') or item.get('proxy_scheme')}"
139
- if item.get("proxy_source"):
140
- detail += f" proxy_source={item['proxy_source']}"
141
- if item.get("proxy_mode"):
142
- detail += f" proxy_mode={item['proxy_mode']}"
143
- if item.get("missing_required"):
144
- detail += " missing=" + ",".join(item["missing_required"])
145
- if item.get("effective_model"):
146
- detail += f" model={item['effective_model']}"
147
- if item.get("suggestion"):
148
- detail += f" suggestion={item['suggestion']}"
149
- blockers.append(detail)
150
- if not check.get("checks"):
151
- blockers.append(f"{name}: failed")
152
- return blockers or ["unknown preflight blocker"]
153
-
154
-
155
- def preflight_next_actions(blockers: list[str]) -> list[str]:
156
- actions = ["Fix failed checks, then rerun preflight."]
157
- if any("proxy_connectivity_failed" in item for item in blockers):
158
- actions.insert(0, "Allow the profile BASE_URL through the configured proxy, or disable the proxy for Team Agent startup.")
159
- if any("proxy_source=ambient" in item for item in blockers):
160
- actions.insert(0, "Current environment proxy is being used for this compatible_api worker; either fix that proxy for BASE_URL, set HTTPS_PROXY/HTTP_PROXY in the profile, or set PROXY_MODE=direct in the profile to bypass proxy for this worker.")
161
- if any("missing=" in item or "profile_required_values_missing" in item for item in blockers):
162
- actions.insert(
163
- 0,
164
- "Ask the human user to fill the local profile file; agents must inspect only with `team-agent profile show <name> --workspace . --json` or the returned --team variant and must not read .team/*/profiles/*.env.",
165
- )
166
- if any("model_mismatch" in item or "does not match profile MODEL" in item for item in blockers):
167
- actions.insert(0, "Keep the model in the profile MODEL field or make the role model exactly match it.")
168
- return actions
169
-
170
-
171
- def ensure_profiles_for_roles(team_dir: Path) -> None:
172
- from team_agent.compiler import _read_front_matter
173
- from team_agent.profiles import ensure_profile_secret_boundary, ensure_profile_secret_boundary_dir, init_profile
174
-
175
- workspace = team_workspace(team_dir)
176
- profiles_dir = team_dir / "profiles"
177
- profiles_dir.mkdir(parents=True, exist_ok=True)
178
- ensure_profile_secret_boundary(workspace)
179
- ensure_profile_secret_boundary_dir(profiles_dir)
180
- for role_doc in sorted((team_dir / "agents").glob("*.md")):
181
- meta, _ = _read_front_matter(role_doc)
182
- profile = meta.get("profile")
183
- auth_mode = meta.get("auth_mode") or "subscription"
184
- if not profile:
185
- continue
186
- if not (profiles_dir / f"{profile}.env").exists() and not (profiles_dir / f"{profile}.example.env").exists():
187
- init_profile(workspace, str(profile), str(auth_mode))
188
- if auth_mode == "subscription":
189
- body = f"AUTH_MODE=subscription\nPROFILE_NAME={profile}\n"
190
- elif auth_mode == "official_api":
191
- body = f"AUTH_MODE=official_api\nPROFILE_NAME={profile}\nAPI_KEY=\nMODEL=\n"
192
- else:
193
- body = f"AUTH_MODE={auth_mode}\nPROFILE_NAME={profile}\nBASE_URL=\nAPI_KEY=\nMODEL=\n"
194
- (profiles_dir / f"{profile}.example.env").write_text(body, encoding="utf-8")