@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,532 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from team_agent.messaging.deps import (
4
- EventLog,
5
- MessageStore,
6
- RuntimeError,
7
- _capture_missing_sessions,
8
- _current_task_for_agent,
9
- _deliver_pending_message,
10
- _find_agent,
11
- _find_task,
12
- _is_leader_sender,
13
- _is_leader_target,
14
- _is_runtime_team_agent,
15
- _leader_id,
16
- _message_by_id,
17
- _mirror_peer_message_to_leader,
18
- _runtime_lock,
19
- _runtime_team_agent_ids,
20
- _send_to_leader_receiver,
21
- check_team_owner,
22
- load_runtime_state,
23
- load_spec,
24
- missing_tools,
25
- route_task,
26
- save_team_scoped_state,
27
- select_runtime_state,
28
- ambiguous_team_target_result,
29
- team_state_key,
30
- update_task_status,
31
- )
32
-
33
- from pathlib import Path
34
- from typing import Any
35
-
36
- def send_message(
37
- workspace: Path, target: str | list[str] | None, content: str, task_id: str | None = None,
38
- sender: str = "leader", requires_ack: bool = True, confirm_human: bool = False,
39
- wait_visible: bool = True, timeout: float = 30.0, lock_timeout: float = 5.0,
40
- watch_result: bool = False, block_until_delivered: bool = True, team: str | None = None,
41
- ) -> dict[str, Any]:
42
- with _runtime_lock(workspace, "send", timeout=lock_timeout):
43
- return _send_message_unlocked(
44
- workspace,
45
- target,
46
- content,
47
- task_id=task_id,
48
- sender=sender,
49
- requires_ack=requires_ack,
50
- confirm_human=confirm_human,
51
- wait_visible=wait_visible,
52
- timeout=timeout,
53
- watch_result=watch_result,
54
- block_until_delivered=block_until_delivered,
55
- team=team,
56
- )
57
-
58
-
59
- def _send_message_unlocked(
60
- workspace: Path, target: str | list[str] | None, content: str, task_id: str | None = None,
61
- sender: str = "leader", requires_ack: bool = True, confirm_human: bool = False,
62
- wait_visible: bool = True, timeout: float = 30.0, watch_result: bool = False,
63
- block_until_delivered: bool = True, team: str | None = None,
64
- ) -> dict[str, Any]:
65
- if team is None:
66
- ambiguous = ambiguous_team_target_result(load_runtime_state(workspace))
67
- if ambiguous:
68
- return ambiguous
69
- state = select_runtime_state(workspace, team)
70
- gate = check_team_owner(state)
71
- spec_path = Path(state.get("spec_path") or workspace / "team.spec.yaml")
72
- if not spec_path.exists() and state.get("team_dir"):
73
- candidate = Path(str(state["team_dir"])) / "team.spec.yaml"
74
- if candidate.exists():
75
- spec_path = candidate
76
- spec = load_spec(spec_path)
77
- event_log = EventLog(workspace)
78
- if gate:
79
- from team_agent.messaging.owner_bypass import apply_worker_sender_bypass
80
- if not apply_worker_sender_bypass(state, sender, target, task_id, event_log):
81
- return gate
82
- owner_team_id = team_state_key(state)
83
- leader_id = _leader_id(state, spec)
84
- _flag_rebind_required_when_unbound_plain_shell_leader(workspace, state, spec, sender, leader_id, event_log)
85
-
86
- if isinstance(target, list):
87
- if watch_result:
88
- return {"ok": False, "status": "failed", "reason": "watch_result_not_supported_for_fanout", "to": target}
89
- return _fanout_message_unlocked(
90
- workspace,
91
- state,
92
- spec,
93
- event_log,
94
- target,
95
- content,
96
- task_id=task_id,
97
- sender=sender,
98
- requires_ack=requires_ack,
99
- wait_visible=wait_visible,
100
- timeout=timeout,
101
- block_until_delivered=block_until_delivered,
102
- owner_team_id=owner_team_id,
103
- )
104
-
105
- if target == "*":
106
- if watch_result:
107
- return {"ok": False, "status": "failed", "reason": "watch_result_not_supported_for_broadcast", "to": target}
108
- return _broadcast_message_unlocked(
109
- workspace,
110
- state,
111
- spec,
112
- event_log,
113
- content,
114
- task_id=task_id,
115
- sender=sender,
116
- requires_ack=requires_ack,
117
- wait_visible=wait_visible,
118
- timeout=timeout,
119
- block_until_delivered=block_until_delivered,
120
- owner_team_id=owner_team_id,
121
- )
122
-
123
- return _send_single_message_unlocked(
124
- workspace,
125
- state,
126
- spec,
127
- event_log,
128
- target,
129
- content,
130
- task_id=task_id,
131
- sender=sender,
132
- requires_ack=requires_ack,
133
- confirm_human=confirm_human,
134
- wait_visible=wait_visible,
135
- timeout=timeout,
136
- watch_result=watch_result,
137
- block_until_delivered=block_until_delivered,
138
- owner_team_id=owner_team_id,
139
- )
140
-
141
-
142
- def _flag_rebind_required_when_unbound_plain_shell_leader(
143
- workspace: Path,
144
- state: dict[str, Any],
145
- spec: dict[str, Any],
146
- sender: str,
147
- leader_id: str,
148
- event_log: EventLog,
149
- ) -> None:
150
- # Gap 39 C5: a leader send from a plain shell (no $TMUX_PANE) must never self-bind
151
- # the caller as the leader receiver. When the lease is fully unbound, flag a
152
- # rebind_required so the message stays queued and the operator knows to reconnect
153
- # from a real tmux leader pane. Only fires for an unbound lease + no caller pane.
154
- import os
155
- from team_agent.messaging.deps import _leader_receiver_is_direct
156
- if not _is_leader_sender(sender, leader_id):
157
- return
158
- if isinstance(state.get("team_owner"), dict) and state["team_owner"].get("pane_id"):
159
- return
160
- if _leader_receiver_is_direct(state.get("leader_receiver")):
161
- return
162
- if os.environ.get("TEAM_AGENT_LEADER_PANE_ID") or os.environ.get("TMUX_PANE"):
163
- return
164
- event_log.write(
165
- "leader_receiver.rebind_required",
166
- reason="not_in_tmux_pane",
167
- old_pane_id=(state.get("leader_receiver") or {}).get("pane_id"),
168
- new_pane_id=None,
169
- team_id=team_state_key(state),
170
- recovery_action="run team-agent claim-leader --confirm from the leader's tmux pane",
171
- )
172
-
173
-
174
- def _send_single_message_unlocked(
175
- workspace: Path,
176
- state: dict[str, Any],
177
- spec: dict[str, Any],
178
- event_log: EventLog,
179
- target: str | None,
180
- content: str,
181
- *,
182
- task_id: str | None = None,
183
- sender: str = "leader",
184
- requires_ack: bool = True,
185
- confirm_human: bool = False,
186
- wait_visible: bool = True,
187
- timeout: float = 30.0,
188
- watch_result: bool = False,
189
- mirror_peer: bool = True,
190
- route_task_id: bool = True,
191
- block_until_delivered: bool = True,
192
- owner_team_id: str | None = None,
193
- ) -> dict[str, Any]:
194
- leader_id = _leader_id(state, spec)
195
-
196
- if _is_leader_target(target, leader_id) and not _is_leader_sender(sender, leader_id):
197
- return _send_to_leader_receiver(workspace, state, leader_id, content, task_id, sender, requires_ack, event_log)
198
-
199
- from team_agent.messaging.session_drift import session_drift_refusal
200
- drift = session_drift_refusal(state, target, leader_id, sender, task_id, event_log)
201
- if drift:
202
- return drift
203
-
204
- if task_id and route_task_id:
205
- task = _find_task(state.get("tasks", []), task_id)
206
- if task.get("human_confirmation") and not task.get("human_confirmed"):
207
- if not confirm_human:
208
- update_task_status(state["tasks"], task_id, "blocked", "human confirmation required before dispatch")
209
- save_team_scoped_state(workspace, state)
210
- event_log.write(
211
- "send.human_confirmation_required",
212
- task_id=task_id,
213
- requested_target=target,
214
- )
215
- return {
216
- "ok": False,
217
- "status": "blocked",
218
- "reason": "human_confirmation_required",
219
- "task_id": task_id,
220
- }
221
- task["human_confirmed"] = True
222
- event_log.write("send.human_confirmation_granted", task_id=task_id, confirmed_by=sender)
223
- route = route_task(spec, task)
224
- routed_target = route["agent_id"]
225
- requested_target = target
226
- target = target or routed_target
227
- task["assignee"] = target
228
- event_log.write(
229
- "routing.decision",
230
- source="send",
231
- task_id=task_id,
232
- route_agent=routed_target,
233
- selected_agent=target,
234
- reason=route["reason"],
235
- manual_override=bool(requested_target and requested_target != routed_target),
236
- )
237
- agent = _find_agent(spec, target)
238
- if agent:
239
- missing = missing_tools(agent, task)
240
- if missing:
241
- update_task_status(state["tasks"], task_id, "blocked", f"missing permissions: {', '.join(missing)}")
242
- save_team_scoped_state(workspace, state)
243
- event_log.write(
244
- "send.blocked_missing_permissions",
245
- task_id=task_id,
246
- agent_id=target,
247
- missing_tools=missing,
248
- )
249
- return {
250
- "ok": False,
251
- "status": "blocked",
252
- "task_id": task_id,
253
- "agent_id": target,
254
- "missing_tools": missing,
255
- }
256
-
257
- if not target:
258
- raise RuntimeError("send requires target or --task")
259
- if not _is_leader_target(target, leader_id) and not _is_runtime_team_agent(target, state, spec):
260
- event_log.write("send.target_rejected", sender=sender, target=target, reason="target_not_in_team")
261
- return {"ok": False, "status": "refused", "reason": "target_not_in_team", "from": sender, "to": target}
262
- store = MessageStore(workspace)
263
- message_id = store.create_message(task_id, sender, target, content, requires_ack=requires_ack, owner_team_id=owner_team_id)
264
- if not block_until_delivered:
265
- watch: dict[str, Any] | None = None
266
- if watch_result:
267
- watch_task_id = task_id or _current_task_for_agent(state.get("tasks", []), str(target))
268
- watcher_id = store.create_result_watcher(watch_task_id, str(target), message_id, leader_id, owner_team_id=owner_team_id)
269
- watch = {
270
- "status": "registered",
271
- "watcher_id": watcher_id,
272
- "task_id": watch_task_id,
273
- "agent_id": target,
274
- "notice": (
275
- "Team Agent will deliver this message when the worker is available, "
276
- "then collect the result and notify the leader when this task reports completion."
277
- ),
278
- }
279
- event_log.write(
280
- "result_watcher.created",
281
- watcher_id=watcher_id,
282
- task_id=watch_task_id,
283
- agent_id=target,
284
- message_id=message_id,
285
- )
286
- _capture_missing_sessions(workspace, state, event_log, timeout_s=0.0, log_miss=False)
287
- save_team_scoped_state(workspace, state)
288
- event_log.write(
289
- "send.durably_stored",
290
- message_id=message_id,
291
- target=target,
292
- sender=sender,
293
- task_id=task_id,
294
- )
295
- result = {
296
- "ok": True,
297
- "message_id": message_id,
298
- "status": "queued",
299
- "message_status": "accepted",
300
- "to": target,
301
- "queued": True,
302
- "durably_stored": True,
303
- "reason": "deferred_to_coordinator",
304
- "visible": False,
305
- "submitted": False,
306
- }
307
- if watch is not None:
308
- result["watch_result"] = True
309
- result["watch"] = watch
310
- return result
311
- delivered_result = _deliver_pending_message(workspace, state, message_id, wait_visible=wait_visible, timeout=timeout)
312
- row = _message_by_id(store, message_id)
313
- message_status = row["status"] if row else delivered_result.get("message_status", delivered_result.get("status", "accepted"))
314
- if (
315
- mirror_peer
316
- and not _is_leader_sender(sender, leader_id)
317
- and not _is_leader_target(target, leader_id)
318
- and delivered_result.get("ok")
319
- and not delivered_result.get("queued")
320
- ):
321
- _mirror_peer_message_to_leader(workspace, state, sender, target, content, task_id, event_log)
322
- watch: dict[str, Any] | None = None
323
- if watch_result and delivered_result.get("ok"):
324
- watch_task_id = task_id or _current_task_for_agent(state.get("tasks", []), str(target))
325
- watcher_id = store.create_result_watcher(watch_task_id, str(target), message_id, leader_id, owner_team_id=owner_team_id)
326
- watch = {
327
- "status": "registered",
328
- "watcher_id": watcher_id,
329
- "task_id": watch_task_id,
330
- "agent_id": target,
331
- "notice": (
332
- "Team Agent will deliver this message when the worker is available, "
333
- "then collect the result and notify the leader when this task reports completion."
334
- if delivered_result.get("queued")
335
- else "Team Agent will collect the result and notify the leader when this task reports completion."
336
- ),
337
- }
338
- event_log.write(
339
- "result_watcher.created",
340
- watcher_id=watcher_id,
341
- task_id=watch_task_id,
342
- agent_id=target,
343
- message_id=message_id,
344
- )
345
- _capture_missing_sessions(workspace, state, event_log, timeout_s=0.0, log_miss=False)
346
- save_team_scoped_state(workspace, state)
347
- result = {
348
- "ok": bool(delivered_result.get("ok")),
349
- "message_id": message_id,
350
- "status": delivered_result.get("status", message_status),
351
- "message_status": message_status,
352
- "to": target,
353
- "visible": message_status in {"visible", "submitted"},
354
- "submitted": message_status in {"visible", "submitted", "submitted_unverified", "delivered", "acknowledged"},
355
- "verification": delivered_result.get("verification"),
356
- "submit_verification": delivered_result.get("submit_verification"),
357
- "turn_verification": delivered_result.get("turn_verification"),
358
- }
359
- result.update({key: delivered_result[key] for key in ("reason", "stage") if delivered_result.get(key)})
360
- result.update(_structured_delivery_refusal(delivered_result))
361
- if delivered_result.get("queued"):
362
- result["queued"] = True
363
- result["reason"] = delivered_result.get("reason")
364
- if delivered_result.get("warning"):
365
- result["warning"] = delivered_result["warning"]
366
- for key in ("paste_attempts", "submit_attempts"):
367
- if key in delivered_result:
368
- result[key] = delivered_result[key]
369
- if watch is not None:
370
- result["watch_result"] = True
371
- result["watch"] = watch
372
- return result
373
-
374
-
375
- def _broadcast_message_unlocked(
376
- workspace: Path,
377
- state: dict[str, Any],
378
- spec: dict[str, Any],
379
- event_log: EventLog,
380
- content: str,
381
- *,
382
- task_id: str | None,
383
- sender: str,
384
- requires_ack: bool,
385
- wait_visible: bool,
386
- timeout: float,
387
- block_until_delivered: bool = True,
388
- owner_team_id: str | None = None,
389
- ) -> dict[str, Any]:
390
- targets = _broadcast_targets(state, spec, sender)
391
- if not targets:
392
- event_log.write("send.broadcast_skipped", sender=sender, reason="no_team_recipients")
393
- return {"ok": False, "status": "failed", "reason": "no_team_recipients", "to": "*", "targets": []}
394
- event_log.write("send.broadcast_start", sender=sender, targets=targets, task_id=task_id)
395
- deliveries: list[dict[str, Any]] = []
396
- for recipient in targets:
397
- result = _send_single_message_unlocked(
398
- workspace,
399
- state,
400
- spec,
401
- event_log,
402
- recipient,
403
- content,
404
- task_id=task_id,
405
- sender=sender,
406
- requires_ack=requires_ack,
407
- confirm_human=False,
408
- wait_visible=wait_visible,
409
- timeout=timeout,
410
- watch_result=False,
411
- mirror_peer=False,
412
- route_task_id=False,
413
- block_until_delivered=block_until_delivered,
414
- owner_team_id=owner_team_id,
415
- )
416
- deliveries.append(_compact_broadcast_delivery(result))
417
- failed = [item for item in deliveries if not item.get("ok")]
418
- status = "broadcast_delivered" if not failed else "broadcast_partial"
419
- event_log.write(
420
- "send.broadcast_complete",
421
- sender=sender,
422
- targets=targets,
423
- status=status,
424
- delivered_count=len(deliveries) - len(failed),
425
- failed_count=len(failed),
426
- )
427
- return {
428
- "ok": not failed,
429
- "status": status,
430
- "to": "*",
431
- "targets": targets,
432
- "delivered_count": len(deliveries) - len(failed),
433
- "failed_count": len(failed),
434
- "deliveries": deliveries,
435
- }
436
-
437
-
438
- def _fanout_message_unlocked(
439
- workspace: Path,
440
- state: dict[str, Any],
441
- spec: dict[str, Any],
442
- event_log: EventLog,
443
- targets: list[str],
444
- content: str,
445
- *,
446
- task_id: str | None,
447
- sender: str,
448
- requires_ack: bool,
449
- wait_visible: bool,
450
- timeout: float,
451
- block_until_delivered: bool = True,
452
- owner_team_id: str | None = None,
453
- ) -> dict[str, Any]:
454
- raw_recipients = [target for target in targets if target]
455
- recipients = list(dict.fromkeys(raw_recipients))
456
- if not recipients:
457
- return {"ok": False, "status": "failed", "reason": "no_fanout_recipients", "to": targets, "targets": []}
458
- if len(raw_recipients) != len(recipients):
459
- event_log.write("send.fanout_dedupe", sender=sender, task_id=task_id,
460
- raw_targets=raw_recipients, deduped_targets=recipients,
461
- duplicate_count=len(raw_recipients) - len(recipients))
462
- event_log.write("send.fanout_start", sender=sender, targets=recipients, task_id=task_id)
463
- deliveries: list[dict[str, Any]] = []
464
- by_recipient: dict[str, dict[str, Any]] = {}
465
- for recipient in recipients:
466
- result = _send_single_message_unlocked(
467
- workspace,
468
- state,
469
- spec,
470
- event_log,
471
- recipient,
472
- content,
473
- task_id=task_id,
474
- sender=sender,
475
- requires_ack=requires_ack,
476
- confirm_human=False,
477
- wait_visible=wait_visible,
478
- timeout=timeout,
479
- watch_result=False,
480
- mirror_peer=False,
481
- route_task_id=False,
482
- block_until_delivered=block_until_delivered,
483
- owner_team_id=owner_team_id,
484
- )
485
- compact = _compact_fanout_delivery(result)
486
- deliveries.append(compact)
487
- by_recipient[recipient] = compact
488
- failed = [item for item in deliveries if not item.get("delivered")]
489
- status = "fanout_delivered" if not failed else "fanout_partial"
490
- aggregate = {
491
- "ok": True,
492
- "status": status,
493
- "to": recipients,
494
- "targets": recipients,
495
- "delivered_count": len(deliveries) - len(failed),
496
- "failed_count": len(failed),
497
- "deliveries": deliveries,
498
- "recipients": by_recipient,
499
- }
500
- event_log.write("send.fanout_status", **aggregate)
501
- return aggregate
502
-
503
-
504
- def _broadcast_targets(state: dict[str, Any], spec: dict[str, Any], sender: str) -> list[str]:
505
- leader_id = _leader_id(state, spec)
506
- targets = [leader_id, *_runtime_team_agent_ids(state, spec)]
507
- if _is_leader_sender(sender, leader_id):
508
- excluded = {leader_id}
509
- else:
510
- excluded = {sender}
511
- return [target for target in targets if target not in excluded]
512
-
513
-
514
- def _compact_broadcast_delivery(result: dict[str, Any]) -> dict[str, Any]:
515
- keys = ["ok", "status", "message_id", "to", "reason", "channel", "detected", "pane_id", "pane_mode", "pane_capture_tail", "stage", "verification"]
516
- return {key: result[key] for key in keys if key in result}
517
-
518
-
519
- def _compact_fanout_delivery(result: dict[str, Any]) -> dict[str, Any]:
520
- compact = _compact_broadcast_delivery(result)
521
- compact["delivered"] = bool(result.get("submitted") or result.get("visible") or result.get("status") in {"submitted", "visible", "delivered", "acknowledged"})
522
- return compact
523
-
524
-
525
- def _structured_delivery_refusal(delivered_result: dict[str, Any]) -> dict[str, Any]:
526
- attempts = delivered_result.get("paste_attempts")
527
- if not isinstance(attempts, list):
528
- return {}
529
- for attempt in attempts:
530
- if isinstance(attempt, dict) and attempt.get("reason") == "recipient_pane_in_non_input_mode":
531
- return {key: attempt[key] for key in ("detected", "pane_id", "pane_mode", "pane_capture_tail") if key in attempt}
532
- return {}
@@ -1,94 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
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
-
10
- _UUID = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}"
11
- _RESUME_THREAD_RE = re.compile(
12
- rf"(?:Switched to thread|resume|thread)\s+({_UUID})",
13
- re.IGNORECASE,
14
- )
15
-
16
-
17
- def extract_thread_id_from_scrollback(scrollback: str) -> str | None:
18
- if not scrollback:
19
- return None
20
- matches = _RESUME_THREAD_RE.findall(scrollback)
21
- if not matches:
22
- return None
23
- return matches[-1].lower()
24
-
25
-
26
- def detect_session_drift(
27
- workspace: Path,
28
- state: dict[str, Any],
29
- event_log: EventLog,
30
- *,
31
- agent_id: str,
32
- agent_state: dict[str, Any],
33
- scrollback: str,
34
- ) -> dict[str, Any] | None:
35
- provider = str(agent_state.get("provider") or "").lower()
36
- if provider != "codex":
37
- return None
38
- stored = str(agent_state.get("session_id") or "").strip()
39
- if not stored:
40
- return None
41
- if str(agent_state.get("status") or "").lower() == "session_drift":
42
- return None
43
- actual = extract_thread_id_from_scrollback(scrollback)
44
- if not actual:
45
- return None
46
- if actual.lower() == stored.lower():
47
- return None
48
- now = datetime.now(timezone.utc).isoformat()
49
- event = event_log.write(
50
- "coordinator.session_drift_detected",
51
- agent_id=agent_id,
52
- stored_session_id=stored,
53
- actual_thread_id=actual,
54
- status="session_drift",
55
- provider=provider,
56
- ts=now,
57
- remediation="team-agent reset-agent --discard-session <agent>",
58
- )
59
- agent_state["status"] = "session_drift"
60
- agent_state["session_drift"] = {
61
- "stored_session_id": stored,
62
- "actual_thread_id": actual,
63
- "detected_at": now,
64
- "remediation": "team-agent reset-agent --discard-session <agent>",
65
- }
66
- return event
67
-
68
-
69
- def session_drift_refusal(state, target, leader_id, sender, task_id, event_log):
70
- if not target or target == leader_id or target == "*":
71
- return None
72
- rs = (state.get("agents") or {}).get(target) or {}
73
- if str(rs.get("status") or "").lower() != "session_drift":
74
- return None
75
- info = rs.get("session_drift") or {}
76
- event_log.write(
77
- "send.refused_session_drift",
78
- target=target,
79
- sender=sender,
80
- task_id=task_id,
81
- stored_session_id=info.get("stored_session_id"),
82
- actual_thread_id=info.get("actual_thread_id"),
83
- )
84
- return {
85
- "ok": False,
86
- "status": "refused",
87
- "reason": "session_drift",
88
- "to": target,
89
- "action": f"team-agent reset-agent --discard-session {target}",
90
- "session_drift": info,
91
- }
92
-
93
-
94
- __all__ = ["detect_session_drift", "extract_thread_id_from_scrollback", "session_drift_refusal"]