@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,247 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import copy
4
- import json
5
- import os
6
- import re
7
- import subprocess
8
- import time
9
- from datetime import datetime, timedelta, timezone
10
- from typing import Any
11
-
12
- from team_agent import runtime as _runtime
13
- from team_agent.errors import RuntimeError, ValidationError
14
- from team_agent.events import EventLog
15
- from team_agent.message_store import MessageStore
16
- from team_agent.paths import runtime_dir
17
- from team_agent.permissions import missing_tools
18
- from team_agent.routing import route_task
19
- from team_agent.spec import load_spec, validate_result_envelope
20
- from team_agent.state import (
21
- ambiguous_team_target_result,
22
- check_team_owner,
23
- load_runtime_state,
24
- save_runtime_state,
25
- save_team_scoped_state,
26
- select_runtime_state,
27
- team_state_key,
28
- write_team_state,
29
- )
30
- from team_agent.task_graph import update_task_status
31
-
32
- # Explicit runtime dependency surface for messaging extraction. Wrappers keep
33
- # runtime monkeypatch points stable while avoiding module-wide globals sync.
34
- _RUNTIME_CONSTANTS = (
35
- "DELIVERY_CAPTURE_LINES",
36
- "PASTED_CONTENT_PROMPT_RE",
37
- "TMUX_PANE_FORMAT",
38
- "TMUX_PASTE_BYTES_PER_SECOND",
39
- "TMUX_PASTE_MAX_READY_TIMEOUT",
40
- "TMUX_PASTE_MIN_READY_TIMEOUT",
41
- "TMUX_STDIN_BUFFER_THRESHOLD",
42
- "TMUX_SUBMIT_BYTES_PER_SECOND",
43
- "TMUX_SUBMIT_MAX_SETTLE_TIMEOUT",
44
- "TMUX_SUBMIT_MIN_SETTLE_TIMEOUT",
45
- )
46
- _RUNTIME_PATCH_POINTS = (
47
- "_capture_has_pasted_content_prompt",
48
- "_capture_missing_sessions",
49
- "_capture_tmux_pane_text",
50
- "_choose_leader_submit_key",
51
- "_current_task_for_agent",
52
- "_deliver_pending_message",
53
- "_deliver_pending_messages",
54
- "_find_agent",
55
- "_find_task",
56
- "_find_task_or_none",
57
- "_format_team_agent_message",
58
- "_handle_provider_runtime_prompts",
59
- "_handle_provider_startup_prompts",
60
- "_is_leader_sender",
61
- "_is_leader_target",
62
- "_is_message_scoped_result",
63
- "_is_runtime_team_agent",
64
- "_leader_id",
65
- "_leader_receiver_is_direct",
66
- "_message_by_id",
67
- "_message_payload",
68
- "_mirror_peer_message_to_leader",
69
- "_notify_leader_of_report_result",
70
- "_rediscover_leader_receiver",
71
- "_refresh_agent_runtime_statuses",
72
- "_result_status_to_task_status",
73
- "_runtime_lock",
74
- "_runtime_team_agent_ids",
75
- "_send_to_leader_receiver",
76
- "_submit_worker_prompt",
77
- "_tmux_inject_text",
78
- "_tmux_load_buffer_stdin",
79
- "_tmux_pane_info",
80
- "_tmux_paste_ready_timeout",
81
- "_tmux_set_buffer_text",
82
- "_tmux_submit_settle_timeout",
83
- "_tmux_window_exists",
84
- "_validate_leader_receiver",
85
- "_wait_for_message_ready",
86
- "_wait_for_worker_message_ready",
87
- "core_list_targets",
88
- "core_render_message",
89
- "run_cmd",
90
- "send_message",
91
- "start_coordinator",
92
- )
93
- for _name in (*_RUNTIME_CONSTANTS, *_RUNTIME_PATCH_POINTS):
94
- if not hasattr(_runtime, _name):
95
- raise ImportError(f"team_agent.runtime missing messaging dependency: {_name}")
96
-
97
- DELIVERY_CAPTURE_LINES = _runtime.DELIVERY_CAPTURE_LINES
98
- PASTED_CONTENT_PROMPT_RE = _runtime.PASTED_CONTENT_PROMPT_RE
99
- TMUX_PANE_FORMAT = _runtime.TMUX_PANE_FORMAT
100
- TMUX_PASTE_BYTES_PER_SECOND = _runtime.TMUX_PASTE_BYTES_PER_SECOND
101
- TMUX_PASTE_MAX_READY_TIMEOUT = _runtime.TMUX_PASTE_MAX_READY_TIMEOUT
102
- TMUX_PASTE_MIN_READY_TIMEOUT = _runtime.TMUX_PASTE_MIN_READY_TIMEOUT
103
- TMUX_STDIN_BUFFER_THRESHOLD = _runtime.TMUX_STDIN_BUFFER_THRESHOLD
104
- TMUX_SUBMIT_BYTES_PER_SECOND = _runtime.TMUX_SUBMIT_BYTES_PER_SECOND
105
- TMUX_SUBMIT_MAX_SETTLE_TIMEOUT = _runtime.TMUX_SUBMIT_MAX_SETTLE_TIMEOUT
106
- TMUX_SUBMIT_MIN_SETTLE_TIMEOUT = _runtime.TMUX_SUBMIT_MIN_SETTLE_TIMEOUT
107
-
108
-
109
- def _runtime_symbol(name: str) -> Any:
110
- return getattr(_runtime, name)
111
-
112
- def _capture_has_pasted_content_prompt(*args: Any, **kwargs: Any) -> Any:
113
- return _runtime_symbol("_capture_has_pasted_content_prompt")(*args, **kwargs)
114
-
115
- def _capture_missing_sessions(*args: Any, **kwargs: Any) -> Any:
116
- return _runtime_symbol("_capture_missing_sessions")(*args, **kwargs)
117
-
118
- def _capture_tmux_pane_text(*args: Any, **kwargs: Any) -> Any:
119
- return _runtime_symbol("_capture_tmux_pane_text")(*args, **kwargs)
120
-
121
- def _choose_leader_submit_key(*args: Any, **kwargs: Any) -> Any:
122
- return _runtime_symbol("_choose_leader_submit_key")(*args, **kwargs)
123
-
124
- def _current_task_for_agent(*args: Any, **kwargs: Any) -> Any:
125
- return _runtime_symbol("_current_task_for_agent")(*args, **kwargs)
126
-
127
- def _deliver_pending_message(*args: Any, **kwargs: Any) -> Any:
128
- return _runtime_symbol("_deliver_pending_message")(*args, **kwargs)
129
-
130
- def _deliver_pending_messages(*args: Any, **kwargs: Any) -> Any:
131
- return _runtime_symbol("_deliver_pending_messages")(*args, **kwargs)
132
-
133
- def _find_agent(*args: Any, **kwargs: Any) -> Any:
134
- return _runtime_symbol("_find_agent")(*args, **kwargs)
135
-
136
- def _find_task(*args: Any, **kwargs: Any) -> Any:
137
- return _runtime_symbol("_find_task")(*args, **kwargs)
138
-
139
- def _find_task_or_none(*args: Any, **kwargs: Any) -> Any:
140
- return _runtime_symbol("_find_task_or_none")(*args, **kwargs)
141
-
142
- def _format_team_agent_message(*args: Any, **kwargs: Any) -> Any:
143
- return _runtime_symbol("_format_team_agent_message")(*args, **kwargs)
144
-
145
- def _handle_provider_runtime_prompts(*args: Any, **kwargs: Any) -> Any:
146
- return _runtime_symbol("_handle_provider_runtime_prompts")(*args, **kwargs)
147
-
148
- def _handle_provider_startup_prompts(*args: Any, **kwargs: Any) -> Any:
149
- return _runtime_symbol("_handle_provider_startup_prompts")(*args, **kwargs)
150
-
151
- def _is_leader_sender(*args: Any, **kwargs: Any) -> Any:
152
- return _runtime_symbol("_is_leader_sender")(*args, **kwargs)
153
-
154
- def _is_leader_target(*args: Any, **kwargs: Any) -> Any:
155
- return _runtime_symbol("_is_leader_target")(*args, **kwargs)
156
-
157
- def _is_message_scoped_result(*args: Any, **kwargs: Any) -> Any:
158
- return _runtime_symbol("_is_message_scoped_result")(*args, **kwargs)
159
-
160
- def _is_runtime_team_agent(*args: Any, **kwargs: Any) -> Any:
161
- return _runtime_symbol("_is_runtime_team_agent")(*args, **kwargs)
162
-
163
- def _leader_id(*args: Any, **kwargs: Any) -> Any:
164
- return _runtime_symbol("_leader_id")(*args, **kwargs)
165
-
166
- def _leader_receiver_is_direct(*args: Any, **kwargs: Any) -> Any:
167
- return _runtime_symbol("_leader_receiver_is_direct")(*args, **kwargs)
168
-
169
- def _message_by_id(*args: Any, **kwargs: Any) -> Any:
170
- return _runtime_symbol("_message_by_id")(*args, **kwargs)
171
-
172
- def _message_payload(*args: Any, **kwargs: Any) -> Any:
173
- return _runtime_symbol("_message_payload")(*args, **kwargs)
174
-
175
- def _mirror_peer_message_to_leader(*args: Any, **kwargs: Any) -> Any:
176
- return _runtime_symbol("_mirror_peer_message_to_leader")(*args, **kwargs)
177
-
178
- def _notify_leader_of_report_result(*args: Any, **kwargs: Any) -> Any:
179
- return _runtime_symbol("_notify_leader_of_report_result")(*args, **kwargs)
180
-
181
- def _rediscover_leader_receiver(*args: Any, **kwargs: Any) -> Any:
182
- return _runtime_symbol("_rediscover_leader_receiver")(*args, **kwargs)
183
-
184
- def _refresh_agent_runtime_statuses(*args: Any, **kwargs: Any) -> Any:
185
- return _runtime_symbol("_refresh_agent_runtime_statuses")(*args, **kwargs)
186
-
187
- def _result_status_to_task_status(*args: Any, **kwargs: Any) -> Any:
188
- return _runtime_symbol("_result_status_to_task_status")(*args, **kwargs)
189
-
190
- def _runtime_lock(*args: Any, **kwargs: Any) -> Any:
191
- return _runtime_symbol("_runtime_lock")(*args, **kwargs)
192
-
193
- def _runtime_team_agent_ids(*args: Any, **kwargs: Any) -> Any:
194
- return _runtime_symbol("_runtime_team_agent_ids")(*args, **kwargs)
195
-
196
- def _send_to_leader_receiver(*args: Any, **kwargs: Any) -> Any:
197
- return _runtime_symbol("_send_to_leader_receiver")(*args, **kwargs)
198
-
199
- def _submit_worker_prompt(*args: Any, **kwargs: Any) -> Any:
200
- return _runtime_symbol("_submit_worker_prompt")(*args, **kwargs)
201
-
202
- def _tmux_inject_text(*args: Any, **kwargs: Any) -> Any:
203
- return _runtime_symbol("_tmux_inject_text")(*args, **kwargs)
204
-
205
- def _tmux_pane_info(*args: Any, **kwargs: Any) -> Any:
206
- return _runtime_symbol("_tmux_pane_info")(*args, **kwargs)
207
-
208
- def _tmux_load_buffer_stdin(*args: Any, **kwargs: Any) -> Any:
209
- return _runtime_symbol("_tmux_load_buffer_stdin")(*args, **kwargs)
210
-
211
- def _tmux_paste_ready_timeout(*args: Any, **kwargs: Any) -> Any:
212
- return _runtime_symbol("_tmux_paste_ready_timeout")(*args, **kwargs)
213
-
214
- def _tmux_set_buffer_text(*args: Any, **kwargs: Any) -> Any:
215
- return _runtime_symbol("_tmux_set_buffer_text")(*args, **kwargs)
216
-
217
- def _tmux_submit_settle_timeout(*args: Any, **kwargs: Any) -> Any:
218
- return _runtime_symbol("_tmux_submit_settle_timeout")(*args, **kwargs)
219
-
220
- def _tmux_window_exists(*args: Any, **kwargs: Any) -> Any:
221
- return _runtime_symbol("_tmux_window_exists")(*args, **kwargs)
222
-
223
- def _validate_leader_receiver(*args: Any, **kwargs: Any) -> Any:
224
- return _runtime_symbol("_validate_leader_receiver")(*args, **kwargs)
225
-
226
- def _wait_for_message_ready(*args: Any, **kwargs: Any) -> Any:
227
- return _runtime_symbol("_wait_for_message_ready")(*args, **kwargs)
228
-
229
- def _wait_for_worker_message_ready(*args: Any, **kwargs: Any) -> Any:
230
- return _runtime_symbol("_wait_for_worker_message_ready")(*args, **kwargs)
231
-
232
- def run_cmd(*args: Any, **kwargs: Any) -> Any:
233
- return _runtime_symbol("run_cmd")(*args, **kwargs)
234
-
235
- def core_list_targets(*args: Any, **kwargs: Any) -> Any:
236
- return _runtime_symbol("core_list_targets")(*args, **kwargs)
237
-
238
- def core_render_message(*args: Any, **kwargs: Any) -> Any:
239
- return _runtime_symbol("core_render_message")(*args, **kwargs)
240
-
241
- def send_message(*args: Any, **kwargs: Any) -> Any:
242
- return _runtime_symbol("send_message")(*args, **kwargs)
243
-
244
- def start_coordinator(*args: Any, **kwargs: Any) -> Any:
245
- return _runtime_symbol("start_coordinator")(*args, **kwargs)
246
-
247
- __all__ = ['DELIVERY_CAPTURE_LINES', 'EventLog', 'MessageStore', 'PASTED_CONTENT_PROMPT_RE', 'RuntimeError', 'TMUX_PANE_FORMAT', 'TMUX_PASTE_BYTES_PER_SECOND', 'TMUX_PASTE_MAX_READY_TIMEOUT', 'TMUX_PASTE_MIN_READY_TIMEOUT', 'TMUX_STDIN_BUFFER_THRESHOLD', 'TMUX_SUBMIT_BYTES_PER_SECOND', 'TMUX_SUBMIT_MAX_SETTLE_TIMEOUT', 'TMUX_SUBMIT_MIN_SETTLE_TIMEOUT', 'ValidationError', '_capture_has_pasted_content_prompt', '_capture_missing_sessions', '_capture_tmux_pane_text', '_choose_leader_submit_key', '_current_task_for_agent', '_deliver_pending_message', '_deliver_pending_messages', '_find_agent', '_find_task', '_find_task_or_none', '_format_team_agent_message', '_handle_provider_runtime_prompts', '_handle_provider_startup_prompts', '_is_leader_sender', '_is_leader_target', '_is_message_scoped_result', '_is_runtime_team_agent', '_leader_id', '_leader_receiver_is_direct', '_message_by_id', '_message_payload', '_mirror_peer_message_to_leader', '_notify_leader_of_report_result', '_rediscover_leader_receiver', '_refresh_agent_runtime_statuses', '_result_status_to_task_status', '_runtime_lock', '_runtime_team_agent_ids', '_send_to_leader_receiver', '_submit_worker_prompt', '_tmux_inject_text', '_tmux_load_buffer_stdin', '_tmux_pane_info', '_tmux_paste_ready_timeout', '_tmux_set_buffer_text', '_tmux_submit_settle_timeout', '_tmux_window_exists', '_validate_leader_receiver', '_wait_for_message_ready', '_wait_for_worker_message_ready', 'ambiguous_team_target_result', 'check_team_owner', 'copy', 'core_list_targets', 'core_render_message', 'datetime', 'json', 'load_runtime_state', 'load_spec', 'missing_tools', 'os', 're', 'route_task', 'run_cmd', 'runtime_dir', 'save_runtime_state', 'save_team_scoped_state', 'select_runtime_state', 'send_message', 'start_coordinator', 'subprocess', 'team_state_key', 'time', 'timedelta', 'timezone', 'update_task_status', 'validate_result_envelope', 'write_team_state']
@@ -1,423 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime, timedelta, timezone
4
- from pathlib import Path
5
- from typing import Any
6
-
7
- from team_agent.events import EventLog
8
- from team_agent.message_store import MessageStore
9
- from team_agent.messaging.deps import load_runtime_state, load_spec, save_runtime_state, team_state_key
10
- from team_agent.messaging.internal_delivery import deliver_stored_message
11
-
12
-
13
- _UNDELIVERED_MESSAGE_STATUSES = {
14
- "pending",
15
- "accepted",
16
- "queued_until_idle",
17
- "queued_until_start",
18
- "queued_stopped",
19
- "queued_pane_missing",
20
- "failed",
21
- "delivery_blocked",
22
- "injected_unverified",
23
- }
24
-
25
-
26
- STABLE_IDLE_SECONDS = 120
27
- FIRE_DEBOUNCE_SECONDS = 300
28
- OBLIGATION_PENDING_MIN_AGE_SECONDS = 60
29
-
30
- # Event-log progress signal (Gap 32 §"Idle-Detector False Positive Continues Post Phase G hotfix-3"):
31
- # the team_last_progress_at calculation must also count leader-side sends and worker MCP calls
32
- # as recent team activity, not only agent_health.last_output_at. Without this, a worker that has
33
- # called MCP but not yet emitted a visible turn shows up as idle and the idle reminder fires
34
- # spuriously inside the stable-idle window.
35
- _PROGRESS_EVENT_TYPES = frozenset({
36
- "send.deliver_attempt",
37
- "leader_receiver.deliver_attempt",
38
- "mcp.report_result",
39
- "mcp.send_message",
40
- })
41
- _PROGRESS_EVENT_PREFIXES = ("mcp.read_",)
42
- _PROGRESS_EVENT_WINDOW_SECONDS = 300
43
- _PROGRESS_EVENT_TAIL_LIMIT = 1000
44
-
45
-
46
- def _parse_iso(text: Any) -> datetime | None:
47
- if not isinstance(text, str) or not text:
48
- return None
49
- try:
50
- dt = datetime.fromisoformat(text.replace("Z", "+00:00"))
51
- except ValueError:
52
- return None
53
- if dt.tzinfo is None:
54
- dt = dt.replace(tzinfo=timezone.utc)
55
- return dt
56
-
57
-
58
- def record_team_progress(
59
- state: dict[str, Any],
60
- now: datetime | None = None,
61
- *,
62
- source: str = "",
63
- owner_team_id: str | None = None,
64
- ) -> None:
65
- coordinator = state.setdefault("coordinator", {})
66
- progress = coordinator.setdefault("team_last_progress_at", {})
67
- key = owner_team_id or team_state_key(state)
68
- if not key:
69
- return
70
- progress[key] = {
71
- "at": (now or datetime.now(timezone.utc)).isoformat(),
72
- "source": source,
73
- }
74
-
75
-
76
- def _team_last_progress_at(
77
- state: dict[str, Any],
78
- store: MessageStore,
79
- owner_team_id: str,
80
- event_log: EventLog | None = None,
81
- now: datetime | None = None,
82
- workspace: Path | None = None,
83
- ) -> tuple[datetime | None, str | None]:
84
- sources: list[tuple[datetime, str]] = []
85
- coordinator = state.get("coordinator") or {}
86
- explicit = (coordinator.get("team_last_progress_at") or {}).get(owner_team_id)
87
- if isinstance(explicit, dict):
88
- ts = _parse_iso(explicit.get("at"))
89
- if ts:
90
- sources.append((ts, "explicit_marker"))
91
- elif isinstance(explicit, str):
92
- ts = _parse_iso(explicit)
93
- if ts:
94
- sources.append((ts, "explicit_marker"))
95
- health = store.agent_health(owner_team_id=owner_team_id)
96
- for row in health.values():
97
- ts = _parse_iso(row.get("last_output_at"))
98
- if ts:
99
- sources.append((ts, "agent_health.last_output_at"))
100
- if event_log is not None:
101
- # Spark MEDIUM #3 (d9f740d): in multi-team workspaces an unscoped progress event in
102
- # team A's activity must NOT suppress team B's idle_fallback. require_team_scope=True
103
- # when the workspace has more than one team so unscoped events are ignored. The
104
- # team-scoped state passed in here does not carry the workspace-level `teams` dict, so
105
- # we re-read the workspace state from disk to detect multi-team shape.
106
- require_team_scope = False
107
- teams = state.get("teams")
108
- if isinstance(teams, dict) and len(teams) > 1:
109
- require_team_scope = True
110
- elif workspace is not None:
111
- try:
112
- ws_teams = (load_runtime_state(workspace).get("teams") or {})
113
- except Exception:
114
- ws_teams = {}
115
- if isinstance(ws_teams, dict) and len(ws_teams) > 1:
116
- require_team_scope = True
117
- event_ts = _scan_event_progress_signals(
118
- event_log, owner_team_id, now or datetime.now(timezone.utc),
119
- require_team_scope=require_team_scope,
120
- )
121
- if event_ts:
122
- sources.append((event_ts, "event_log"))
123
- if not sources:
124
- return None, None
125
- sources.sort(key=lambda item: item[0], reverse=True)
126
- return sources[0]
127
-
128
-
129
- # Stage 14 (Gap 36b) — mtime cache per (workspace_path, owner_team_id, require_team_scope).
130
- # Mac mini 2026-05-26 evidence: _scan_event_progress_signals was a 22% CPU hot path because
131
- # every 2-second coordinator tick parsed up to 1000 events from a 28 MB events.jsonl. With
132
- # the cache, the parse only re-runs when the file changes; quiet workspaces pay zero file
133
- # I/O between writes.
134
- _PROGRESS_SCAN_CACHE: dict[tuple[str, str, bool], tuple[float, datetime | None]] = {}
135
-
136
-
137
- def _scan_event_progress_signals(
138
- event_log: EventLog,
139
- owner_team_id: str,
140
- now: datetime,
141
- *,
142
- require_team_scope: bool = False,
143
- ) -> datetime | None:
144
- cache_key = (str(event_log.path), owner_team_id, require_team_scope)
145
- try:
146
- current_mtime = event_log.path.stat().st_mtime
147
- except FileNotFoundError:
148
- _PROGRESS_SCAN_CACHE.pop(cache_key, None)
149
- return None
150
- cached = _PROGRESS_SCAN_CACHE.get(cache_key)
151
- if cached is not None and cached[0] == current_mtime:
152
- return cached[1]
153
- window_start = now - timedelta(seconds=_PROGRESS_EVENT_WINDOW_SECONDS)
154
- latest: datetime | None = None
155
- for event in event_log.tail(_PROGRESS_EVENT_TAIL_LIMIT):
156
- event_type = str(event.get("event") or "")
157
- if event_type not in _PROGRESS_EVENT_TYPES and not any(
158
- event_type.startswith(prefix) for prefix in _PROGRESS_EVENT_PREFIXES
159
- ):
160
- continue
161
- event_team = event.get("team") or event.get("owner_team_id")
162
- if event_team is None:
163
- if require_team_scope:
164
- continue
165
- elif event_team != owner_team_id:
166
- continue
167
- ts = _parse_iso(event.get("ts"))
168
- if not ts or ts < window_start:
169
- continue
170
- if latest is None or ts > latest:
171
- latest = ts
172
- _PROGRESS_SCAN_CACHE[cache_key] = (current_mtime, latest)
173
- return latest
174
-
175
-
176
- def _reset_progress_scan_cache() -> None:
177
- """Test-only hook to force re-scan."""
178
- _PROGRESS_SCAN_CACHE.clear()
179
-
180
-
181
- def _team_last_idle_fallback_fire_at(state: dict[str, Any], owner_team_id: str) -> datetime | None:
182
- coordinator = state.get("coordinator") or {}
183
- fires = coordinator.get("team_last_idle_fallback_fire_at") or {}
184
- return _parse_iso(fires.get(owner_team_id))
185
-
186
-
187
- def _record_idle_fallback_fire(state: dict[str, Any], owner_team_id: str, now: datetime) -> None:
188
- coordinator = state.setdefault("coordinator", {})
189
- fires = coordinator.setdefault("team_last_idle_fallback_fire_at", {})
190
- fires[owner_team_id] = now.isoformat()
191
-
192
-
193
- def _team_undelivered_obligations(
194
- state: dict[str, Any],
195
- store: MessageStore,
196
- owner_team_id: str,
197
- active_task_statuses: set[str],
198
- *,
199
- now: datetime | None = None,
200
- ) -> list[dict[str, Any]]:
201
- now = now or datetime.now(timezone.utc)
202
- min_age = timedelta(seconds=OBLIGATION_PENDING_MIN_AGE_SECONDS)
203
- obligations: list[dict[str, Any]] = []
204
- for message in store.messages(owner_team_id=owner_team_id):
205
- if message.get("status") not in _UNDELIVERED_MESSAGE_STATUSES:
206
- continue
207
- created_at = _parse_iso(message.get("created_at"))
208
- if created_at and (now - created_at) < min_age:
209
- continue
210
- obligations.append(
211
- {
212
- "kind": "undelivered_message",
213
- "message_id": message.get("message_id"),
214
- "recipient": message.get("recipient"),
215
- "status": message.get("status"),
216
- }
217
- )
218
- for watcher in store.retryable_result_watchers():
219
- if watcher.get("status") not in {"pending", "notify_failed"}:
220
- continue
221
- created_at = _parse_iso(watcher.get("created_at"))
222
- if created_at and (now - created_at) < min_age:
223
- continue
224
- obligations.append(
225
- {
226
- "kind": "pending_result_watcher",
227
- "watcher_id": watcher.get("watcher_id"),
228
- "task_id": watcher.get("task_id"),
229
- "agent_id": watcher.get("agent_id"),
230
- }
231
- )
232
- for task in state.get("tasks", []):
233
- if task.get("status", "pending") in active_task_statuses and task.get("assignee"):
234
- obligations.append(
235
- {
236
- "kind": "active_task",
237
- "task_id": task.get("id"),
238
- "assignee": task.get("assignee"),
239
- "status": task.get("status"),
240
- }
241
- )
242
- return obligations
243
-
244
-
245
- def _all_workers_idle(
246
- state: dict[str, Any],
247
- store: MessageStore,
248
- owner_team_id: str,
249
- ) -> tuple[bool, list[str]]:
250
- health = store.agent_health(owner_team_id=owner_team_id)
251
- worker_ids = list(state.get("agents", {}).keys()) or list(health.keys())
252
- if not worker_ids:
253
- return False, []
254
- idle: list[str] = []
255
- for agent_id in worker_ids:
256
- row = health.get(agent_id) or {}
257
- status = str(row.get("status") or "").lower()
258
- if status != "idle":
259
- return False, []
260
- idle.append(agent_id)
261
- return True, idle
262
-
263
-
264
- def _register_unified_alert(
265
- state: dict[str, Any],
266
- owner_team_id: str,
267
- agent_id: str,
268
- alert_type: str,
269
- snapshot: dict[str, Any],
270
- suppressed_by: str,
271
- now: datetime,
272
- ) -> dict[str, Any]:
273
- coordinator = state.setdefault("coordinator", {})
274
- suppressed = coordinator.setdefault("suppressed_idle_alerts", {})
275
- team_suppressions = suppressed.setdefault(owner_team_id, {})
276
- agent_suppressions = team_suppressions.setdefault(agent_id, {})
277
- entry = {
278
- "suppressed_at": now.isoformat(),
279
- "suppressed_by": suppressed_by,
280
- "snapshot": snapshot,
281
- }
282
- agent_suppressions[alert_type] = entry
283
- return entry
284
-
285
-
286
- def detect_idle_fallbacks(
287
- workspace: Path,
288
- state: dict[str, Any],
289
- store: MessageStore,
290
- event_log: EventLog,
291
- now: datetime | None = None,
292
- ) -> list[dict[str, Any]]:
293
- from team_agent.messaging.scheduler import (
294
- _ACTIVE_TASK_STATUSES,
295
- _active_alert_suppression,
296
- _agent_alert_snapshot,
297
- )
298
- now = now or datetime.now(timezone.utc)
299
- owner_team_id = team_state_key(state)
300
- obligations = _team_undelivered_obligations(state, store, owner_team_id, _ACTIVE_TASK_STATUSES, now=now)
301
- if not obligations:
302
- return []
303
- all_idle, idle_workers = _all_workers_idle(state, store, owner_team_id)
304
- if not all_idle:
305
- record_team_progress(state, now, source="all_workers_idle:false", owner_team_id=owner_team_id)
306
- save_runtime_state(workspace, state)
307
- return []
308
- last_progress, progress_source = _team_last_progress_at(
309
- state, store, owner_team_id, event_log=event_log, now=now, workspace=workspace,
310
- )
311
- if last_progress and (now - last_progress) < timedelta(seconds=STABLE_IDLE_SECONDS):
312
- reason = "recent_team_progress" if progress_source == "event_log" else "stable_idle_window"
313
- event_log.write(
314
- "coordinator.idle_fallback_skipped",
315
- reason=reason,
316
- team=owner_team_id,
317
- stable_idle_seconds=STABLE_IDLE_SECONDS,
318
- elapsed_seconds=int((now - last_progress).total_seconds()),
319
- progress_source=progress_source,
320
- )
321
- return []
322
- last_fire = _team_last_idle_fallback_fire_at(state, owner_team_id)
323
- if last_fire and (now - last_fire) < timedelta(seconds=FIRE_DEBOUNCE_SECONDS):
324
- event_log.write(
325
- "coordinator.idle_fallback_skipped",
326
- reason="fire_debounce",
327
- team=owner_team_id,
328
- fire_debounce_seconds=FIRE_DEBOUNCE_SECONDS,
329
- elapsed_seconds=int((now - last_fire).total_seconds()),
330
- )
331
- return []
332
- spec_path = Path(state.get("spec_path", workspace / "team.spec.yaml"))
333
- spec = load_spec(spec_path) if spec_path.exists() else {}
334
- leader_id = state.get("leader", {}).get("id") or spec.get("leader", {}).get("id") or "leader"
335
- alerts: list[dict[str, Any]] = []
336
- for agent_id in idle_workers:
337
- suppression = _active_alert_suppression(state, store, event_log, agent_id, "idle_fallback")
338
- if suppression:
339
- continue
340
- snapshot = _agent_alert_snapshot(state, store, agent_id, owner_team_id)
341
- _register_unified_alert(state, owner_team_id, agent_id, "idle_fallback", snapshot, "coordinator", now)
342
- alerts.append({"agent_id": agent_id, "alert_type": "idle_fallback", "obligations": obligations})
343
- if not alerts:
344
- return []
345
- _record_idle_fallback_fire(state, owner_team_id, now)
346
- save_runtime_state(workspace, state)
347
- content = (
348
- "There is still unfinished work. Continue coordinating, deliver a result, "
349
- "or acknowledge that this idle state is intentional via team-agent acknowledge-idle."
350
- )
351
- try:
352
- deliver_stored_message(
353
- workspace,
354
- leader_id,
355
- content,
356
- sender="coordinator",
357
- requires_ack=False,
358
- wait_visible=False,
359
- team=owner_team_id,
360
- )
361
- except Exception as exc:
362
- event_log.write("coordinator.idle_fallback_push_failed", error=str(exc), team=owner_team_id)
363
- event_log.write(
364
- "coordinator.idle_fallback",
365
- team=owner_team_id,
366
- idle_workers=idle_workers,
367
- obligation_count=len(obligations),
368
- alert_count=len(alerts),
369
- )
370
- return alerts
371
-
372
-
373
- def detect_cross_worker_deadlocks(
374
- workspace: Path,
375
- state: dict[str, Any],
376
- store: MessageStore,
377
- event_log: EventLog,
378
- now: datetime | None = None,
379
- ) -> list[dict[str, Any]]:
380
- from team_agent.messaging.scheduler import (
381
- _active_alert_suppression,
382
- _agent_alert_snapshot,
383
- )
384
- now = now or datetime.now(timezone.utc)
385
- owner_team_id = team_state_key(state)
386
- health = store.agent_health(owner_team_id=owner_team_id)
387
- candidate_recipients: dict[str, list[dict[str, Any]]] = {}
388
- for message in store.messages(owner_team_id=owner_team_id):
389
- if message.get("status") not in _UNDELIVERED_MESSAGE_STATUSES:
390
- continue
391
- recipient = message.get("recipient")
392
- if not recipient:
393
- continue
394
- candidate_recipients.setdefault(str(recipient), []).append(message)
395
- alerts: list[dict[str, Any]] = []
396
- for agent_id, messages in candidate_recipients.items():
397
- row = health.get(agent_id) or {}
398
- status = str(row.get("status") or "").lower()
399
- if status != "idle":
400
- continue
401
- suppression = _active_alert_suppression(state, store, event_log, agent_id, "cross_worker_deadlock")
402
- if suppression:
403
- continue
404
- snapshot = _agent_alert_snapshot(state, store, agent_id, owner_team_id)
405
- snapshot["pending_message_ids"] = sorted(str(m.get("message_id")) for m in messages)
406
- _register_unified_alert(state, owner_team_id, agent_id, "cross_worker_deadlock", snapshot, "coordinator", now)
407
- alerts.append(
408
- {
409
- "agent_id": agent_id,
410
- "alert_type": "cross_worker_deadlock",
411
- "pending_messages": snapshot["pending_message_ids"],
412
- }
413
- )
414
- if not alerts:
415
- return []
416
- save_runtime_state(workspace, state)
417
- event_log.write(
418
- "coordinator.cross_worker_deadlock",
419
- team=owner_team_id,
420
- agent_ids=[alert["agent_id"] for alert in alerts],
421
- alert_count=len(alerts),
422
- )
423
- return alerts