@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,1243 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import hashlib
5
- import os
6
- import re
7
- import signal
8
- import shlex
9
- import shutil
10
- import subprocess
11
- import sys
12
- import time
13
- import copy
14
- import fcntl
15
- from concurrent.futures import ThreadPoolExecutor, as_completed
16
- from contextlib import contextmanager
17
- from datetime import datetime, timedelta, timezone
18
- from pathlib import Path
19
- from typing import Any
20
-
21
- from team_agent.errors import RuntimeError, ValidationError
22
- from team_agent.events import EventLog
23
- from team_agent.message_store import MessageStore
24
- from team_agent.paths import artifacts_dir, logs_dir, messages_dir, runtime_dir, team_workspace
25
- from team_agent.permissions import missing_tools, resolve_permissions
26
- from team_agent.profiles import (
27
- compact_profile_check,
28
- effective_model,
29
- prepare_agent_profile_launch,
30
- smoke_check_agent_profile,
31
- validate_agent_profile,
32
- )
33
- from team_agent.rust_core import core_binary, list_targets as core_list_targets, redact_text, render_message as core_render_message
34
- from team_agent.providers import (
35
- ResumeUnavailable,
36
- get_adapter,
37
- shell_command_for_agent,
38
- shell_fork_command_for_agent,
39
- shell_resume_command_for_agent,
40
- )
41
- from team_agent.display import (
42
- GHOSTTY_DISPLAY_BACKENDS,
43
- GHOSTTY_WORKSPACE_PANES_PER_WINDOW,
44
- close_ghostty_display as _close_ghostty_display,
45
- close_ghostty_workspace as _close_ghostty_workspace,
46
- close_ghostty_workspace_slot as _close_ghostty_workspace_slot,
47
- close_team_display_backends as _close_team_display_backends,
48
- ghostty_app_exists as _ghostty_app_exists,
49
- ghostty_attach_args as _ghostty_attach_args,
50
- ghostty_command as _ghostty_command,
51
- ghostty_display_session_name as _ghostty_display_session_name,
52
- ghostty_pids_by_title as _ghostty_pids_by_title,
53
- ghostty_workspace_aggregator_name as _ghostty_workspace_aggregator_name,
54
- ghostty_workspace_blocked as _ghostty_workspace_blocked,
55
- ghostty_workspace_pane_command as _ghostty_workspace_pane_command,
56
- ghostty_workspace_pane_title as _ghostty_workspace_pane_title,
57
- ghostty_workspace_partial_update_display as _ghostty_workspace_partial_update_display,
58
- ghostty_workspace_window_name as _ghostty_workspace_window_name,
59
- kill_ghostty_workspace_linked_sessions as _kill_ghostty_workspace_linked_sessions,
60
- open_ghostty_worker_window as _open_ghostty_worker_window,
61
- open_ghostty_workspace as _open_ghostty_workspace,
62
- open_ghostty_workspace_agent_display as _open_ghostty_workspace_agent_display,
63
- open_worker_displays as _open_worker_displays,
64
- prepare_ghostty_display_session as _prepare_ghostty_display_session,
65
- prepare_ghostty_workspace_aggregator as _prepare_ghostty_workspace_aggregator,
66
- prepare_ghostty_workspace_linked_sessions as _prepare_ghostty_workspace_linked_sessions,
67
- set_ghostty_workspace_pane_title as _set_ghostty_workspace_pane_title,
68
- )
69
- from team_agent.leader import (
70
- LEADER_OWNERSHIP_LOCK,
71
- attach_leader,
72
- attach_leader_to_state as _attach_leader_to_state,
73
- claim_leader as _legacy_claim_leader,
74
- leader_identity,
75
- leader_session_name as _leader_session_name,
76
- leader_start_plan,
77
- start_leader,
78
- )
79
- from team_agent.launch import (
80
- DANGEROUS_LEADER_FLAGS,
81
- attach_team_profile_dirs as _attach_team_profile_dirs,
82
- command_has_flag as _command_has_flag,
83
- compile_team_dir_spec as _compile_team_dir_spec,
84
- detect_inherited_dangerous_permissions as _detect_inherited_dangerous_permissions,
85
- effective_runtime_config as _effective_runtime_config,
86
- ensure_agent_start_requirements as _ensure_agent_start_requirements,
87
- init_workspace,
88
- is_team_doc_dir as _is_team_doc_dir,
89
- launch,
90
- process_ancestry as _process_ancestry,
91
- process_info as _process_info,
92
- requires_direct_leader_receiver as _requires_direct_leader_receiver,
93
- spec_team_dir as _spec_team_dir,
94
- tmux_session_conflict_error as _tmux_session_conflict_error,
95
- validate_file,
96
- )
97
- from team_agent.approvals import (
98
- APPROVAL_CHOICE_RE as _APPROVAL_CHOICE_RE,
99
- INTERNAL_MCP_APPROVAL_CHOICE,
100
- INTERNAL_MCP_AUTO_APPROVE_TOOLS,
101
- STARTUP_PROMPT_RUNTIME_CHECK_LIMIT,
102
- active_approval_choice_index as _active_approval_choice_index,
103
- active_approval_control_index as _active_approval_control_index,
104
- age_text as _age_text,
105
- agent_health_status as _agent_health_status,
106
- approval_choice_keys as _approval_choice_keys,
107
- approval_prompt_fingerprint as _approval_prompt_fingerprint,
108
- capture_has_approval_prompt as _capture_has_approval_prompt,
109
- capture_has_team_orchestrator_mcp_prompt as _capture_has_team_orchestrator_mcp_prompt,
110
- choose_internal_mcp_approval_choice as _choose_internal_mcp_approval_choice,
111
- current_task_for_agent as _current_task_for_agent,
112
- detect_provider_status as _detect_provider_status,
113
- extract_approval_choices as _extract_approval_choices,
114
- extract_approval_prompt as _extract_approval_prompt,
115
- extract_command_approval_subject as _extract_command_approval_subject,
116
- handle_internal_mcp_approval_prompt as _handle_internal_mcp_approval_prompt,
117
- handle_provider_runtime_prompts as _handle_provider_runtime_prompts,
118
- handle_provider_startup_prompts as _handle_provider_startup_prompts,
119
- is_approval_control_line as _is_approval_control_line,
120
- line_is_approval_choice as _line_is_approval_choice,
121
- refresh_agent_runtime_statuses as _refresh_agent_runtime_statuses,
122
- submit_internal_mcp_approval as _submit_internal_mcp_approval,
123
- sync_agent_health as _sync_agent_health,
124
- )
125
- from team_agent.diagnose import (
126
- compact_model_checks as _compact_model_checks,
127
- diagnose,
128
- doctor,
129
- ensure_profiles_for_roles as _ensure_profiles_for_roles,
130
- format_model_check_failures as _format_model_check_failures,
131
- format_profile_check_failures as _format_profile_check_failures,
132
- format_profile_smoke_failures as _format_profile_smoke_failures,
133
- model_checks_for_agents as _model_checks_for_agents,
134
- prepare_quick_start_team as _prepare_quick_start_team,
135
- preflight,
136
- preflight_blockers as _preflight_blockers,
137
- preflight_next_actions as _preflight_next_actions,
138
- profile_checks_for_agents as _profile_checks_for_agents,
139
- profile_smoke_checks_for_agents as _profile_smoke_checks_for_agents,
140
- quick_start as _legacy_quick_start,
141
- repair_state,
142
- settle,
143
- start,
144
- wait_ready,
145
- )
146
- from team_agent.coordinator import (
147
- COORDINATOR_PROTOCOL_VERSION,
148
- coordinator_health,
149
- coordinator_log_path,
150
- coordinator_meta_path,
151
- coordinator_metadata_ok as _coordinator_metadata_ok,
152
- coordinator_pid_path,
153
- coordinator_tick,
154
- message_store_schema_health as _message_store_schema_health,
155
- pid_is_running as _pid_is_running,
156
- read_coordinator_metadata as _read_coordinator_metadata,
157
- start_coordinator,
158
- stop_coordinator,
159
- write_coordinator_metadata,
160
- )
161
- from team_agent.restart import (
162
- format_restart_candidates as _format_restart_candidates,
163
- quick_start_existing_context as _quick_start_existing_context,
164
- restart,
165
- restart_candidate_from_state as _restart_candidate_from_state,
166
- restart_candidates as _restart_candidates,
167
- rollback_restart_session as _rollback_restart_session,
168
- safe_snapshot_name as _safe_snapshot_name,
169
- save_team_runtime_snapshot as _save_team_runtime_snapshot,
170
- select_restart_state as _select_restart_state,
171
- state_has_restart_context as _state_has_restart_context,
172
- state_team_name as _state_team_name,
173
- team_runtime_snapshot_dir as _team_runtime_snapshot_dir,
174
- load_snapshot_state as _load_snapshot_state,
175
- )
176
- from team_agent.routing import route_task
177
- from team_agent.sessions import (
178
- attach_profile_resume_root as _attach_profile_resume_root,
179
- capture_agent_session as _capture_agent_session,
180
- capture_missing_sessions as _capture_missing_sessions,
181
- clear_session_capture_fields as _clear_session_capture_fields,
182
- copy_session_metadata as _copy_session_metadata,
183
- prepare_resume_state as _prepare_resume_state,
184
- recover_resume_session_from_events as _recover_resume_session_from_events,
185
- sessions_overview as sessions,
186
- )
187
- from team_agent.status import (
188
- APPROVAL_SCAN_LINES,
189
- PEEK_MAX_LINES,
190
- PEEK_MAX_MATCHES,
191
- PEEK_SEARCH_SCAN_LINES,
192
- PENDING_DELIVERY_STATUSES,
193
- STATUS_EVENT_LIMIT,
194
- STATUS_TEXT_LIMIT,
195
- approvals,
196
- compact_agent_state as _compact_agent_state,
197
- compact_event as _compact_event,
198
- compact_mapping as _compact_mapping,
199
- compact_status as _compact_status,
200
- compact_task as _compact_task,
201
- compact_value as _compact_value,
202
- format_approvals,
203
- format_inbox,
204
- format_search_matches as _format_search_matches,
205
- format_status,
206
- inbox,
207
- latest_result_summaries as _latest_result_summaries,
208
- peek,
209
- queued_message_statuses as _queued_message_statuses,
210
- result_summary_from_row as _result_summary_from_row,
211
- search_lines as _search_lines,
212
- status,
213
- validate_line_count as _validate_line_count,
214
- )
215
- from team_agent.simple_yaml import dumps
216
- from team_agent.spec import load_spec, validate_result_envelope, validate_spec, workspace_from_spec
217
- from team_agent.state import (
218
- SESSION_CAPTURE_FIELDS,
219
- SESSION_STATE_FIELDS,
220
- check_team_owner,
221
- load_runtime_state,
222
- normalize_agent_session_state,
223
- populate_team_owner_from_env,
224
- runtime_state_path,
225
- save_runtime_state,
226
- save_team_scoped_state,
227
- select_runtime_state,
228
- team_state_key,
229
- write_spec,
230
- write_team_state,
231
- )
232
-
233
- # Import-time assertions: lifecycle/start.py + messaging/deps.py keep an
234
- # existence check on these runtime symbols. The aliases above pin them so
235
- # accidental rename in team_agent.sessions trips a loud ImportError here.
236
- assert callable(_attach_profile_resume_root)
237
- assert callable(_capture_agent_session)
238
- assert callable(_capture_missing_sessions)
239
- assert callable(_clear_session_capture_fields)
240
- assert callable(_copy_session_metadata)
241
- assert callable(_prepare_resume_state)
242
- assert callable(_recover_resume_session_from_events)
243
- assert callable(sessions)
244
-
245
- # Display lane re-exports: lifecycle/start.py + lifecycle/operations.py +
246
- # existing tests assume team_agent.runtime exposes these names. Each alias
247
- # fails at import if team_agent.display drops the symbol, preventing the
248
- # 0a36ad9-style "imports in HEAD, body missing on disk" hazard.
249
- assert callable(_close_ghostty_display)
250
- assert callable(_close_ghostty_workspace)
251
- assert callable(_close_ghostty_workspace_slot)
252
- assert callable(_ghostty_app_exists)
253
- assert callable(_ghostty_attach_args)
254
- assert callable(_ghostty_command)
255
- assert callable(_ghostty_display_session_name)
256
- assert callable(_ghostty_pids_by_title)
257
- assert callable(_ghostty_workspace_aggregator_name)
258
- assert callable(_ghostty_workspace_blocked)
259
- assert callable(_ghostty_workspace_pane_command)
260
- assert callable(_ghostty_workspace_pane_title)
261
- assert callable(_ghostty_workspace_partial_update_display)
262
- assert callable(_ghostty_workspace_window_name)
263
- assert callable(_kill_ghostty_workspace_linked_sessions)
264
- assert callable(_open_ghostty_worker_window)
265
- assert callable(_open_ghostty_workspace)
266
- assert callable(_open_ghostty_workspace_agent_display)
267
- assert callable(_open_worker_displays)
268
- assert callable(_prepare_ghostty_display_session)
269
- assert callable(_prepare_ghostty_workspace_aggregator)
270
- assert callable(_prepare_ghostty_workspace_linked_sessions)
271
- assert callable(_set_ghostty_workspace_pane_title)
272
- assert isinstance(GHOSTTY_WORKSPACE_PANES_PER_WINDOW, int)
273
-
274
- # Status lane re-exports: the runtime.* alias for each status helper keeps
275
- # CLI handlers and existing tests stable; constants travel through runtime
276
- # so callers that read runtime.APPROVAL_SCAN_LINES (or the others) still
277
- # resolve. Drift in team_agent.status fails loudly here.
278
- assert callable(approvals)
279
- assert callable(format_approvals)
280
- assert callable(format_inbox)
281
- assert callable(format_status)
282
- assert callable(inbox)
283
- assert callable(peek)
284
- assert callable(status)
285
- assert callable(_compact_agent_state)
286
- assert callable(_compact_event)
287
- assert callable(_compact_mapping)
288
- assert callable(_compact_status)
289
- assert callable(_compact_task)
290
- assert callable(_compact_value)
291
- assert callable(_format_search_matches)
292
- assert callable(_latest_result_summaries)
293
- assert callable(_queued_message_statuses)
294
- assert callable(_result_summary_from_row)
295
- assert callable(_search_lines)
296
- assert callable(_validate_line_count)
297
- assert isinstance(APPROVAL_SCAN_LINES, int)
298
- assert isinstance(PEEK_MAX_LINES, int)
299
- assert isinstance(PEEK_MAX_MATCHES, int)
300
- assert isinstance(PEEK_SEARCH_SCAN_LINES, int)
301
- assert isinstance(STATUS_EVENT_LIMIT, int)
302
- assert isinstance(STATUS_TEXT_LIMIT, int)
303
- assert isinstance(PENDING_DELIVERY_STATUSES, set)
304
-
305
- # Restart lane re-exports: lifecycle/agents.py + lifecycle/operations.py
306
- # call runtime._save_team_runtime_snapshot, existing tests target
307
- # runtime.restart, runtime._select_restart_state, runtime._rollback_restart_session,
308
- # runtime._safe_snapshot_name, etc. The aliases above plus these assertions
309
- # catch any rename or removal in team_agent.restart at import time.
310
- assert callable(restart)
311
- assert callable(_rollback_restart_session)
312
- assert callable(_save_team_runtime_snapshot)
313
- assert callable(_restart_candidates)
314
- assert callable(_restart_candidate_from_state)
315
- assert callable(_state_has_restart_context)
316
- assert callable(_select_restart_state)
317
- assert callable(_format_restart_candidates)
318
- assert callable(_quick_start_existing_context)
319
- assert callable(_safe_snapshot_name)
320
- assert callable(_state_team_name)
321
- assert callable(_team_runtime_snapshot_dir)
322
- assert callable(_load_snapshot_state)
323
-
324
- # Coordinator lane re-exports keep runtime.coordinator_health,
325
- # runtime.start_coordinator, runtime.coordinator_pid_path, etc. resolving
326
- # for the daemon entry (team_agent.coordinator.__main__) and the existing
327
- # tests. One representative identity smoke + one lightweight loop catch
328
- # any rename or drop in team_agent.coordinator at import time without the
329
- # over-coupled per-symbol assertIs sweep. Per-helper behavior is verified
330
- # by tests/test_coordinator_boundary.py and the broader suite.
331
- import team_agent.coordinator as _coordinator_pkg
332
- assert start_coordinator is _coordinator_pkg.start_coordinator
333
- for _name in (
334
- "COORDINATOR_PROTOCOL_VERSION",
335
- "coordinator_health",
336
- "coordinator_log_path",
337
- "coordinator_meta_path",
338
- "coordinator_metadata_ok",
339
- "coordinator_pid_path",
340
- "coordinator_tick",
341
- "message_store_schema_health",
342
- "pid_is_running",
343
- "read_coordinator_metadata",
344
- "start_coordinator",
345
- "stop_coordinator",
346
- "write_coordinator_metadata",
347
- ):
348
- assert hasattr(_coordinator_pkg, _name), f"team_agent.coordinator missing {_name}"
349
- del _coordinator_pkg, _name
350
-
351
- # Diagnose lane re-exports keep runtime.diagnose, runtime.doctor,
352
- # runtime.preflight, runtime.start, runtime.quick_start, runtime.wait_ready,
353
- # runtime.settle, runtime.repair_state plus the private helper aliases
354
- # resolving for CLI handlers and existing tests. Same identity-smoke +
355
- # lightweight-loop convention as coordinator.
356
- import team_agent.diagnose as _diagnose_pkg
357
- assert diagnose is _diagnose_pkg.diagnose
358
- for _name in (
359
- "compact_model_checks",
360
- "diagnose",
361
- "doctor",
362
- "ensure_profiles_for_roles",
363
- "format_model_check_failures",
364
- "format_profile_check_failures",
365
- "format_profile_smoke_failures",
366
- "model_checks_for_agents",
367
- "prepare_quick_start_team",
368
- "preflight",
369
- "preflight_blockers",
370
- "preflight_next_actions",
371
- "profile_checks_for_agents",
372
- "profile_smoke_checks_for_agents",
373
- "quick_start",
374
- "repair_state",
375
- "settle",
376
- "start",
377
- "wait_ready",
378
- ):
379
- assert hasattr(_diagnose_pkg, _name), f"team_agent.diagnose missing {_name}"
380
- del _diagnose_pkg, _name
381
-
382
- # Approvals lane re-exports keep runtime.<symbol> resolving for the
383
- # coordinator-tick + status-refresh + prompt-detection helpers that
384
- # messaging/lifecycle/tests look up via the runtime alias surface. Same
385
- # calibrated convention as coordinator and diagnose: one identity smoke
386
- # + one lightweight loop over the full public name set.
387
- import team_agent.approvals as _approvals_pkg
388
- assert _refresh_agent_runtime_statuses is _approvals_pkg.refresh_agent_runtime_statuses
389
- for _name in (
390
- "APPROVAL_CHOICE_RE",
391
- "INTERNAL_MCP_APPROVAL_CHOICE",
392
- "INTERNAL_MCP_AUTO_APPROVE_TOOLS",
393
- "STARTUP_PROMPT_RUNTIME_CHECK_LIMIT",
394
- "active_approval_choice_index",
395
- "active_approval_control_index",
396
- "age_text",
397
- "agent_health_status",
398
- "approval_choice_keys",
399
- "approval_prompt_fingerprint",
400
- "capture_has_approval_prompt",
401
- "capture_has_team_orchestrator_mcp_prompt",
402
- "choose_internal_mcp_approval_choice",
403
- "current_task_for_agent",
404
- "detect_provider_status",
405
- "extract_approval_choices",
406
- "extract_approval_prompt",
407
- "extract_command_approval_subject",
408
- "handle_internal_mcp_approval_prompt",
409
- "handle_provider_runtime_prompts",
410
- "handle_provider_startup_prompts",
411
- "is_approval_control_line",
412
- "line_is_approval_choice",
413
- "refresh_agent_runtime_statuses",
414
- "submit_internal_mcp_approval",
415
- "sync_agent_health",
416
- ):
417
- assert hasattr(_approvals_pkg, _name), f"team_agent.approvals missing {_name}"
418
- del _approvals_pkg, _name
419
-
420
- # Launch lane re-exports keep runtime.launch, runtime.init_workspace,
421
- # runtime.validate_file plus the private bootstrap/config helpers
422
- # resolving for CLI handlers and tests. Same calibrated convention.
423
- import team_agent.launch as _launch_pkg
424
- assert launch is _launch_pkg.launch
425
- for _name in (
426
- "DANGEROUS_LEADER_FLAGS",
427
- "attach_team_profile_dirs",
428
- "command_has_flag",
429
- "compile_team_dir_spec",
430
- "detect_inherited_dangerous_permissions",
431
- "effective_runtime_config",
432
- "ensure_agent_start_requirements",
433
- "init_workspace",
434
- "is_team_doc_dir",
435
- "launch",
436
- "process_ancestry",
437
- "process_info",
438
- "requires_direct_leader_receiver",
439
- "spec_team_dir",
440
- "tmux_session_conflict_error",
441
- "validate_file",
442
- ):
443
- assert hasattr(_launch_pkg, _name), f"team_agent.launch missing {_name}"
444
- del _launch_pkg, _name
445
-
446
- # Leader lane re-exports keep runtime leader helpers resolving for CLI handlers and tests.
447
- import team_agent.leader as _leader_pkg
448
- assert attach_leader is _leader_pkg.attach_leader
449
- for _name in ("attach_leader", "attach_leader_to_state", "claim_leader", "leader_identity", "leader_session_name", "leader_start_plan", "start_leader"):
450
- assert hasattr(_leader_pkg, _name), f"team_agent.leader missing {_name}"
451
- del _leader_pkg, _name
452
- from team_agent.task_graph import ready_tasks, update_task_status
453
- from team_agent.task_graph import TASK_STATUSES
454
-
455
-
456
- TMUX_PANE_FORMAT = (
457
- "#{pane_id}\t#{session_name}\t#{window_index}\t#{window_name}\t"
458
- "#{pane_index}\t#{pane_tty}\t#{pane_current_command}\t#{pane_active}\t"
459
- "#{pane_current_path}\t#{session_attached}\t#{pane_in_mode}"
460
- )
461
- HEALTH_STATUSES = {"RUNNING", "IDLE", "AWAITING_APPROVAL", "BLOCKED", "ERROR", "DONE"}
462
- DELIVERY_CAPTURE_LINES = 40
463
- SUBMITTED_DELIVERY_STATUSES = {"injected", "visible", "submitted", "submitted_unverified", "delivered", "acknowledged"}
464
- TMUX_STDIN_BUFFER_THRESHOLD = 16 * 1024
465
- TMUX_PASTE_MIN_READY_TIMEOUT = 1.5
466
- TMUX_PASTE_MAX_READY_TIMEOUT = 30.0
467
- TMUX_PASTE_BYTES_PER_SECOND = 25_000
468
- TMUX_SUBMIT_MIN_SETTLE_TIMEOUT = 0.35
469
- TMUX_SUBMIT_MAX_SETTLE_TIMEOUT = 15.0
470
- TMUX_SUBMIT_BYTES_PER_SECOND = 50_000
471
- PASTED_CONTENT_PROMPT_RE = re.compile(
472
- r"\[\s*Pasted\s+(?:Content\s+\d+\s+chars?|text\s+#\d+\s+\+\s*\d+\s+lines?)\s*\]",
473
- re.IGNORECASE,
474
- )
475
-
476
- def run_cmd(args: list[str], timeout: int = 20) -> subprocess.CompletedProcess[str]:
477
- return subprocess.run(args, text=True, capture_output=True, timeout=timeout, check=False)
478
-
479
-
480
- def ensure_workspace_dirs(workspace: Path) -> None:
481
- for path in [runtime_dir(workspace), logs_dir(workspace), artifacts_dir(workspace), messages_dir(workspace)]:
482
- path.mkdir(parents=True, exist_ok=True)
483
-
484
-
485
- def shutdown(workspace: Path, keep_logs: bool = True, team: str | None = None) -> dict[str, Any]:
486
- from team_agent.state import resolve_team_scoped_state
487
- state, refusal = resolve_team_scoped_state(workspace, team)
488
- if refusal:
489
- return refusal
490
- gate = check_team_owner(state)
491
- if gate:
492
- return gate
493
- session_name = state.get("session_name")
494
- resolved_team_id = (
495
- team
496
- or state.get("active_team_key")
497
- or (team_state_key(state) if state.get("session_name") else None)
498
- )
499
- event_log = EventLog(workspace)
500
- captured: list[str] = []
501
- closed_displays: set[str] = set()
502
- missing_before = [agent_id for agent_id, agent_state in state.get("agents", {}).items() if not agent_state.get("session_id")]
503
- fallback_captured = _capture_missing_sessions(workspace, state, event_log, timeout_s=2.0, log_miss=False)
504
- event_log.write("shutdown.session_capture_checked", missing_before=missing_before, captured=fallback_captured)
505
- for agent_id, agent_state in state.get("agents", {}).items():
506
- if not agent_state.get("session_id"):
507
- event_log.write(
508
- "shutdown.session_capture_missed",
509
- agent_id=agent_id,
510
- provider=agent_state.get("provider"),
511
- spawn_cwd=agent_state.get("spawn_cwd"),
512
- )
513
- coordinator = stop_coordinator(workspace)
514
- if session_name and _tmux_session_exists(session_name):
515
- leader_receiver = state.get("leader_receiver", {})
516
- leader_window = leader_receiver.get("window") if leader_receiver.get("mode") != "direct_tmux" else None
517
- if leader_window and _tmux_window_exists(session_name, leader_window):
518
- log_path = logs_dir(workspace) / f"{leader_window}.scrollback"
519
- proc = run_cmd(["tmux", "capture-pane", "-p", "-S", "-", "-t", f"{session_name}:{leader_window}"], timeout=10)
520
- if proc.returncode == 0:
521
- log_path.write_text(proc.stdout, encoding="utf-8")
522
- captured.append(str(log_path))
523
- for agent_id, agent_state in state.get("agents", {}).items():
524
- window = agent_state.get("window", agent_id)
525
- log_path = logs_dir(workspace) / f"{agent_id}.scrollback"
526
- proc = run_cmd(["tmux", "capture-pane", "-p", "-S", "-", "-t", f"{session_name}:{window}"], timeout=10)
527
- if proc.returncode == 0:
528
- log_path.write_text(proc.stdout, encoding="utf-8")
529
- captured.append(str(log_path))
530
- display_cleanup = _close_team_display_backends(state, event_log)
531
- for agent_id, agent_state in state.get("agents", {}).items():
532
- _close_ghostty_display(agent_id, agent_state, event_log)
533
- closed_displays.add(agent_id)
534
- proc = run_cmd(["tmux", "kill-session", "-t", session_name], timeout=10)
535
- if proc.returncode != 0:
536
- if "can't find session" in proc.stderr:
537
- event_log.write("shutdown.idempotent", session=session_name, reason="session disappeared before kill")
538
- else:
539
- raise RuntimeError(f"tmux kill-session failed: {proc.stderr.strip()}")
540
- else:
541
- event_log.write("shutdown.kill_session", session=session_name, keep_logs=keep_logs, captured=captured)
542
- else:
543
- event_log.write("shutdown.idempotent", session=session_name, reason="session missing")
544
- display_cleanup = _close_team_display_backends(state, event_log)
545
- for agent_id, agent_state in state.get("agents", {}).items():
546
- if agent_id not in closed_displays:
547
- _close_ghostty_display(agent_id, agent_state, event_log)
548
- mcp_path = Path(agent_state["mcp_config"]) if agent_state.get("mcp_config") else None
549
- try:
550
- get_adapter(agent_state["provider"]).cleanup_mcp(workspace, agent_id, mcp_path)
551
- event_log.write(
552
- "shutdown.mcp_cleanup",
553
- agent_id=agent_id,
554
- provider=agent_state.get("provider"),
555
- mcp_config=str(mcp_path) if mcp_path else None,
556
- )
557
- except Exception as exc:
558
- event_log.write(
559
- "shutdown.mcp_cleanup_failed",
560
- agent_id=agent_id,
561
- provider=agent_state.get("provider"),
562
- mcp_config=str(mcp_path) if mcp_path else None,
563
- error=str(exc),
564
- )
565
- if agent_state.get("status") != "paused":
566
- agent_state["status"] = "stopped"
567
- save_team_scoped_state(workspace, state)
568
- _save_team_runtime_snapshot(workspace, state)
569
- # 0.2.6 Family B (C10/C11/C12): atomically unregister the team and
570
- # archive its runtime snapshot directory. Both branches of --keep-logs
571
- # still drop the team from state.teams; logs survive inside the
572
- # archived directory.
573
- archive_path, teams_remaining, new_active = _commit_shutdown_cleanup(
574
- workspace, str(resolved_team_id or ""), session_name, event_log
575
- )
576
- result = {
577
- "ok": True,
578
- "session_name": session_name,
579
- "team": resolved_team_id,
580
- "logs": captured,
581
- "coordinator": coordinator,
582
- "archive_path": archive_path,
583
- "teams_remaining": teams_remaining,
584
- "new_active_team_key": new_active,
585
- "cleanup_mode": "synchronous_committed",
586
- }
587
- removed_orphans = (display_cleanup or {}).get("orphans_removed") or {}
588
- remaining_orphans = (display_cleanup or {}).get("orphans_detected") or {}
589
- if removed_orphans:
590
- result["orphans_detected"] = removed_orphans
591
- result["warnings"] = ["Adaptive display tmux objects were found and removed during shutdown cleanup."]
592
- if remaining_orphans:
593
- result["cleanup_mode"] = "synchronous_with_orphans"
594
- result["orphans_detected"] = remaining_orphans
595
- result["warning"] = "Adaptive display tmux objects remain after shutdown cleanup."
596
- event_log.write(
597
- "shutdown.orphans_detected",
598
- warning=result["warning"],
599
- message=result["warning"],
600
- orphans_detected=remaining_orphans,
601
- adaptive_display_sessions=remaining_orphans.get("adaptive_display_sessions", []),
602
- adaptive_overview_windows=remaining_orphans.get("adaptive_overview_windows", []),
603
- )
604
- return result
605
-
606
-
607
- def _commit_shutdown_cleanup(
608
- workspace: Path,
609
- team_key: str,
610
- session_name: str | None,
611
- event_log: EventLog,
612
- ) -> tuple[str | None, list[str], str | None]:
613
- import shutil as _shutil
614
- from datetime import datetime as _dt, timezone as _tz
615
- workspace_state = load_runtime_state(workspace)
616
- teams = workspace_state.get("teams") if isinstance(workspace_state.get("teams"), dict) else {}
617
- if team_key and team_key in teams:
618
- teams.pop(team_key, None)
619
- if workspace_state.get("active_team_key") == team_key:
620
- workspace_state["active_team_key"] = None
621
- workspace_state["teams"] = teams
622
- archive_dest: Path | None = None
623
- if session_name:
624
- runtime_teams_dir = runtime_dir(workspace) / "teams"
625
- from team_agent.restart.snapshot import safe_snapshot_name as _safe
626
- snapshot_name = _safe(str(session_name))
627
- snapshot_dir = runtime_teams_dir / snapshot_name
628
- if snapshot_dir.exists():
629
- ts = _dt.now(_tz.utc).strftime("%Y%m%dT%H%M%SZ")
630
- archive_dest = runtime_teams_dir / f".archived-{snapshot_name}-{ts}"
631
- try:
632
- _shutil.move(str(snapshot_dir), str(archive_dest))
633
- except OSError as exc:
634
- event_log.write(
635
- "team.shutdown_blocked",
636
- reason="archive_move_failed",
637
- team_key=team_key,
638
- error=str(exc),
639
- hint="check filesystem permissions on .team/runtime/teams and rerun shutdown",
640
- )
641
- return None, sorted(teams.keys()), workspace_state.get("active_team_key")
642
- save_runtime_state(workspace, workspace_state)
643
- archive_path_str = str(archive_dest) if archive_dest is not None else None
644
- new_active = workspace_state.get("active_team_key")
645
- event_log.write(
646
- "team.shutdown_completed",
647
- team_key=team_key,
648
- archive_path=archive_path_str,
649
- teams_remaining=sorted(teams.keys()),
650
- new_active_team_key=new_active,
651
- )
652
- return archive_path_str, sorted(teams.keys()), new_active
653
-
654
-
655
-
656
- def remove_agent(
657
- workspace: Path,
658
- agent_id: str,
659
- *,
660
- from_spec: bool = False,
661
- confirm: bool = False,
662
- force: bool = False,
663
- team: str | None = None,
664
- ) -> dict[str, Any]:
665
- from team_agent.lifecycle.agents import remove_agent as lifecycle_remove_agent
666
-
667
- with _runtime_lock(workspace, "remove-agent"):
668
- return lifecycle_remove_agent(workspace, agent_id, from_spec=from_spec, confirm=confirm, force=force, team=team)
669
-
670
-
671
- def acknowledge_idle(workspace: Path, agent_id: str | None = None, *, team: str | None = None) -> dict[str, Any]:
672
- with _runtime_lock(workspace, "acknowledge-idle"):
673
- try:
674
- state = select_runtime_state(workspace, team)
675
- except Exception as exc:
676
- return {"ok": False, "status": "refused", "reason": "team_target_unresolved", "team": team, "error": str(exc)}
677
- gate = check_team_owner(state)
678
- if gate:
679
- return gate
680
- now_dt = datetime.now(timezone.utc); now = now_dt.isoformat()
681
- ttl_seconds = 1800
682
- expires_at = (now_dt + timedelta(seconds=ttl_seconds)).isoformat()
683
- owner_team_id = team_state_key(state); coordinator = state.setdefault("coordinator", {})
684
- coordinator.setdefault("idle_acknowledged", {})[owner_team_id] = {"acknowledged_at": now, "expires_at": expires_at, "ttl_seconds": ttl_seconds}
685
- team_suppressions = coordinator.setdefault("suppressed_idle_alerts", {}).setdefault(owner_team_id, {})
686
- entry = {"suppressed_at": now, "suppressed_by": "manual_acknowledge", "manual_acknowledge": True, "expires_at": expires_at, "ttl_seconds": ttl_seconds}
687
- for worker_id in state.get("agents", {}):
688
- team_suppressions.setdefault(worker_id, {})["idle_fallback"] = dict(entry)
689
- save_team_scoped_state(workspace, state)
690
- EventLog(workspace).write("coordinator.idle_acknowledged", agent_id=agent_id, team=owner_team_id, acknowledged_at=now, expires_at=expires_at, ttl_seconds=ttl_seconds)
691
- return {"ok": True, "team": owner_team_id, "agent_id": agent_id, "acknowledged_at": now, "expires_at": expires_at, "ttl_seconds": ttl_seconds}
692
-
693
- _OWNER_IDENTITY_FIELDS = (
694
- "pane_id",
695
- "leader_session_uuid",
696
- "machine_fingerprint",
697
- "provider",
698
- "os_user",
699
- )
700
-
701
-
702
- def _owner_identity_matches(existing: dict[str, Any], candidate: dict[str, Any]) -> bool:
703
- for field in _OWNER_IDENTITY_FIELDS:
704
- if str(existing.get(field) or "") != str(candidate.get(field) or ""):
705
- return False
706
- return True
707
-
708
-
709
- def _resolve_owner_team_id(state: dict[str, Any], team: str | None) -> str | None:
710
- if team:
711
- return str(team)
712
- active = state.get("active_team_key")
713
- if active:
714
- return str(active)
715
- teams = state.get("teams") or {}
716
- if isinstance(teams, dict) and len(teams) == 1:
717
- return next(iter(teams))
718
- return None
719
-
720
-
721
- def takeover(workspace: Path, team: str | None = None, confirm: bool = False) -> dict[str, Any]:
722
- """0.2.6 Family A: positive-source ownership rebind.
723
-
724
- Identity is sourced exclusively from ``bind_owner_from_caller_pane``
725
- (``$TMUX_PANE`` + one targeted ``tmux display-message``). The new
726
- owner record force-writes every identity field into
727
- ``state.teams[<team_id>].team_owner``; old fields are not merged,
728
- migrated, or setdefaulted. Idempotent: re-running with the same
729
- caller identity returns success without mutating state.
730
- """
731
- if not confirm:
732
- return {
733
- "ok": False,
734
- "status": "refused",
735
- "reason": "confirm_required",
736
- "action": "rerun with --confirm to claim ownership of this team",
737
- }
738
- from team_agent.leader_binding import (
739
- bind_owner_from_caller_pane,
740
- emit_owner_bound_event,
741
- )
742
- with _runtime_lock(workspace, LEADER_OWNERSHIP_LOCK):
743
- state = load_runtime_state(workspace)
744
- team_id = _resolve_owner_team_id(state, team)
745
- if not team_id:
746
- return {
747
- "ok": False,
748
- "status": "refused",
749
- "reason": "team_target_unresolved",
750
- "team": team,
751
- "hint": "pass --team <name> or run quick-start first to register an active team.",
752
- }
753
- bind = bind_owner_from_caller_pane(workspace, team_id)
754
- if not bind.get("ok"):
755
- return {"ok": False, "status": "refused", **bind}
756
- new_owner = bind["owner"]
757
- teams = state.setdefault("teams", {})
758
- team_entry = teams.get(team_id) or {}
759
- existing_owner = team_entry.get("team_owner") if isinstance(team_entry.get("team_owner"), dict) else {}
760
- if existing_owner and _owner_identity_matches(existing_owner, new_owner):
761
- return {
762
- "ok": True,
763
- "status": "claimed",
764
- "team": team_id,
765
- "team_owner": existing_owner,
766
- "idempotent": True,
767
- }
768
- team_entry["team_owner"] = new_owner
769
- teams[team_id] = team_entry
770
- if team_state_key(state) == team_id:
771
- state["team_owner"] = new_owner
772
- from team_agent.leader import _write_lease_dual_state
773
- _write_lease_dual_state(workspace, state)
774
- emit_owner_bound_event(
775
- workspace,
776
- caller_pane_id=bind.get("caller_pane_id", ""),
777
- caller_current_command=bind.get("caller_current_command", ""),
778
- derived_leader_session_uuid=new_owner["leader_session_uuid"],
779
- team_id=team_id,
780
- old_leader_session_uuid=str(existing_owner.get("leader_session_uuid") or ""),
781
- )
782
- return {
783
- "ok": True,
784
- "status": "claimed",
785
- "team": team_id,
786
- "team_owner": new_owner,
787
- "previous_owner": existing_owner or None,
788
- }
789
-
790
-
791
- def claim_leader(workspace: Path, team: str | None = None, confirm: bool = False) -> dict[str, Any]:
792
- """0.2.6 Family A: positive-source claim-leader.
793
-
794
- Calls :func:`bind_owner_from_caller_pane` to confirm the caller is in
795
- a leader-shaped tmux pane, then delegates to the legacy multi-
796
- candidate lease arbiter for residual handling. The bind step is the
797
- only source of caller identity; the legacy lease path no longer
798
- re-derives it.
799
- """
800
- from team_agent.leader_binding import bind_owner_from_caller_pane
801
- state = load_runtime_state(workspace)
802
- team_id = _resolve_owner_team_id(state, team) or team_state_key(state)
803
- bind = bind_owner_from_caller_pane(workspace, team_id)
804
- if not bind.get("ok"):
805
- return {"ok": False, "status": "refused", **bind}
806
- return _legacy_claim_leader(workspace, team=team, confirm=confirm)
807
-
808
-
809
- def quick_start(
810
- agents_dir: Path,
811
- name: str | None = None,
812
- yes: bool = False,
813
- fresh: bool = False,
814
- team_id: str | None = None,
815
- ) -> dict[str, Any]:
816
- """0.2.6 Family A: positive-source quick-start.
817
-
818
- The caller-pane shape gate is owned by
819
- :func:`bind_owner_from_caller_pane`. Quick-start binds the caller
820
- pane BEFORE any team setup runs; ``$TMUX_PANE`` missing or the
821
- caller pane not running a leader host short-circuits to a refusal
822
- (no fallback to legacy reverse-scan). On success, the legacy
823
- bootstrap brings up the workspace and the bind-derived
824
- ``team_owner`` is force-written into
825
- ``state.teams[team_id].team_owner`` so the runtime owner identity
826
- matches the caller pane verbatim.
827
- """
828
- from team_agent.leader_binding import (
829
- bind_owner_from_caller_pane,
830
- emit_owner_bound_event,
831
- )
832
- from team_agent.diagnose.quick_start import prepare_quick_start_team
833
-
834
- # Pre-resolve team_dir + workspace so the caller-pane bind can write
835
- # its audit event before any worker is spawned. ``prepare_quick_start_team``
836
- # is idempotent (mkdir + shutil.copy2 of role docs) and used inside
837
- # ``_legacy_quick_start`` as the very first step anyway.
838
- team_dir = prepare_quick_start_team(
839
- Path(agents_dir).resolve(), Path.cwd().resolve(), name, team_id=team_id
840
- )
841
- workspace = team_workspace(team_dir)
842
- ensure_workspace_dirs(workspace)
843
- # Spark MED 1 (b1b17b1 review): the on-disk team_dir already passed
844
- # through ``_safe_snapshot_name``; reusing ``team_dir.name`` here
845
- # keeps the on-disk path and the state.teams key aligned. Using the
846
- # raw ``team_id`` would have split the two writes whenever the
847
- # caller-supplied id contained spaces or shell-unsafe characters.
848
- resolved_team_id = team_dir.name or "current"
849
- bind = bind_owner_from_caller_pane(workspace, resolved_team_id)
850
- if not bind.get("ok"):
851
- return {"ok": False, "status": "refused", **bind}
852
- new_owner = bind["owner"]
853
- result = _legacy_quick_start(
854
- Path(agents_dir).resolve(), name=name, yes=yes, fresh=fresh, team_id=team_id
855
- )
856
- # Spark MED 2 (b1b17b1 review): only commit the owner force-write
857
- # and emit ``owner.bound_from_caller_pane`` when the legacy bootstrap
858
- # actually succeeded. Otherwise pass the refusal envelope back
859
- # verbatim — ``existing_runtime_state`` / ``preflight`` failures
860
- # must not leave a "team owner claimed" side effect behind.
861
- if not result.get("ok"):
862
- return result
863
- state = load_runtime_state(workspace)
864
- teams = state.setdefault("teams", {})
865
- team_entry = teams.get(resolved_team_id) or {}
866
- existing_owner = (
867
- team_entry.get("team_owner")
868
- if isinstance(team_entry.get("team_owner"), dict)
869
- else {}
870
- )
871
- if not (existing_owner and _owner_identity_matches(existing_owner, new_owner)):
872
- team_entry["team_owner"] = new_owner
873
- teams[resolved_team_id] = team_entry
874
- if not state.get("active_team_key"):
875
- state["active_team_key"] = resolved_team_id
876
- from team_agent.leader import _write_lease_dual_state
877
- _write_lease_dual_state(workspace, state)
878
- emit_owner_bound_event(
879
- workspace,
880
- caller_pane_id=bind.get("caller_pane_id", ""),
881
- caller_current_command=bind.get("caller_current_command", ""),
882
- derived_leader_session_uuid=new_owner["leader_session_uuid"],
883
- team_id=resolved_team_id,
884
- old_leader_session_uuid=str(existing_owner.get("leader_session_uuid") or ""),
885
- )
886
- return result
887
-
888
-
889
- def _running_agent_state(workspace: Path, agent: dict[str, Any], previous: dict[str, Any]) -> dict[str, Any]:
890
- agent_state = dict(previous)
891
- agent_state.update(
892
- {
893
- "status": "running",
894
- "provider": agent["provider"],
895
- "agent_id": agent["id"],
896
- "model": agent.get("model"),
897
- "auth_mode": agent.get("auth_mode"),
898
- "profile": agent.get("profile"),
899
- "window": agent["id"],
900
- "permissions": resolve_permissions(agent),
901
- "spawn_cwd": str(workspace),
902
- }
903
- )
904
- return agent_state
905
-
906
-
907
- def _handle_startup_prompts_and_verify_window(
908
- adapter: Any,
909
- event_log: EventLog,
910
- event_prefix: str,
911
- agent_id: str,
912
- provider: str,
913
- session_name: str,
914
- start_mode: str,
915
- ) -> bool:
916
- handled_prompts = adapter.handle_startup_prompts(session_name, agent_id, checks=20, sleep_s=0.5)
917
- for prompt_event in handled_prompts:
918
- event_log.write(f"{event_prefix}.startup_prompt_handled", agent_id=agent_id, provider=provider, **prompt_event)
919
- deadline = time.monotonic() + 1.0
920
- saw_window = False
921
- while True:
922
- if _tmux_window_exists(session_name, agent_id):
923
- saw_window = True
924
- if time.monotonic() >= deadline:
925
- return True
926
- elif saw_window or time.monotonic() >= deadline:
927
- break
928
- time.sleep(0.2)
929
- event_log.write(
930
- f"{event_prefix}.window_missing_after_start",
931
- agent_id=agent_id,
932
- provider=provider,
933
- start_mode=start_mode,
934
- target=f"{session_name}:{agent_id}",
935
- saw_window=saw_window,
936
- )
937
- return False
938
-
939
-
940
-
941
-
942
- def shutil_which(command: str) -> str | None:
943
- from shutil import which
944
-
945
- return which(command)
946
-
947
-
948
- @contextmanager
949
- def _runtime_lock(workspace: Path, name: str, timeout: float = 5.0):
950
- lock_path = runtime_dir(workspace) / f"{name}.lock"
951
- lock_path.parent.mkdir(parents=True, exist_ok=True)
952
- event_log = EventLog(workspace)
953
- log_lock_events = name != "state-save"
954
- start = time.monotonic()
955
- with lock_path.open("w", encoding="utf-8") as lock_file:
956
- while True:
957
- try:
958
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
959
- waited = time.monotonic() - start
960
- if log_lock_events:
961
- event_log.write("runtime.lock_acquired", lock=name, waited_sec=round(waited, 3))
962
- break
963
- except BlockingIOError:
964
- if time.monotonic() - start >= timeout:
965
- if log_lock_events:
966
- event_log.write("runtime.lock_busy", lock=name, timeout_sec=timeout)
967
- raise RuntimeError(
968
- f"{name} is locked by another team-agent process; serialize team-agent {name} calls and retry"
969
- )
970
- time.sleep(0.05)
971
- try:
972
- yield
973
- finally:
974
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
975
- if log_lock_events:
976
- event_log.write("runtime.lock_released", lock=name)
977
-
978
-
979
- def _leader_id(state: dict[str, Any], spec: dict[str, Any]) -> str:
980
- return state.get("leader", {}).get("id") or spec.get("leader", {}).get("id") or "leader"
981
-
982
-
983
- def _is_leader_sender(sender: str, leader_id: str) -> bool:
984
- return sender in {leader_id, "leader", "Leader"}
985
-
986
-
987
- def _is_leader_target(target: str | None, leader_id: str) -> bool:
988
- return target in {leader_id, "leader", "Leader"}
989
-
990
-
991
- def _spec_agent_ids(spec: dict[str, Any]) -> list[str]:
992
- return [str(agent["id"]) for agent in spec.get("agents", [])]
993
-
994
-
995
- def _runtime_team_agent_ids(state: dict[str, Any], spec: dict[str, Any]) -> list[str]:
996
- runtime_agents = state.get("agents", {})
997
- return [agent_id for agent_id in _spec_agent_ids(spec) if agent_id in runtime_agents]
998
-
999
-
1000
- def _is_runtime_team_agent(agent_id: str, state: dict[str, Any], spec: dict[str, Any]) -> bool:
1001
- return agent_id in set(_runtime_team_agent_ids(state, spec))
1002
-
1003
-
1004
- def get_adapter_or_raise(name: str) -> str:
1005
- if name == "tmux" and not shutil_which("tmux"):
1006
- raise RuntimeError("tmux is not installed; install tmux 3.3+ before launch")
1007
- return name
1008
-
1009
-
1010
- def _tmux_session_exists(session_name: str | None) -> bool:
1011
- if not session_name:
1012
- return False
1013
- proc = run_cmd(["tmux", "has-session", "-t", session_name], timeout=5)
1014
- return proc.returncode == 0
1015
-
1016
-
1017
- def _tmux_start_command_for_agent_window(session_name: str, window_name: str, command: str) -> tuple[list[str], str]:
1018
- if _tmux_session_exists(session_name):
1019
- return ["tmux", "new-window", "-t", session_name, "-n", window_name, "sh", "-lc", command], "new-window"
1020
- return ["tmux", "new-session", "-d", "-s", session_name, "-n", window_name, "sh", "-lc", command], "new-session"
1021
-
1022
-
1023
- def _tmux_window_exists(session_name: str | None, window: str | None) -> bool:
1024
- if not session_name or not window:
1025
- return False
1026
- proc = run_cmd(["tmux", "list-windows", "-t", session_name, "-F", "#{window_name}"], timeout=5)
1027
- if proc.returncode != 0:
1028
- return False
1029
- return window in proc.stdout.splitlines()
1030
-
1031
-
1032
- def _find_task(tasks: list[dict[str, Any]], task_id: str) -> dict[str, Any]:
1033
- for task in tasks:
1034
- if task.get("id") == task_id:
1035
- return task
1036
- raise RuntimeError(f"unknown task id: {task_id}")
1037
-
1038
-
1039
- def _find_task_or_none(tasks: list[dict[str, Any]], task_id: str) -> dict[str, Any] | None:
1040
- for task in tasks:
1041
- if task.get("id") == task_id:
1042
- return task
1043
- return None
1044
-
1045
-
1046
- def _is_message_scoped_result(store: MessageStore, envelope: dict[str, Any]) -> bool:
1047
- task_id = str(envelope.get("task_id") or "")
1048
- agent_id = str(envelope.get("agent_id") or "")
1049
- if not task_id.startswith("msg_"):
1050
- return False
1051
- message = _message_by_id(store, task_id)
1052
- return bool(message and message.get("recipient") == agent_id)
1053
-
1054
-
1055
- def _find_agent(spec: dict[str, Any], agent_id: str | None) -> dict[str, Any] | None:
1056
- if not agent_id:
1057
- return None
1058
- for agent in spec.get("agents", []):
1059
- if agent.get("id") == agent_id:
1060
- return agent
1061
- if spec.get("leader", {}).get("id") == agent_id:
1062
- return spec["leader"]
1063
- return None
1064
-
1065
-
1066
- def _result_status_to_task_status(task: dict[str, Any], result_status: str) -> str:
1067
- if result_status == "success":
1068
- return "done"
1069
- if result_status == "blocked":
1070
- return "blocked"
1071
- if result_status in {"partial", "failed"}:
1072
- return _retry_or_failed(task)
1073
- raise KeyError(result_status)
1074
-
1075
-
1076
- def _retry_or_failed(task: dict[str, Any]) -> str:
1077
- retry_count = int(task.get("retry_count") or 0)
1078
- retry_limit = int(task.get("retry_limit") or 0)
1079
- if retry_count < retry_limit:
1080
- task["retry_count"] = retry_count + 1
1081
- return "needs_retry"
1082
- task["retry_count"] = retry_count
1083
- return "failed"
1084
-
1085
-
1086
- def _deliver_pending_message(workspace: Path, state: dict[str, Any], message_id: str, wait_visible: bool = True, timeout: float = 30.0, *, _trust_retry_attempt: int = 1) -> dict[str, Any]:
1087
- from team_agent.messaging.delivery import _deliver_pending_message as impl
1088
-
1089
- return impl(workspace, state, message_id, wait_visible, timeout, _trust_retry_attempt=_trust_retry_attempt)
1090
-
1091
- def _enable_codex_fast_mode(session_name: str, window_name: str) -> dict[str, Any]:
1092
- from team_agent.messaging.tmux_prompt import _enable_codex_fast_mode as impl
1093
-
1094
- return impl(session_name, window_name)
1095
-
1096
- def _leader_command_provider(command: str) -> str | None:
1097
- from team_agent.messaging.leader_panes import _leader_command_provider as impl
1098
-
1099
- return impl(command)
1100
-
1101
- def _message_by_id(store: MessageStore, message_id: str) -> dict[str, Any] | None:
1102
- from team_agent.messaging.leader import _message_by_id as impl
1103
-
1104
- return impl(store, message_id)
1105
-
1106
- def _resolve_leader_pane(pane: str | None, provider: str, workspace: Path | None = None, require_current: bool = False) -> tuple[dict[str, str], str]:
1107
- from team_agent.messaging.leader_panes import _resolve_leader_pane as impl
1108
-
1109
- return impl(pane, provider, workspace, require_current)
1110
-
1111
- def _target_fingerprint(pane_info: dict[str, Any]) -> str:
1112
- from team_agent.messaging.leader_panes import _target_fingerprint as impl
1113
-
1114
- return impl(pane_info)
1115
-
1116
- def _validate_leader_receiver(receiver: dict[str, Any]) -> dict[str, Any]:
1117
- from team_agent.messaging.leader_panes import _validate_leader_receiver as impl
1118
-
1119
- return impl(receiver)
1120
-
1121
- def collect(workspace: Path, result_file: Path | None = None, *, ensure_coordinator: bool = True) -> dict[str, Any]:
1122
- from team_agent.messaging.results import collect as impl
1123
-
1124
- return impl(workspace, result_file, ensure_coordinator=ensure_coordinator)
1125
-
1126
- def send_message(workspace: Path, target: str | list[str] | None, content: str, task_id: str | None = None, sender: str = "leader", requires_ack: bool = True, confirm_human: bool = False, wait_visible: bool = True, timeout: float = 30.0, lock_timeout: float = 5.0, watch_result: bool = False, block_until_delivered: bool = True, team: str | None = None) -> dict[str, Any]:
1127
- from team_agent.messaging.send import send_message as impl
1128
-
1129
- return impl(workspace, target, content, task_id, sender, requires_ack, confirm_human, wait_visible, timeout, lock_timeout, watch_result, block_until_delivered, team)
1130
-
1131
-
1132
- # Lazy-resolved delegation surface. 77 wrappers that used to live inline in
1133
- # runtime.py collapse into this single map plus a PEP 562 __getattr__. Tests
1134
- # that patch team_agent.runtime.<name> still work because mock.patch's
1135
- # setattr shadows the attribute via runtime.__dict__ until the patch exits;
1136
- # attribute access outside a patch resolves via __getattr__ which imports
1137
- # the target module lazily (no top-level cycle into messaging/lifecycle/etc.).
1138
- _DELEGATE_MAP: dict[str, str] = {
1139
- '_broadcast_message_unlocked': 'team_agent.messaging.send._broadcast_message_unlocked',
1140
- '_broadcast_targets': 'team_agent.messaging.send._broadcast_targets',
1141
- '_capture_contains_message_fragment': 'team_agent.messaging.tmux_prompt._capture_contains_message_fragment',
1142
- '_capture_has_pasted_content_prompt': 'team_agent.messaging.tmux_prompt._capture_has_pasted_content_prompt',
1143
- '_capture_tmux_pane_text': 'team_agent.messaging.tmux_prompt._capture_tmux_pane_text',
1144
- '_choose_leader_submit_key': 'team_agent.messaging.leader_panes._choose_leader_submit_key',
1145
- '_collect_results_and_notify_watchers': 'team_agent.messaging.results._collect_results_and_notify_watchers',
1146
- '_compact_broadcast_delivery': 'team_agent.messaging.send._compact_broadcast_delivery',
1147
- '_compact_visible_text': 'team_agent.messaging.tmux_prompt._compact_visible_text',
1148
- '_coordinator_should_run': 'team_agent.messaging.results._coordinator_should_run',
1149
- '_deliver_pending_messages': 'team_agent.messaging.delivery._deliver_pending_messages',
1150
- '_detect_stuck_agents': 'team_agent.messaging.scheduler._detect_stuck_agents',
1151
- '_ensure_coordinator_after_collect': 'team_agent.messaging.results._ensure_coordinator_after_collect',
1152
- '_fail_leader_delivery': 'team_agent.messaging.leader._fail_leader_delivery',
1153
- '_fire_due_scheduled_events': 'team_agent.messaging.scheduler._fire_due_scheduled_events',
1154
- '_format_leader_pane_candidates': 'team_agent.messaging.leader_panes._format_leader_pane_candidates',
1155
- '_format_report_result_notification': 'team_agent.messaging.results._format_report_result_notification',
1156
- '_format_result_watcher_notification': 'team_agent.messaging.results._format_result_watcher_notification',
1157
- '_format_team_agent_message': 'team_agent.messaging.leader._format_team_agent_message',
1158
- '_infer_active_tmux_pane': 'team_agent.messaging.leader_panes._infer_active_tmux_pane',
1159
- '_infer_workspace_tmux_pane': 'team_agent.messaging.leader_panes._infer_workspace_tmux_pane',
1160
- '_is_strong_message_fragment': 'team_agent.messaging.tmux_prompt._is_strong_message_fragment',
1161
- '_leader_command_is_exact': 'team_agent.messaging.leader_panes._leader_command_is_exact',
1162
- '_leader_command_looks_usable': 'team_agent.messaging.leader_panes._leader_command_looks_usable',
1163
- '_leader_inbox_path': 'team_agent.messaging.leader._leader_inbox_path',
1164
- '_leader_pane_rank': 'team_agent.messaging.leader_panes._leader_pane_rank',
1165
- '_leader_receiver_is_direct': 'team_agent.messaging.leader._leader_receiver_is_direct',
1166
- '_leader_submit_verification': 'team_agent.messaging.tmux_io._leader_submit_verification',
1167
- '_message_content_lines': 'team_agent.messaging.tmux_prompt._message_content_lines',
1168
- '_message_fragment_candidates': 'team_agent.messaging.tmux_prompt._message_fragment_candidates',
1169
- '_message_payload': 'team_agent.messaging.leader._message_payload',
1170
- '_mirror_peer_message_to_leader': 'team_agent.messaging.leader._mirror_peer_message_to_leader',
1171
- '_notify_leader_of_report_result': 'team_agent.messaging.results._notify_leader_of_report_result',
1172
- '_notify_result_watchers': 'team_agent.messaging.results._notify_result_watchers',
1173
- '_pane_is_usable_leader': 'team_agent.messaging.leader_panes._pane_is_usable_leader',
1174
- '_pane_path_matches_workspace': 'team_agent.messaging.leader_panes._pane_path_matches_workspace',
1175
- '_parse_tmux_pane_info': 'team_agent.messaging.leader_panes._parse_tmux_pane_info',
1176
- '_prepare_tmux_pane_for_input': 'team_agent.messaging.tmux_io._prepare_tmux_pane_for_input',
1177
- '_record_invalid_result': 'team_agent.messaging.results._record_invalid_result',
1178
- '_rediscover_leader_receiver': 'team_agent.messaging.leader_panes._rediscover_leader_receiver',
1179
- '_schedule_send_retry': 'team_agent.messaging.scheduler._schedule_send_retry',
1180
- '_send_message_unlocked': 'team_agent.messaging.send._send_message_unlocked',
1181
- '_send_single_message_unlocked': 'team_agent.messaging.send._send_single_message_unlocked',
1182
- '_send_to_leader_receiver': 'team_agent.messaging.leader._send_to_leader_receiver',
1183
- '_start_agent_unlocked': 'team_agent.lifecycle.start._start_agent_unlocked',
1184
- '_submit_worker_prompt': 'team_agent.messaging.tmux_prompt._submit_worker_prompt',
1185
- '_team_state_result_entries': 'team_agent.messaging.results._team_state_result_entries',
1186
- '_tmux_current_client_pane_info': 'team_agent.messaging.leader_panes._tmux_current_client_pane_info',
1187
- '_tmux_inject_text': 'team_agent.messaging.tmux_io._tmux_inject_text',
1188
- '_tmux_list_panes': 'team_agent.messaging.leader_panes._tmux_list_panes',
1189
- '_tmux_load_buffer_stdin': 'team_agent.messaging.tmux_io._tmux_load_buffer_stdin',
1190
- '_tmux_pane_info': 'team_agent.messaging.leader_panes._tmux_pane_info',
1191
- '_tmux_paste_ready_timeout': 'team_agent.messaging.tmux_io._tmux_paste_ready_timeout',
1192
- '_tmux_set_buffer_text': 'team_agent.messaging.tmux_io._tmux_set_buffer_text',
1193
- '_tmux_submit_settle_timeout': 'team_agent.messaging.tmux_io._tmux_submit_settle_timeout',
1194
- '_tmux_text_size': 'team_agent.messaging.tmux_io._tmux_text_size',
1195
- '_tmux_truthy': 'team_agent.messaging.leader_panes._tmux_truthy',
1196
- '_wait_for_message_ready': 'team_agent.messaging.tmux_prompt._wait_for_message_ready',
1197
- '_wait_for_pasted_prompt_cleared': 'team_agent.messaging.tmux_prompt._wait_for_pasted_prompt_cleared',
1198
- '_wait_for_visible_token': 'team_agent.messaging.tmux_prompt._wait_for_visible_token',
1199
- '_wait_for_worker_message_ready': 'team_agent.messaging.tmux_prompt._wait_for_worker_message_ready',
1200
- '_watcher_matches_result': 'team_agent.messaging.results._watcher_matches_result',
1201
- '_write_leader_fallback_audit': 'team_agent.messaging.leader._write_leader_fallback_audit',
1202
- 'add_agent': 'team_agent.lifecycle.operations.add_agent',
1203
- 'allow_peer_talk': 'team_agent.messaging.leader.allow_peer_talk',
1204
- 'fork_agent': 'team_agent.lifecycle.operations.fork_agent',
1205
- 'report_result': 'team_agent.messaging.results.report_result',
1206
- 'reset_agent': 'team_agent.lifecycle.operations.reset_agent',
1207
- 'start_agent': 'team_agent.lifecycle.start.start_agent',
1208
- 'stop_agent': 'team_agent.lifecycle.operations.stop_agent',
1209
- 'stuck_cancel': 'team_agent.messaging.scheduler.stuck_cancel',
1210
- 'stuck_list': 'team_agent.messaging.scheduler.stuck_list',
1211
- }
1212
-
1213
-
1214
- def __getattr__(name: str) -> Any:
1215
- target = _DELEGATE_MAP.get(name)
1216
- if target is None:
1217
- raise AttributeError(f"module 'team_agent.runtime' has no attribute {name!r}")
1218
- module_path, _, attr = target.rpartition('.')
1219
- import importlib
1220
- try:
1221
- # Eager import + cache so subsequent runtime.<name> lookups return
1222
- # the same object (stable identity), preserve the real callable's
1223
- # signature/docstring/qualname for inspect.signature and
1224
- # functools.wraps, and skip __getattr__ entirely on the hot path.
1225
- real = getattr(importlib.import_module(module_path), attr)
1226
- except Exception:
1227
- # Partial-load fallback: messaging/deps.py runs a top-level
1228
- # hasattr(_runtime, _name) sweep while the messaging package is
1229
- # still loading, so eager import of a messaging.* target would
1230
- # cycle. Return a deferred proxy that retries at call time and
1231
- # self-installs the real callable on first successful call so
1232
- # subsequent calls hit the cache.
1233
- def _proxy(*args: Any, **kwargs: Any) -> Any:
1234
- real_callable = getattr(importlib.import_module(module_path), attr)
1235
- globals()[name] = real_callable
1236
- return real_callable(*args, **kwargs)
1237
-
1238
- _proxy.__name__ = name
1239
- _proxy.__qualname__ = name
1240
- _proxy.__module__ = "team_agent.runtime"
1241
- return _proxy
1242
- globals()[name] = real
1243
- return real