@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
@@ -1,29 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from typing import Any
4
-
5
- from team_agent.events import EventLog
6
- from team_agent.state import worker_sender_bypasses_owner_gate
7
-
8
-
9
- def apply_worker_sender_bypass(
10
- state: dict[str, Any],
11
- sender: str | None,
12
- target: Any,
13
- task_id: str | None,
14
- event_log: EventLog,
15
- ) -> bool:
16
- via = worker_sender_bypasses_owner_gate(state, sender)
17
- if not via:
18
- return False
19
- event_log.write(
20
- "send.bypassed_owner_gate_worker_sender",
21
- sender=sender,
22
- env_team_agent_id=via,
23
- target=target if isinstance(target, str) else None,
24
- task_id=task_id,
25
- )
26
- return True
27
-
28
-
29
- __all__ = ["apply_worker_sender_bypass"]
@@ -1,539 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from datetime import datetime, timezone
5
- from pathlib import Path
6
- from typing import Any
7
-
8
- from team_agent.events import EventLog
9
- from team_agent.message_store import MessageStore
10
- from team_agent.message_store.leader_notification_log import peek_leader_notification
11
- from team_agent.message_store.result_watchers import leader_notified_message_id_for_result
12
- from team_agent.messaging.deps import send_message
13
- from team_agent.messaging.internal_delivery import deliver_stored_message
14
-
15
- _RESULT_DELIVERY_MAX_ATTEMPTS = 5
16
- _DELIVERED_RESULT_MESSAGE_STATUSES = {"visible", "submitted", "submitted_unverified", "delivered", "acknowledged"}
17
-
18
-
19
- def retry_result_deliveries(workspace: Path, event_log: EventLog) -> list[dict[str, Any]]:
20
- store = MessageStore(workspace)
21
- notified: list[dict[str, Any]] = []
22
- for watcher in store.retryable_result_watchers():
23
- if watcher.get("status") != "notify_failed" or not watcher.get("result_id"):
24
- continue
25
- row = store.result_by_id(str(watcher["result_id"]))
26
- if not row:
27
- continue
28
- notified.extend(notify_result_watchers(
29
- workspace,
30
- _result_entry_from_row(row),
31
- event_log,
32
- watchers=[watcher],
33
- dedupe_reason="rebind_retry",
34
- ))
35
- return notified
36
-
37
-
38
- def notify_result_watchers(
39
- workspace: Path,
40
- result: dict[str, Any],
41
- event_log: EventLog,
42
- watchers: list[dict[str, Any]] | None = None,
43
- dedupe_reason: str | None = None,
44
- ) -> list[dict[str, Any]]:
45
- store = MessageStore(workspace)
46
- candidates = [
47
- watcher
48
- for watcher in (watchers if watchers is not None else store.pending_result_watchers())
49
- if watcher_matches_result(watcher, result)
50
- ]
51
- if not candidates:
52
- return []
53
- primary, superseded = _dedupe_watchers_for_result(candidates)
54
- notified: list[dict[str, Any]] = []
55
- for stale in superseded:
56
- store.mark_result_watcher(
57
- stale["watcher_id"],
58
- "superseded",
59
- result_id=result.get("result_id"),
60
- error="superseded by earlier watcher for same (task_id, agent_id, result_id)",
61
- )
62
- event_log.write(
63
- "result_watcher.superseded",
64
- watcher_id=stale["watcher_id"],
65
- result_id=result.get("result_id"),
66
- task_id=result.get("task_id"),
67
- agent_id=result.get("agent_id"),
68
- primary_watcher_id=primary["watcher_id"],
69
- )
70
- notified.append(
71
- {
72
- "watcher_id": stale["watcher_id"],
73
- "result_id": result.get("result_id"),
74
- "ok": False,
75
- "status": "superseded",
76
- "primary_watcher_id": primary["watcher_id"],
77
- }
78
- )
79
- attempts = result_delivery_attempts(event_log, primary["watcher_id"], str(result.get("result_id") or ""))
80
- # Stage 12 (Gap 26 ∩ Gap 32 roundtable consolidation 2026-05-26): exactly-once dedupe
81
- # lives in leader_notification_log keyed by (result_id, leader_session_uuid) and is
82
- # consulted atomically at the injection boundary inside _send_to_leader_receiver. Here
83
- # we add a read-only fast-path peek so concurrent notify_result_watchers calls for the
84
- # same result short-circuit without spinning up a deliver_stored_message round-trip.
85
- # The peek is NOT the dedupe primitive — the atomic INSERT OR IGNORE at injection is.
86
- result_id_str = str(result.get("result_id") or "") or None
87
- if result_id_str:
88
- leader_identity = _resolve_leader_notification_identity(workspace, primary.get("owner_team_id"))
89
- if leader_identity:
90
- prior = peek_leader_notification(
91
- store,
92
- result_id=result_id_str,
93
- leader_session_uuid=leader_identity.get("leader_session_uuid"),
94
- owner_team_id=primary.get("owner_team_id"),
95
- owner_epoch=leader_identity.get("owner_epoch"),
96
- )
97
- if prior:
98
- notified.append(_mark_watcher_dedupe_skip(
99
- store, event_log, primary, result, attempts,
100
- prior["notified_message_id"],
101
- dedupe_reason or "injection_log_already_notified",
102
- notified_at=prior.get("notified_at"),
103
- leader_session_uuid=leader_identity.get("leader_session_uuid"),
104
- ))
105
- return notified
106
- # Legacy compat: watcher.notified_message_id set by a prior path (Gap 32 reversal of
107
- # 78055bc, or any pre-Stage-12 code) also blocks redelivery. This preserves the
108
- # Stage 11.9-11.12 era contract while the new gate (leader_notification_log) is the
109
- # authoritative dedupe primitive going forward.
110
- legacy_canonical = leader_notified_message_id_for_result(
111
- store, primary.get("owner_team_id"), result_id_str,
112
- )
113
- if legacy_canonical:
114
- notified.append(_mark_watcher_dedupe_skip(
115
- store, event_log, primary, result, attempts,
116
- legacy_canonical,
117
- dedupe_reason or "rebind_retry",
118
- ))
119
- return notified
120
- existing = delivered_result_message(
121
- store, str(result.get("result_id") or ""),
122
- task_id=result.get("task_id"),
123
- owner_team_id=primary.get("owner_team_id"),
124
- )
125
- if existing:
126
- notified.append(_mark_watcher_already_delivered(store, event_log, primary, result, attempts, existing))
127
- return notified
128
- if attempts >= _RESULT_DELIVERY_MAX_ATTEMPTS:
129
- notified.append(_mark_delivery_exhausted(store, event_log, primary, result, attempts))
130
- else:
131
- notified.append(_deliver_result_to_watcher(workspace, store, event_log, primary, result, attempts))
132
- return notified
133
-
134
-
135
- def _resolve_leader_session_uuid(workspace: Path, owner_team_id: str | None) -> str | None:
136
- """Helper: read the team's leader_session_uuid from runtime state for gate lookups."""
137
- try:
138
- from team_agent.messaging.deps import load_runtime_state, team_state_key
139
- state = load_runtime_state(workspace)
140
- if owner_team_id and isinstance(state.get("teams"), dict):
141
- scoped = state["teams"].get(owner_team_id)
142
- if isinstance(scoped, dict):
143
- state = scoped
144
- elif owner_team_id and team_state_key(state) != owner_team_id:
145
- return None
146
- owner = state.get("team_owner") or {}
147
- return str(owner.get("leader_session_uuid") or "") or None
148
- except Exception:
149
- return None
150
-
151
-
152
- def _resolve_leader_notification_identity(workspace: Path, owner_team_id: str | None) -> dict[str, Any] | None:
153
- try:
154
- from team_agent.messaging.deps import load_runtime_state, team_state_key
155
- state = load_runtime_state(workspace)
156
- if owner_team_id and isinstance(state.get("teams"), dict):
157
- scoped = state["teams"].get(owner_team_id)
158
- if isinstance(scoped, dict):
159
- state = scoped
160
- elif owner_team_id and team_state_key(state) != owner_team_id:
161
- return None
162
- owner = state.get("team_owner") or {}
163
- receiver = state.get("leader_receiver") or {}
164
- return {
165
- "leader_session_uuid": str(owner.get("leader_session_uuid") or receiver.get("leader_session_uuid") or "") or None,
166
- "owner_epoch": int(owner.get("owner_epoch") or receiver.get("owner_epoch") or 0),
167
- }
168
- except Exception:
169
- return None
170
-
171
-
172
- def _infer_dedupe_reason(primary: dict[str, Any], store: MessageStore) -> str:
173
- if primary.get("notified_message_id"):
174
- return "rebind_retry"
175
- return "watcher_duplicate"
176
-
177
-
178
- def _mark_watcher_dedupe_skip(
179
- store: MessageStore,
180
- event_log: EventLog,
181
- watcher: dict[str, Any],
182
- result: dict[str, Any],
183
- attempts: int,
184
- canonical_message_id: str,
185
- reason: str,
186
- *,
187
- notified_at: str | None = None,
188
- leader_session_uuid: str | None = None,
189
- ) -> dict[str, Any]:
190
- original_message_id = watcher.get("notified_message_id")
191
- # Stage 12: the canonical message_id (or sentinel from the gate) is auditing metadata
192
- # here. The authoritative dedupe gate is leader_notification_log; this mark just keeps
193
- # the watcher row from being re-picked by retry scans.
194
- store.mark_result_watcher(
195
- watcher["watcher_id"],
196
- "notified",
197
- result_id=result.get("result_id"),
198
- notified_message_id=canonical_message_id,
199
- )
200
- event_log.write(
201
- "leader_receiver.notification_dedupe_skip",
202
- result_id=result.get("result_id"),
203
- original_message_id=original_message_id,
204
- suppressed_message_id=canonical_message_id,
205
- reason=reason,
206
- team_id=watcher.get("owner_team_id"),
207
- watcher_id=watcher["watcher_id"],
208
- task_id=result.get("task_id"),
209
- agent_id=result.get("agent_id"),
210
- attempt=attempts + 1,
211
- leader_session_uuid=leader_session_uuid,
212
- prior_notified_at=notified_at,
213
- )
214
- return {
215
- "watcher_id": watcher["watcher_id"],
216
- "result_id": result.get("result_id"),
217
- "ok": True,
218
- "message_id": canonical_message_id,
219
- "deduped": True,
220
- "dedupe_reason": reason,
221
- }
222
-
223
-
224
- def _dedupe_watchers_for_result(
225
- watchers: list[dict[str, Any]],
226
- ) -> tuple[dict[str, Any], list[dict[str, Any]]]:
227
- ordered = sorted(watchers, key=lambda w: (str(w.get("created_at") or ""), str(w.get("watcher_id") or "")))
228
- return ordered[0], ordered[1:]
229
-
230
-
231
- def _deliver_result_to_watcher(
232
- workspace: Path,
233
- store: MessageStore,
234
- event_log: EventLog,
235
- watcher: dict[str, Any],
236
- result: dict[str, Any],
237
- attempts: int,
238
- ) -> dict[str, Any]:
239
- try:
240
- deliver = deliver_stored_message if watcher.get("owner_team_id") else send_message
241
- delivery = deliver(
242
- workspace,
243
- watcher.get("leader_id") or "leader",
244
- format_result_watcher_notification(result),
245
- task_id=result.get("task_id"),
246
- sender="coordinator",
247
- requires_ack=False,
248
- wait_visible=False,
249
- team=watcher.get("owner_team_id"),
250
- )
251
- except Exception as exc:
252
- return _mark_delivery_failed(store, event_log, watcher, result, attempts, str(exc))
253
- status = "notified" if delivery.get("ok") else "notify_failed"
254
- error = delivery.get("reason") or delivery.get("error")
255
- # Stage 12: notified_message_id is now auditing metadata. The exactly-once contract
256
- # lives in the leader_notification_log table consulted by _send_to_leader_receiver;
257
- # whatever the gate suppresses comes back as ok=true deduped=true, and the watcher row
258
- # records this as a successful notification with the canonical message_id.
259
- persisted_message_id = (
260
- delivery.get("canonical_message_id") if delivery.get("deduped")
261
- else (delivery.get("message_id") if delivery.get("ok") else None)
262
- )
263
- store.mark_result_watcher(
264
- watcher["watcher_id"],
265
- status,
266
- result_id=result.get("result_id"),
267
- notified_message_id=persisted_message_id,
268
- error=error,
269
- )
270
- event_log.write(
271
- "result_watcher.notified",
272
- watcher_id=watcher["watcher_id"],
273
- result_id=result.get("result_id"),
274
- task_id=result.get("task_id"),
275
- agent_id=result.get("agent_id"),
276
- ok=bool(delivery.get("ok")),
277
- delivery_status=delivery.get("status"),
278
- message_id=delivery.get("message_id"),
279
- error=error,
280
- attempt=attempts + 1,
281
- )
282
- return {
283
- "watcher_id": watcher["watcher_id"],
284
- "result_id": result.get("result_id"),
285
- "ok": bool(delivery.get("ok")),
286
- "message_id": delivery.get("message_id"),
287
- }
288
-
289
-
290
- def _mark_delivery_failed(
291
- store: MessageStore,
292
- event_log: EventLog,
293
- watcher: dict[str, Any],
294
- result: dict[str, Any],
295
- attempts: int,
296
- error: str,
297
- ) -> dict[str, Any]:
298
- store.mark_result_watcher(watcher["watcher_id"], "notify_failed", result_id=result.get("result_id"), error=error)
299
- event_log.write(
300
- "result_watcher.notify_failed",
301
- watcher_id=watcher["watcher_id"],
302
- result_id=result.get("result_id"),
303
- attempt=attempts + 1,
304
- error=error,
305
- )
306
- return {"watcher_id": watcher["watcher_id"], "result_id": result.get("result_id"), "ok": False, "error": error}
307
-
308
-
309
- def _mark_watcher_already_delivered(
310
- store: MessageStore,
311
- event_log: EventLog,
312
- watcher: dict[str, Any],
313
- result: dict[str, Any],
314
- attempts: int,
315
- message: dict[str, Any],
316
- ) -> dict[str, Any]:
317
- store.mark_result_watcher(
318
- watcher["watcher_id"],
319
- "notified",
320
- result_id=result.get("result_id"),
321
- notified_message_id=message.get("message_id"),
322
- )
323
- event_log.write(
324
- "result_watcher.notified",
325
- watcher_id=watcher["watcher_id"],
326
- result_id=result.get("result_id"),
327
- task_id=result.get("task_id"),
328
- agent_id=result.get("agent_id"),
329
- ok=True,
330
- delivery_status="already_delivered",
331
- message_id=message.get("message_id"),
332
- deduped=True,
333
- attempt=attempts,
334
- )
335
- return {
336
- "watcher_id": watcher["watcher_id"],
337
- "result_id": result.get("result_id"),
338
- "ok": True,
339
- "message_id": message.get("message_id"),
340
- "deduped": True,
341
- }
342
-
343
-
344
- def _mark_delivery_exhausted(
345
- store: MessageStore,
346
- event_log: EventLog,
347
- watcher: dict[str, Any],
348
- result: dict[str, Any],
349
- attempts: int,
350
- ) -> dict[str, Any]:
351
- error = "result delivery retry budget exhausted"
352
- store.mark_result_watcher(watcher["watcher_id"], "delivery_exhausted", result_id=result.get("result_id"), error=error)
353
- event_log.write(
354
- "result_delivery_exhausted",
355
- watcher_id=watcher["watcher_id"],
356
- result_id=result.get("result_id"),
357
- task_id=result.get("task_id"),
358
- agent_id=result.get("agent_id"),
359
- attempts=attempts,
360
- last_error=watcher.get("error"),
361
- )
362
- return {"watcher_id": watcher["watcher_id"], "result_id": result.get("result_id"), "ok": False, "error": error}
363
-
364
-
365
- def _result_entry_from_row(row: dict[str, Any]) -> dict[str, Any]:
366
- envelope = json.loads(row["envelope"])
367
- return {
368
- "result_id": row["result_id"],
369
- "task_id": envelope.get("task_id"),
370
- "agent_id": envelope.get("agent_id"),
371
- "status": envelope.get("status"),
372
- "summary": envelope.get("summary"),
373
- "tests": envelope.get("tests", []),
374
- "created_at": row.get("created_at"),
375
- "scope": "task",
376
- }
377
-
378
-
379
- def result_delivery_attempts(event_log: EventLog, watcher_id: str, result_id: str) -> int:
380
- attempts = 0
381
- for event in event_log.tail(500):
382
- if event.get("watcher_id") != watcher_id:
383
- continue
384
- if event.get("event") == "result_watcher.requeued":
385
- attempts = 0
386
- continue
387
- if event.get("result_id") != result_id:
388
- continue
389
- if event.get("event") in {"result_watcher.notified", "result_watcher.notify_failed"}:
390
- attempts += 1
391
- return attempts
392
-
393
-
394
- def delivered_result_message(
395
- store: MessageStore,
396
- result_id: str,
397
- *,
398
- task_id: str | None = None,
399
- owner_team_id: str | None = None,
400
- ) -> dict[str, Any] | None:
401
- if not result_id:
402
- return None
403
- for message in reversed(store.messages(owner_team_id=owner_team_id)):
404
- if message.get("recipient") != "leader":
405
- continue
406
- if task_id and message.get("task_id") != task_id:
407
- continue
408
- if message.get("status") not in _DELIVERED_RESULT_MESSAGE_STATUSES:
409
- continue
410
- if f"Result id: {result_id}" in str(message.get("content") or ""):
411
- return message
412
- return None
413
-
414
-
415
- def result_id_from_text(content: str) -> str | None:
416
- for line in content.splitlines():
417
- if line.startswith("Result id: "):
418
- return line.removeprefix("Result id: ").strip() or None
419
- return None
420
-
421
-
422
- def watcher_matches_result(watcher: dict[str, Any], result: dict[str, Any]) -> bool:
423
- task_id = watcher.get("task_id")
424
- agent_id = watcher.get("agent_id")
425
- return (not task_id or task_id == result.get("task_id")) and (not agent_id or agent_id == result.get("agent_id"))
426
-
427
-
428
- def requeue_after_claim_leader(
429
- workspace: Path,
430
- store: MessageStore,
431
- event_log: EventLog,
432
- owner_team_id: str,
433
- claimed_pane_id: str,
434
- *,
435
- incident_ts: str | None = None,
436
- ) -> list[dict[str, Any]]:
437
- """Post-claim hook (Gap 26 / Mac mini Stage 11 Scenarios 3, 11.10): re-route every
438
- not-yet-delivered leader-bound notification to the newly claimed pane. Returns the
439
- list of requeued watcher records (may be empty).
440
-
441
- Stage 11.10 semantic reframe: claim-leader means "all not-yet-delivered leader-bound
442
- notifications for this team_id reroute to the claimed pane". Watcher status is
443
- irrelevant — `notified_message_id` is the only dedupe gate. Gap 32 exactly-once
444
- contract still holds: notified_message_id non-null blocks redelivery.
445
-
446
- Selection rules:
447
- - watcher is scoped to this team (owner_team_id match)
448
- - watcher has no notified_message_id (Gap 32 once-only)
449
- - watcher's latest activity timestamp (completed_at fallback created_at) is
450
- at-or-after incident_ts when provided; without an incident_ts every
451
- un-notified watcher is requeued.
452
- - watcher status is otherwise ignored (pending / delivery_blocked /
453
- delivery_exhausted / notify_failed all become candidates).
454
-
455
- Atomicity vs coordinator's own scheduled retry: just before flipping a watcher's
456
- status, re-fetch the row from the store. If notified_message_id became non-null
457
- in the gap (the scheduled retry beat us), emit a benign
458
- leader_receiver.claim_requeue_already_in_flight event and skip. If the race
459
- leaks past this check, Gap 32 dedupe inside notify_result_watchers still
460
- guarantees exactly-once injection.
461
- """
462
- # Stage 11.12: CAS re-fetch + claim_requeue_already_in_flight event retired. The atomic
463
- # UPSERT in notify_result_watchers (claim_leader_notification) is now the single race
464
- # gate. We mark eligible watchers to notify_failed and let retry_result_deliveries route
465
- # through the UPSERT — concurrent claim/scheduled-retry paths both pass through the
466
- # same atomic claim and only one fires deliver_attempt.
467
- incident_dt = _parse_iso(incident_ts)
468
- requeued: list[dict[str, Any]] = []
469
- for watcher in store.result_watchers(owner_team_id=owner_team_id):
470
- if watcher.get("notified_message_id"):
471
- continue
472
- latest_ts = _parse_iso(watcher.get("completed_at")) or _parse_iso(watcher.get("created_at"))
473
- if incident_dt and latest_ts and latest_ts < incident_dt:
474
- continue
475
- watcher_id = watcher["watcher_id"]
476
- prior_state = str(watcher.get("status") or "")
477
- store.mark_result_watcher(
478
- watcher_id, "notify_failed",
479
- result_id=watcher.get("result_id"),
480
- )
481
- event_log.write(
482
- "leader_receiver.claim_requeue",
483
- result_id=watcher.get("result_id"),
484
- watcher_id=watcher_id,
485
- prior_state=prior_state,
486
- requeued_at=datetime.now(timezone.utc).isoformat(),
487
- claimed_pane_id=claimed_pane_id,
488
- team_id=owner_team_id,
489
- )
490
- requeued.append({
491
- "watcher_id": watcher_id,
492
- "result_id": watcher.get("result_id"),
493
- "prior_state": prior_state,
494
- })
495
- if requeued:
496
- try:
497
- retry_result_deliveries(workspace, event_log)
498
- except Exception as exc:
499
- event_log.write(
500
- "leader_receiver.claim_requeue_delivery_failed",
501
- error=str(exc),
502
- watcher_ids=[r["watcher_id"] for r in requeued],
503
- team_id=owner_team_id,
504
- claimed_pane_id=claimed_pane_id,
505
- )
506
- return requeued
507
-
508
-
509
- def _parse_iso(text: Any) -> datetime | None:
510
- if not isinstance(text, str) or not text:
511
- return None
512
- try:
513
- dt = datetime.fromisoformat(text.replace("Z", "+00:00"))
514
- except ValueError:
515
- return None
516
- if dt.tzinfo is None:
517
- dt = dt.replace(tzinfo=timezone.utc)
518
- return dt
519
-
520
-
521
- def format_result_watcher_notification(result: dict[str, Any]) -> str:
522
- task_id = result.get("task_id") or "unknown task"
523
- agent_id = result.get("agent_id") or "unknown agent"
524
- status = result.get("status") or "unknown"
525
- summary = result.get("summary") or "completed"
526
- lines = [
527
- f"Task {task_id} reported {status} from {agent_id}: {summary}",
528
- "Team Agent has collected this result and updated team_state.md. No manual polling is needed.",
529
- ]
530
- if result.get("result_id"):
531
- lines.insert(1, f"Result id: {result['result_id']}")
532
- rendered_tests = [
533
- f"{test.get('command') or 'test'}={test.get('status') or 'unknown'}"
534
- for test in (result.get("tests") or [])[:3]
535
- if isinstance(test, dict)
536
- ]
537
- if rendered_tests:
538
- lines.insert(1, "Tests: " + "; ".join(rendered_tests))
539
- return "\n".join(lines)