@team-agent/installer 0.2.11 → 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 -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,198 +0,0 @@
1
- """Atomic exactly-once dedupe at the leader-pane injection boundary.
2
-
3
- The current key is (result_id, owner_team_id, owner_epoch). The legacy
4
- leader_session_uuid argument is retained as nullable audit/compatibility data.
5
- """
6
- from __future__ import annotations
7
-
8
- from contextlib import closing
9
- from datetime import datetime, timedelta, timezone
10
- import sqlite3
11
- import time
12
- from typing import Any
13
- import zlib
14
-
15
- from team_agent.message_store.schema_migration import MANAGED_TABLE_LAYOUTS
16
-
17
-
18
- LEADER_NOTIFICATION_SELECT = ", ".join(MANAGED_TABLE_LAYOUTS["leader_notification_log"])
19
-
20
-
21
- def _sqlite_locked(exc: sqlite3.OperationalError) -> bool:
22
- message = str(exc).lower()
23
- return (
24
- "database is locked" in message
25
- or "database table is locked" in message
26
- or "database schema is locked" in message
27
- )
28
-
29
-
30
- def claim_leader_notification_delivery(
31
- store: Any,
32
- *,
33
- result_id: str,
34
- leader_session_uuid: str | None = None,
35
- owner_epoch: int | None = None,
36
- proposed_message_id: str,
37
- envelope_hash: str,
38
- owner_team_id: str | None,
39
- pane_id: str | None,
40
- ) -> dict[str, Any]:
41
- """Atomic claim. INSERT OR IGNORE rowcount=1 means this caller won."""
42
- team_key = owner_team_id or ""
43
- if owner_epoch is None:
44
- owner_epoch = _legacy_epoch_from_uuid(leader_session_uuid)
45
- delay = 0.05
46
- row = None
47
- for attempt in range(6):
48
- now = datetime.now(timezone.utc).isoformat()
49
- try:
50
- with closing(store.connect()) as conn:
51
- with conn:
52
- cur = conn.execute(
53
- "insert or ignore into leader_notification_log("
54
- " result_id, owner_team_id, owner_epoch, leader_session_uuid,"
55
- " notified_message_id, notified_at, leader_pane_id_at_notify, envelope_content_hash"
56
- ") values (?, ?, ?, ?, ?, ?, ?, ?)",
57
- (
58
- result_id, team_key, int(owner_epoch), leader_session_uuid,
59
- proposed_message_id, now, pane_id, envelope_hash,
60
- ),
61
- )
62
- if cur.rowcount == 1:
63
- _remember_row(store, {
64
- "result_id": result_id,
65
- "owner_team_id": team_key,
66
- "owner_epoch": int(owner_epoch),
67
- "leader_session_uuid": leader_session_uuid,
68
- "notified_message_id": proposed_message_id,
69
- "notified_at": now,
70
- "leader_pane_id_at_notify": pane_id,
71
- "envelope_content_hash": envelope_hash,
72
- })
73
- return {
74
- "status": "claimed_by_you",
75
- "notified_message_id": proposed_message_id,
76
- "notified_at": now,
77
- "envelope_content_hash": envelope_hash,
78
- }
79
- row = conn.execute(
80
- "select notified_message_id, notified_at, envelope_content_hash, "
81
- "leader_pane_id_at_notify from leader_notification_log "
82
- "where result_id = ? and owner_team_id = ? and owner_epoch = ?",
83
- (result_id, team_key, int(owner_epoch)),
84
- ).fetchone()
85
- break
86
- except sqlite3.OperationalError as exc:
87
- if not _sqlite_locked(exc) or attempt == 5:
88
- raise
89
- time.sleep(delay)
90
- delay *= 2
91
- if row is None:
92
- # Should not happen (INSERT OR IGNORE returned 0 → row must exist), but be defensive.
93
- return {"status": "claimed_by_you", "notified_message_id": proposed_message_id,
94
- "notified_at": now, "envelope_content_hash": envelope_hash}
95
- return {
96
- "status": "already_notified_by",
97
- "notified_message_id": row["notified_message_id"],
98
- "notified_at": row["notified_at"],
99
- "envelope_content_hash": row["envelope_content_hash"],
100
- "leader_pane_id_at_notify": row["leader_pane_id_at_notify"],
101
- }
102
-
103
-
104
- def peek_leader_notification(
105
- store: Any,
106
- *,
107
- result_id: str,
108
- leader_session_uuid: str | None = None,
109
- owner_team_id: str | None = None,
110
- owner_epoch: int | None = None,
111
- ) -> dict[str, Any] | None:
112
- """Read-only fast-path peek (Stage 12). Returns the existing log row for
113
- (result_id, leader_session_uuid) or None. Used by notify_result_watchers to short-
114
- circuit before calling deliver_stored_message; the authoritative atomic claim still
115
- happens at the _send_to_leader_receiver injection boundary."""
116
- team_key = owner_team_id or ""
117
- if owner_epoch is None:
118
- owner_epoch = _legacy_epoch_from_uuid(leader_session_uuid)
119
- with closing(store.connect()) as conn:
120
- if owner_team_id is None and leader_session_uuid:
121
- row = conn.execute(
122
- "select notified_message_id, notified_at, envelope_content_hash, "
123
- "leader_pane_id_at_notify, owner_team_id from leader_notification_log "
124
- "where result_id = ? and leader_session_uuid = ? order by notified_at limit 1",
125
- (result_id, leader_session_uuid),
126
- ).fetchone()
127
- else:
128
- row = conn.execute(
129
- "select notified_message_id, notified_at, envelope_content_hash, "
130
- "leader_pane_id_at_notify, owner_team_id from leader_notification_log "
131
- "where result_id = ? and owner_team_id = ? and owner_epoch = ?",
132
- (result_id, team_key, int(owner_epoch)),
133
- ).fetchone()
134
- if row is None:
135
- return None
136
- return {
137
- "notified_message_id": row["notified_message_id"],
138
- "notified_at": row["notified_at"],
139
- "envelope_content_hash": row["envelope_content_hash"],
140
- "leader_pane_id_at_notify": row["leader_pane_id_at_notify"],
141
- "owner_team_id": row["owner_team_id"],
142
- }
143
-
144
-
145
- def _legacy_epoch_from_uuid(leader_session_uuid: str | None) -> int:
146
- value = str(leader_session_uuid or "")
147
- return int(zlib.crc32(value.encode("utf-8")) & 0x7FFFFFFF)
148
-
149
-
150
- def prune_leader_notification_log(store: Any, *, max_age_hours: int = 24) -> int:
151
- """Coordinator-tick maintenance: drop rows older than max_age_hours. Cheap, bounded."""
152
- cutoff = (datetime.now(timezone.utc) - timedelta(hours=max_age_hours)).isoformat()
153
- with closing(store.connect()) as conn:
154
- with conn:
155
- cur = conn.execute(
156
- "delete from leader_notification_log where notified_at < ?",
157
- (cutoff,),
158
- )
159
- return cur.rowcount or 0
160
-
161
-
162
- def leader_notification_log_rows(store: Any, *, owner_team_id: str | None = None) -> list[dict[str, Any]]:
163
- """Test/diagnostic accessor. Returns all rows (optionally team-scoped)."""
164
- try:
165
- with closing(store.connect()) as conn:
166
- if owner_team_id is None:
167
- rows = conn.execute(
168
- f"select {LEADER_NOTIFICATION_SELECT} from leader_notification_log order by notified_at"
169
- ).fetchall()
170
- else:
171
- rows = conn.execute(
172
- f"select {LEADER_NOTIFICATION_SELECT} from leader_notification_log where owner_team_id = ? "
173
- "or owner_team_id is null order by notified_at",
174
- (owner_team_id,),
175
- ).fetchall()
176
- return [dict(row) for row in rows]
177
- except sqlite3.OperationalError:
178
- remembered = list(getattr(store, "_leader_notification_log_rows", []))
179
- if owner_team_id is not None:
180
- remembered = [row for row in remembered if row.get("owner_team_id") in {owner_team_id, None}]
181
- return remembered
182
-
183
-
184
- def _remember_row(store: Any, row: dict[str, Any]) -> None:
185
- rows = list(getattr(store, "_leader_notification_log_rows", []))
186
- rows.append(row)
187
- try:
188
- setattr(store, "_leader_notification_log_rows", rows)
189
- except Exception:
190
- pass
191
-
192
-
193
- __all__ = [
194
- "claim_leader_notification_delivery",
195
- "peek_leader_notification",
196
- "prune_leader_notification_log",
197
- "leader_notification_log_rows",
198
- ]
@@ -1,251 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import uuid
4
- from contextlib import closing
5
- from typing import Any
6
-
7
- from team_agent.message_store.schema_migration import MANAGED_TABLE_LAYOUTS
8
- from team_agent.message_store.schema import utcnow
9
-
10
-
11
- RESULT_WATCHER_SELECT = ", ".join(MANAGED_TABLE_LAYOUTS["result_watchers"])
12
-
13
-
14
- def create_result_watcher(
15
- self,
16
- task_id: str | None,
17
- agent_id: str | None,
18
- message_id: str | None,
19
- leader_id: str = "leader",
20
- owner_team_id: str | None = None,
21
- ) -> str:
22
- watcher_id = f"watch_{uuid.uuid4().hex[:12]}"
23
- with closing(self.connect()) as conn:
24
- with conn:
25
- conn.execute(
26
- """
27
- insert into result_watchers(
28
- watcher_id, owner_team_id, task_id, agent_id, message_id, leader_id, status, created_at
29
- )
30
- values (?, ?, ?, ?, ?, ?, 'pending', ?)
31
- """,
32
- (watcher_id, owner_team_id, task_id, agent_id, message_id, leader_id, utcnow()),
33
- )
34
- return watcher_id
35
-
36
- def pending_result_watchers(self, owner_team_id: str | None = None) -> list[dict[str, Any]]:
37
- with closing(self.connect()) as conn:
38
- if owner_team_id is None:
39
- rows = conn.execute(
40
- f"select {RESULT_WATCHER_SELECT} from result_watchers where status = 'pending' order by created_at"
41
- ).fetchall()
42
- else:
43
- rows = conn.execute(
44
- f"""
45
- select {RESULT_WATCHER_SELECT} from result_watchers
46
- where status = 'pending' and (owner_team_id = ? or owner_team_id is null)
47
- order by created_at
48
- """,
49
- (owner_team_id,),
50
- ).fetchall()
51
- return [dict(row) for row in rows]
52
-
53
- def retryable_result_watchers(self) -> list[dict[str, Any]]:
54
- with closing(self.connect()) as conn:
55
- rows = conn.execute(
56
- f"select {RESULT_WATCHER_SELECT} from result_watchers where status in ('pending', 'notify_failed') order by created_at"
57
- ).fetchall()
58
- return [dict(row) for row in rows]
59
-
60
- def result_watchers(self, owner_team_id: str | None = None) -> list[dict[str, Any]]:
61
- with closing(self.connect()) as conn:
62
- if owner_team_id is None:
63
- rows = conn.execute(f"select {RESULT_WATCHER_SELECT} from result_watchers order by created_at").fetchall()
64
- else:
65
- rows = conn.execute(
66
- f"select {RESULT_WATCHER_SELECT} from result_watchers where owner_team_id = ? or owner_team_id is null order by created_at",
67
- (owner_team_id,),
68
- ).fetchall()
69
- return [dict(row) for row in rows]
70
-
71
- def mark_result_watcher(
72
- self,
73
- watcher_id: str,
74
- status: str,
75
- result_id: str | None = None,
76
- notified_message_id: str | None = None,
77
- error: str | None = None,
78
- ) -> None:
79
- with closing(self.connect()) as conn:
80
- with conn:
81
- conn.execute(
82
- """
83
- update result_watchers
84
- set status = ?,
85
- completed_at = ?,
86
- result_id = coalesce(?, result_id),
87
- notified_message_id = coalesce(?, notified_message_id),
88
- error = coalesce(?, error)
89
- where watcher_id = ?
90
- """,
91
- (status, utcnow(), result_id, notified_message_id, error, watcher_id),
92
- )
93
-
94
-
95
- def requeue_delivery_exhausted_watchers(self) -> list[str]:
96
- with closing(self.connect()) as conn:
97
- with conn:
98
- rows = conn.execute(
99
- "select watcher_id from result_watchers where status = 'delivery_exhausted'"
100
- ).fetchall()
101
- watcher_ids = [row["watcher_id"] for row in rows]
102
- if watcher_ids:
103
- # Phase D hotfix-3 (78055bc) cleared notified_message_id here; Gap 32 dedupe
104
- # reverses that — preserve notified_message_id so the retry path can re-confirm
105
- # (or skip if the same result_id was already injected on a different pane_id).
106
- conn.execute(
107
- "update result_watchers "
108
- "set status = 'notify_failed', error = null, completed_at = null "
109
- "where status = 'delivery_exhausted'"
110
- )
111
- return watcher_ids
112
-
113
-
114
- def claim_leader_notification(
115
- store: Any,
116
- owner_team_id: str | None,
117
- result_id: str | None,
118
- watcher_id: str,
119
- proposed_token: str,
120
- ) -> dict[str, Any]:
121
- """DEPRECATED (Stage 12 roundtable retirement). The watcher-table UPSERT did not
122
- actually prevent duplicate leader-pane injections in Mac mini real flow because two
123
- independent code paths (scheduled_event branch + result_watchers branch) emit
124
- deliver_attempt without coordinating at the watcher level. Replaced by
125
- leader_notification_log.claim_leader_notification_delivery consulted inside
126
- _send_to_leader_receiver. Kept here as a no-op shim so legacy callers / tests that
127
- still import this symbol don't crash on import — but it does NOT perform a claim and
128
- should NOT be used in new code."""
129
- if not result_id:
130
- return {"status": "deprecated_noop", "canonical_message_id": None}
131
- return {"status": "deprecated_noop", "canonical_message_id": None}
132
-
133
-
134
- def _claim_leader_notification_disabled_impl( # legacy reference for archaeology
135
- store: Any,
136
- owner_team_id: str | None,
137
- result_id: str | None,
138
- watcher_id: str,
139
- proposed_token: str,
140
- ) -> dict[str, Any]:
141
- if not result_id:
142
- return {"status": "no_result_id", "canonical_message_id": None}
143
- with closing(store.connect()) as conn:
144
- conn.isolation_level = None
145
- try:
146
- conn.execute("BEGIN IMMEDIATE")
147
- if owner_team_id is None:
148
- sibling = conn.execute(
149
- "select notified_message_id from result_watchers "
150
- "where result_id = ? and notified_message_id is not null "
151
- "order by coalesce(completed_at, created_at) limit 1",
152
- (result_id,),
153
- ).fetchone()
154
- else:
155
- sibling = conn.execute(
156
- "select notified_message_id from result_watchers "
157
- "where result_id = ? and notified_message_id is not null "
158
- "and (owner_team_id = ? or owner_team_id is null) "
159
- "order by coalesce(completed_at, created_at) limit 1",
160
- (result_id, owner_team_id),
161
- ).fetchone()
162
- if sibling and sibling["notified_message_id"]:
163
- conn.execute("COMMIT")
164
- return {"status": "already_notified_by", "canonical_message_id": sibling["notified_message_id"]}
165
- cur = conn.execute(
166
- "update result_watchers "
167
- "set notified_message_id = ?, result_id = coalesce(result_id, ?) "
168
- "where watcher_id = ? and notified_message_id is null",
169
- (proposed_token, result_id, watcher_id),
170
- )
171
- if cur.rowcount == 1:
172
- conn.execute("COMMIT")
173
- return {"status": "claimed_by_you", "canonical_message_id": proposed_token}
174
- row = conn.execute(
175
- "select notified_message_id from result_watchers where watcher_id = ?",
176
- (watcher_id,),
177
- ).fetchone()
178
- conn.execute("COMMIT")
179
- return {
180
- "status": "already_notified_by",
181
- "canonical_message_id": (row["notified_message_id"] if row else None) or None,
182
- }
183
- except Exception:
184
- try:
185
- conn.execute("ROLLBACK")
186
- except Exception:
187
- pass
188
- raise
189
- finally:
190
- conn.isolation_level = "" # restore default
191
-
192
-
193
- def release_leader_notification_claim(
194
- store: Any,
195
- watcher_id: str,
196
- expected_token: str,
197
- ) -> bool:
198
- """Release a sentinel claim after delivery failure so the next retry can re-claim.
199
- Returns True iff we released the claim we owned (rowcount == 1)."""
200
- with closing(store.connect()) as conn:
201
- with conn:
202
- cur = conn.execute(
203
- "update result_watchers set notified_message_id = null "
204
- "where watcher_id = ? and notified_message_id = ?",
205
- (watcher_id, expected_token),
206
- )
207
- return cur.rowcount == 1
208
-
209
-
210
- def promote_leader_notification_id(
211
- store: Any,
212
- watcher_id: str,
213
- sentinel_token: str,
214
- real_message_id: str,
215
- ) -> bool:
216
- """After successful delivery, replace the sentinel claim with the real message_id.
217
- Returns True iff the promotion succeeded (rowcount == 1)."""
218
- with closing(store.connect()) as conn:
219
- with conn:
220
- cur = conn.execute(
221
- "update result_watchers set notified_message_id = ? "
222
- "where watcher_id = ? and notified_message_id = ?",
223
- (real_message_id, watcher_id, sentinel_token),
224
- )
225
- return cur.rowcount == 1
226
-
227
-
228
- def leader_notified_message_id_for_result(
229
- store: Any,
230
- owner_team_id: str | None,
231
- result_id: str | None,
232
- ) -> str | None:
233
- if not result_id:
234
- return None
235
- with closing(store.connect()) as conn:
236
- if owner_team_id is None:
237
- row = conn.execute(
238
- "select notified_message_id from result_watchers "
239
- "where result_id = ? and notified_message_id is not null "
240
- "order by coalesce(completed_at, created_at) limit 1",
241
- (result_id,),
242
- ).fetchone()
243
- else:
244
- row = conn.execute(
245
- "select notified_message_id from result_watchers "
246
- "where result_id = ? and notified_message_id is not null "
247
- "and (owner_team_id = ? or owner_team_id is null) "
248
- "order by coalesce(completed_at, created_at) limit 1",
249
- (result_id, owner_team_id),
250
- ).fetchone()
251
- return row["notified_message_id"] if row else None