@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,504 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from team_agent.messaging.deps import (
4
- EventLog,
5
- MessageStore,
6
- _message_by_id,
7
- _message_payload,
8
- _tmux_inject_text,
9
- _tmux_window_exists,
10
- core_render_message,
11
- )
12
- from team_agent.idle_predicate import record_turn_open_after_delivery
13
-
14
- from datetime import datetime, timedelta, timezone
15
- from pathlib import Path
16
- from typing import Any
17
- import time
18
-
19
-
20
- def _tmux_pane_width(target: str) -> dict[str, Any]:
21
- """Query the tmux pane width (display columns) for ``target``.
22
-
23
- Live wiring seam for the trust-prompt truncation matcher: returns
24
- ``{"ok": True, "pane_width": <int>}`` on success or
25
- ``{"ok": False, "error": "..."}`` on any failure / timeout / unparseable
26
- output. Fail-safe by design: NEVER returns a default width. Callers must
27
- treat failure as "no boundary signal" and let the workspace matcher fall
28
- back to exact equality, so a hard-truncated prompt is never auto-answered
29
- on guesswork.
30
- """
31
- from team_agent.messaging.deps import run_cmd
32
- try:
33
- proc = run_cmd(
34
- ["tmux", "display-message", "-p", "-t", str(target), "-F", "#{pane_width}"],
35
- timeout=2,
36
- )
37
- except Exception as exc: # pragma: no cover - defensive; tmux not present, timeout, etc.
38
- return {"ok": False, "error": f"tmux_query_failed:{exc.__class__.__name__}"}
39
- if getattr(proc, "returncode", 1) != 0:
40
- err = (getattr(proc, "stderr", "") or "").strip().splitlines()
41
- return {"ok": False, "error": err[0] if err else "tmux_query_nonzero"}
42
- text = (getattr(proc, "stdout", "") or "").strip()
43
- if not text:
44
- return {"ok": False, "error": "empty_output"}
45
- try:
46
- width = int(text.splitlines()[0].strip())
47
- except (ValueError, IndexError):
48
- return {"ok": False, "error": "unparseable_output"}
49
- if width <= 0:
50
- return {"ok": False, "error": "non_positive_width"}
51
- return {"ok": True, "pane_width": width}
52
-
53
-
54
- # Spark MEDIUM sweep #3 (2026-05-26): retry_needed bounded backoff. Each entry is
55
- # the delay (seconds) BEFORE the attempt with that number runs; attempt 1 was the
56
- # original delivery, attempt 2 fires 5s after retry_needed, attempt 3 fires 15s
57
- # after the previous, attempt 4 fires 30s after the previous. _TRUST_RETRY_MAX_ATTEMPTS
58
- # bounds the total — the 4th retry_needed is terminal and emits
59
- # leader_panes.trust_auto_answer_exhausted.
60
- _TRUST_RETRY_BACKOFF_SECONDS = {2: 5, 3: 15, 4: 30}
61
- _TRUST_RETRY_MAX_ATTEMPTS = 4
62
-
63
- def _deliver_pending_message(
64
- workspace: Path,
65
- state: dict[str, Any],
66
- message_id: str,
67
- wait_visible: bool = True,
68
- timeout: float = 30.0,
69
- *,
70
- _trust_retry_attempt: int = 1,
71
- ) -> dict[str, Any]:
72
- store = MessageStore(workspace)
73
- row = next((m for m in store.messages() if m["message_id"] == message_id), None)
74
- if not row:
75
- return {"ok": False, "status": "failed", "reason": "message_missing"}
76
- agent_state = state.get("agents", {}).get(row["recipient"])
77
- if not agent_state:
78
- store.mark(message_id, "failed", "unknown recipient")
79
- return {"ok": False, "status": "failed", "reason": "unknown_recipient"}
80
- session_name = state.get("session_name")
81
- window = agent_state.get("window", row["recipient"])
82
- payload = _message_payload(row)
83
- rendered = core_render_message(payload)
84
- text = rendered["text"]
85
- if not session_name or not _tmux_window_exists(session_name, window):
86
- store.mark(message_id, "failed", "tmux target missing")
87
- EventLog(workspace).write("send.failed", message_id=message_id, reason="tmux target missing", target=f"{session_name}:{window}")
88
- return {"ok": False, "status": "failed", "reason": "tmux_target_missing"}
89
- target = f"{session_name}:{window}"
90
- if not store.claim_for_delivery(message_id):
91
- current = _message_by_id(store, message_id)
92
- status = current["status"] if current else "missing"
93
- EventLog(workspace).write("send.delivery_claim_skipped", message_id=message_id, target=target, status=status)
94
- return {
95
- "ok": status in {"injected", "visible", "submitted", "submitted_unverified", "delivered", "acknowledged"},
96
- "status": status,
97
- "reason": "message_already_claimed",
98
- }
99
- recipient_status = agent_state.get("status")
100
- EventLog(workspace).write(
101
- "send.deliver_attempt",
102
- message_id=message_id,
103
- target=target,
104
- payload=payload,
105
- recipient_status=recipient_status,
106
- recipient_busy=recipient_status == "busy",
107
- visible_token=rendered.get("token"),
108
- )
109
- injection = _tmux_inject_text(
110
- target,
111
- text,
112
- "Enter",
113
- f"team-agent-send-{message_id}",
114
- attempts=3 if wait_visible else 1,
115
- provider=agent_state.get("provider", "fake"),
116
- )
117
- if not injection.get("ok") and injection.get("detected") == "codex_trust_prompt":
118
- # Gap 29 (Stage 2): opt-in trust auto-answer. The helper enforces both the
119
- # opt-in flag and a workspace-dir match before sending '1'+Enter, then we
120
- # retry the original paste once the prompt has actually been dismissed.
121
- # Bypassed entirely when opt-out (default) — the existing failed envelope
122
- # is preserved.
123
- from team_agent.messaging.leader_panes import attempt_trust_auto_answer
124
- pane_target = injection.get("pane_id") or target
125
- # Live wiring: query the tmux pane width now and hand it to the trust
126
- # matcher via state["pane_width"]. On failure we leave pane_width
127
- # absent so the matcher falls back to exact equality (fail-safe — a
128
- # right-edge truncated prefix is never auto-answered on guesswork).
129
- width_query = _tmux_pane_width(pane_target)
130
- trust_state = dict(state) if isinstance(state, dict) else {}
131
- if width_query.get("ok"):
132
- trust_state["pane_width"] = width_query["pane_width"]
133
- answer = attempt_trust_auto_answer(
134
- workspace,
135
- pane_target,
136
- injection.get("pane_capture_tail") or "",
137
- EventLog(workspace),
138
- state=trust_state,
139
- )
140
- if answer.get("answered"):
141
- # Spark MEDIUM #4 (2026-05-26): replace the fixed 0.3s sleep with a
142
- # bounded poll. Slow terminals can take well over a second to clear
143
- # the trust prompt; sleeping a fixed amount races dismissal and
144
- # leaves the retry hitting the same codex_trust_prompt state. We
145
- # poll for prompt dismissal up to 3s; if still present, return a
146
- # retry_needed envelope and let the upstream scheduler decide
147
- # whether to back off and try again later.
148
- dismissed = _wait_for_trust_prompt_dismissal(
149
- injection.get("pane_id") or target, timeout=3.0,
150
- )
151
- if not dismissed:
152
- return _handle_trust_retry_needed(
153
- workspace, state, store, message_id, target, injection,
154
- attempt=_trust_retry_attempt,
155
- )
156
- injection = _tmux_inject_text(
157
- target,
158
- text,
159
- "Enter",
160
- f"team-agent-send-{message_id}-trust-retry",
161
- attempts=3 if wait_visible else 1,
162
- provider=agent_state.get("provider", "fake"),
163
- )
164
- if injection["ok"]:
165
- store.mark(message_id, "submitted")
166
- send_event_log = EventLog(workspace)
167
- _stamp_first_send_at_if_leader_to_worker(state, row, send_event_log)
168
- _record_turn_open_if_leader_to_worker(state, row, send_event_log)
169
- send_event_log.write(
170
- "send.submitted",
171
- message_id=message_id,
172
- target=target,
173
- status="submitted",
174
- verification=injection.get("verification"),
175
- submit_verification=injection.get("submit_verification"),
176
- turn_verification=injection.get("turn_verification"),
177
- paste_attempts=injection.get("attempts"),
178
- submit_attempts=injection.get("submit_attempts"),
179
- )
180
- return {
181
- "ok": True,
182
- "status": "delivered",
183
- "message_status": "submitted",
184
- "verification": injection.get("verification"),
185
- "submit_verification": injection.get("submit_verification"),
186
- "turn_verification": injection.get("turn_verification"),
187
- "paste_attempts": injection.get("attempts"),
188
- "submit_attempts": injection.get("submit_attempts"),
189
- }
190
- reason = injection.get("error") or injection.get("verification") or "tmux injection failed"
191
- store.mark(message_id, "failed", reason)
192
- EventLog(workspace).write(
193
- "send.failed",
194
- message_id=message_id,
195
- reason=reason,
196
- target=target,
197
- stage=injection.get("stage"),
198
- verification=injection.get("verification"),
199
- submit_verification=injection.get("submit_verification"),
200
- turn_verification=injection.get("turn_verification"),
201
- paste_attempts=injection.get("attempts"),
202
- submit_attempts=injection.get("submit_attempts"),
203
- )
204
- return {
205
- "ok": False,
206
- "status": "failed",
207
- "reason": reason,
208
- "stage": injection.get("stage"),
209
- "verification": injection.get("verification"),
210
- "submit_verification": injection.get("submit_verification"),
211
- "turn_verification": injection.get("turn_verification"),
212
- "paste_attempts": injection.get("attempts"),
213
- "submit_attempts": injection.get("submit_attempts"),
214
- "detected": injection.get("detected"),
215
- "pane_id": injection.get("pane_id"),
216
- "pane_mode": injection.get("pane_mode"),
217
- "pane_capture_tail": injection.get("pane_capture_tail"),
218
- }
219
-
220
-
221
- def _handle_trust_retry_needed(
222
- workspace: Path,
223
- state: dict[str, Any],
224
- store: MessageStore,
225
- message_id: str,
226
- target: str,
227
- injection: dict[str, Any],
228
- *,
229
- attempt: int,
230
- ) -> dict[str, Any]:
231
- """Spark MEDIUM sweep #3: replace the dead-end failed mark with a real
232
- bounded-backoff consumer. attempt is the number of the delivery that JUST
233
- failed (1 = the original delivery; 2..4 = the scheduler-fired retries).
234
-
235
- Behaviour:
236
- * attempt < _TRUST_RETRY_MAX_ATTEMPTS: schedule a trust_retry
237
- scheduled_event for the message, holding the message in 'failed' status
238
- so _deliver_pending_messages does not race the scheduler. Emit
239
- leader_panes.trust_auto_answer_retry_scheduled. Return status='retry_scheduled'.
240
- * attempt >= _TRUST_RETRY_MAX_ATTEMPTS: terminal. Mark the message failed
241
- and emit leader_panes.trust_auto_answer_exhausted. Return
242
- status='trust_auto_answer_exhausted'.
243
- """
244
- event_log = EventLog(workspace)
245
- next_attempt = attempt + 1
246
- if next_attempt > _TRUST_RETRY_MAX_ATTEMPTS:
247
- store.mark(message_id, "failed", "trust_auto_answer_exhausted")
248
- event_log.write(
249
- "leader_panes.trust_auto_answer_exhausted",
250
- message_id=message_id,
251
- workspace=str(workspace),
252
- attempts=attempt,
253
- target=target,
254
- pane_id=injection.get("pane_id"),
255
- reason="trust_auto_answer_exhausted",
256
- )
257
- return {
258
- "ok": False,
259
- "status": "trust_auto_answer_exhausted",
260
- "reason": "trust_auto_answer_exhausted",
261
- "attempts": attempt,
262
- "detected": injection.get("detected"),
263
- "pane_id": injection.get("pane_id"),
264
- "pane_mode": injection.get("pane_mode"),
265
- "pane_capture_tail": injection.get("pane_capture_tail"),
266
- }
267
- backoff = _TRUST_RETRY_BACKOFF_SECONDS.get(next_attempt, _TRUST_RETRY_BACKOFF_SECONDS[_TRUST_RETRY_MAX_ATTEMPTS])
268
- due_at = (datetime.now(timezone.utc) + timedelta(seconds=backoff)).isoformat()
269
- owner_team_id = _message_owner_team_id(store, message_id)
270
- event_id = store.add_scheduled_event(
271
- due_at,
272
- message_id,
273
- "trust_retry",
274
- {
275
- "message_id": message_id,
276
- "attempt": next_attempt,
277
- "max_attempts": _TRUST_RETRY_MAX_ATTEMPTS,
278
- "first_target": target,
279
- },
280
- owner_team_id=owner_team_id,
281
- )
282
- # Hold the message in 'failed' so _deliver_pending_messages does not race
283
- # the scheduled retry. The scheduler consumer resets it to 'accepted' just
284
- # before re-delivery.
285
- store.mark(message_id, "failed", "trust_retry_scheduled")
286
- event_log.write(
287
- "leader_panes.trust_auto_answer_retry_needed",
288
- message_id=message_id,
289
- workspace=str(workspace),
290
- pane_id=injection.get("pane_id") or target,
291
- target=target,
292
- reason="trust_prompt_not_dismissed_after_answer",
293
- attempt=attempt,
294
- )
295
- event_log.write(
296
- "leader_panes.trust_auto_answer_retry_scheduled",
297
- message_id=message_id,
298
- workspace=str(workspace),
299
- scheduled_event_id=event_id,
300
- due_at=due_at,
301
- next_attempt=next_attempt,
302
- max_attempts=_TRUST_RETRY_MAX_ATTEMPTS,
303
- backoff_seconds=backoff,
304
- )
305
- return {
306
- "ok": False,
307
- "status": "retry_scheduled",
308
- "reason": "trust_prompt_not_dismissed_after_answer",
309
- "stage": "trust_auto_answer_dismissal_wait",
310
- "verification": "trust_prompt_not_dismissed_after_answer",
311
- "scheduled_event_id": event_id,
312
- "scheduled_retry_at": due_at,
313
- "next_attempt": next_attempt,
314
- "max_attempts": _TRUST_RETRY_MAX_ATTEMPTS,
315
- "detected": injection.get("detected"),
316
- "pane_id": injection.get("pane_id"),
317
- "pane_mode": injection.get("pane_mode"),
318
- "pane_capture_tail": injection.get("pane_capture_tail"),
319
- }
320
-
321
-
322
- def _message_owner_team_id(store: MessageStore, message_id: str) -> str | None:
323
- row = _message_by_id(store, message_id)
324
- if not row:
325
- return None
326
- owner = row.get("owner_team_id")
327
- return str(owner) if owner else None
328
-
329
-
330
- def _execute_trust_retry(
331
- workspace: Path,
332
- store: MessageStore,
333
- event_log: EventLog,
334
- payload: dict[str, Any],
335
- *,
336
- owner_team_id: str | None = None,
337
- ) -> dict[str, Any]:
338
- """Scheduler-side consumer for kind='trust_retry'. Resets the message back
339
- to 'accepted' so claim_for_delivery succeeds, re-runs _deliver_pending_message,
340
- and either succeeds, escalates to a further retry (via _handle_trust_retry_needed),
341
- or hits the terminal exhausted branch.
342
- """
343
- from team_agent.state import load_runtime_state
344
- message_id = str(payload.get("message_id") or "")
345
- if not message_id:
346
- return {"ok": False, "reason": "trust_retry_missing_message_id"}
347
- attempt = int(payload.get("attempt") or 1)
348
- row = _message_by_id(store, message_id)
349
- if not row:
350
- event_log.write(
351
- "leader_panes.trust_auto_answer_retry_skipped",
352
- message_id=message_id,
353
- reason="message_missing",
354
- attempt=attempt,
355
- )
356
- return {"ok": False, "reason": "message_missing"}
357
- # Reset to accepted so claim_for_delivery succeeds. The previous attempt
358
- # left the row in 'failed' status with reason='trust_retry_scheduled'.
359
- store.mark(message_id, "accepted", "trust_retry_resuming")
360
- event_log.write(
361
- "leader_panes.trust_auto_answer_retry_attempted",
362
- message_id=message_id,
363
- workspace=str(workspace),
364
- attempt=attempt,
365
- max_attempts=int(payload.get("max_attempts") or _TRUST_RETRY_MAX_ATTEMPTS),
366
- )
367
- state = load_runtime_state(workspace)
368
- if owner_team_id and isinstance(state.get("teams"), dict):
369
- scoped = state["teams"].get(owner_team_id)
370
- if isinstance(scoped, dict):
371
- state = scoped
372
- delivery_result = _deliver_pending_message(
373
- workspace, state, message_id,
374
- wait_visible=True, timeout=30.0,
375
- _trust_retry_attempt=attempt,
376
- )
377
- return delivery_result
378
-
379
-
380
- def _stamp_first_send_at_if_leader_to_worker(
381
- state: dict[str, Any],
382
- row: dict[str, Any],
383
- event_log: EventLog | None = None,
384
- ) -> None:
385
- """Route B atomicity (2026-05-27): record the first time the leader
386
- successfully sends work to each worker. The presence of this stamp drives
387
- restart's resumability decision — a worker the leader has interacted with
388
- has accumulated conversation state, so a missing session_id at restart
389
- time IS an atomicity violation. A worker that has never received work
390
- legitimately fresh-starts during restart.
391
-
392
- Only stamped once per worker (idempotent across re-sends). Only fires on
393
- leader -> worker sends; worker-to-worker peer messages do not count.
394
- The mutation lives on the state dict the caller already saves
395
- (`save_team_scoped_state` in send.py, or `save_runtime_state` after
396
- coordinator_tick), so persistence is automatic.
397
-
398
- C1 (cr verdict, 2026-05-27): when the stamp transitions null -> ts (the
399
- one-time write), emit a `worker.first_interaction` audit event with
400
- worker_id, first_send_at, message_id. Re-sends to the same worker hit the
401
- idempotency guard above and do NOT re-emit. Worker-to-worker peer sends
402
- short-circuit at the sender check and do NOT emit.
403
- """
404
- sender = str(row.get("sender") or "")
405
- recipient = str(row.get("recipient") or "")
406
- if not recipient:
407
- return
408
- leader_id = str((state.get("leader") or {}).get("id") or "leader")
409
- if sender not in {"leader", "Leader", leader_id}:
410
- return
411
- agents = state.get("agents")
412
- if not isinstance(agents, dict):
413
- return
414
- agent_state = agents.get(recipient)
415
- if not isinstance(agent_state, dict):
416
- return
417
- if agent_state.get("first_send_at"):
418
- return
419
- stamp = datetime.now(timezone.utc).isoformat()
420
- agent_state["first_send_at"] = stamp
421
- if event_log is not None:
422
- event_log.write(
423
- "worker.first_interaction",
424
- worker_id=recipient,
425
- first_send_at=stamp,
426
- message_id=str(row.get("message_id") or ""),
427
- )
428
-
429
-
430
- def _record_turn_open_if_leader_to_worker(
431
- state: dict[str, Any],
432
- row: dict[str, Any],
433
- event_log: EventLog,
434
- ) -> None:
435
- sender = str(row.get("sender") or "")
436
- recipient = str(row.get("recipient") or "")
437
- if not recipient:
438
- return
439
- leader_id = str((state.get("leader") or {}).get("id") or "leader")
440
- if sender not in {"leader", "Leader", leader_id}:
441
- return
442
- agents = state.get("agents")
443
- if not isinstance(agents, dict) or not isinstance(agents.get(recipient), dict):
444
- return
445
- coordinator = state.setdefault("coordinator", {})
446
- message_id = str(row.get("message_id") or "")
447
- task_id = str(row.get("task_id") or "")
448
- coordinator["idle_takeover_monitor"] = record_turn_open_after_delivery(
449
- coordinator.get("idle_takeover_monitor"),
450
- node_id=recipient,
451
- turn_id=task_id or message_id or None,
452
- delivered_message_id=message_id or None,
453
- now_monotonic=time.monotonic(),
454
- event_sink=lambda name, fields: event_log.write(name, **fields),
455
- )
456
-
457
-
458
- def _wait_for_trust_prompt_dismissal(target: str, *, timeout: float = 3.0, poll_interval: float = 0.1) -> bool:
459
- """Spark MEDIUM #4: bounded poll for trust prompt dismissal. Returns True once
460
- the pane no longer matches detect_non_input_scrollback, False if the prompt
461
- is still present after `timeout` seconds. Uses the same detector the inject
462
- path uses so behaviour stays consistent."""
463
- import time as _time
464
- from team_agent.messaging.tmux_prompt import detect_non_input_scrollback
465
- deadline = _time.monotonic() + max(timeout, 0.0)
466
- while True:
467
- capture = _capture_pane_tail(target)
468
- detected = detect_non_input_scrollback(capture)
469
- if detected != "codex_trust_prompt":
470
- return True
471
- if _time.monotonic() >= deadline:
472
- return False
473
- _time.sleep(poll_interval)
474
-
475
-
476
- def _capture_pane_tail(target: str) -> str:
477
- from team_agent.messaging.deps import _capture_tmux_pane_text
478
- capture = _capture_tmux_pane_text(target)
479
- if not capture.get("ok"):
480
- return ""
481
- return str(capture.get("capture") or "")
482
-
483
-
484
- def _deliver_pending_messages(workspace: Path, state: dict[str, Any], event_log: EventLog) -> list[str]:
485
- store = MessageStore(workspace)
486
- delivered: list[str] = []
487
- for row in store.messages():
488
- if row["status"] not in {"pending", "accepted"}:
489
- continue
490
- agent_state = state.get("agents", {}).get(row["recipient"]) or {}
491
- if str(agent_state.get("status") or "").lower() == "busy":
492
- event_log.write(
493
- "send.deferred_busy",
494
- message_id=row["message_id"],
495
- sender=row.get("sender"),
496
- recipient=row["recipient"],
497
- reason="recipient_busy",
498
- )
499
- continue
500
- result = _deliver_pending_message(workspace, state, row["message_id"], wait_visible=True, timeout=30.0)
501
- if result.get("ok"):
502
- delivered.append(row["message_id"])
503
- event_log.write("send.pending_delivered", message_id=row["message_id"], agent_id=row["recipient"])
504
- return delivered