@team-agent/installer 0.2.10 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/Cargo.lock +744 -0
  2. package/Cargo.toml +34 -0
  3. package/crates/team-agent/Cargo.toml +33 -0
  4. package/crates/team-agent/src/cli/adapters.rs +1343 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +554 -0
  6. package/crates/team-agent/src/cli/emit.rs +1077 -0
  7. package/crates/team-agent/src/cli/helpers.rs +88 -0
  8. package/crates/team-agent/src/cli/leader.rs +216 -0
  9. package/crates/team-agent/src/cli/mod.rs +1141 -0
  10. package/crates/team-agent/src/cli/profile.rs +306 -0
  11. package/crates/team-agent/src/cli/send.rs +215 -0
  12. package/crates/team-agent/src/cli/status.rs +179 -0
  13. package/crates/team-agent/src/cli/status_port.rs +502 -0
  14. package/crates/team-agent/src/cli/tests/base.rs +616 -0
  15. package/crates/team-agent/src/cli/tests/compile.rs +96 -0
  16. package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
  21. package/crates/team-agent/src/cli/tests/mod.rs +97 -0
  22. package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
  23. package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
  25. package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
  26. package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
  27. package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
  28. package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
  29. package/crates/team-agent/src/cli/types.rs +605 -0
  30. package/crates/team-agent/src/compiler/tests.rs +701 -0
  31. package/crates/team-agent/src/compiler.rs +489 -0
  32. package/crates/team-agent/src/coordinator/backoff.rs +153 -0
  33. package/crates/team-agent/src/coordinator/health.rs +436 -0
  34. package/crates/team-agent/src/coordinator/mod.rs +80 -0
  35. package/crates/team-agent/src/coordinator/orphan.rs +179 -0
  36. package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
  37. package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
  38. package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
  39. package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
  40. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
  41. package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
  42. package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
  43. package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
  44. package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
  45. package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
  46. package/crates/team-agent/src/coordinator/tick.rs +2032 -0
  47. package/crates/team-agent/src/coordinator/types.rs +584 -0
  48. package/crates/team-agent/src/db/migration.rs +716 -0
  49. package/crates/team-agent/src/db/mod.rs +23 -0
  50. package/crates/team-agent/src/db/schema.rs +378 -0
  51. package/crates/team-agent/src/event_log.rs +375 -0
  52. package/crates/team-agent/src/fake_worker.rs +253 -0
  53. package/crates/team-agent/src/leader/helpers.rs +190 -0
  54. package/crates/team-agent/src/leader/inject.rs +33 -0
  55. package/crates/team-agent/src/leader/lease.rs +1063 -0
  56. package/crates/team-agent/src/leader/mod.rs +99 -0
  57. package/crates/team-agent/src/leader/owner_bind.rs +292 -0
  58. package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
  59. package/crates/team-agent/src/leader/rediscover.rs +1099 -0
  60. package/crates/team-agent/src/leader/start.rs +273 -0
  61. package/crates/team-agent/src/leader/takeover.rs +235 -0
  62. package/crates/team-agent/src/leader/tests/basics.rs +183 -0
  63. package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
  64. package/crates/team-agent/src/leader/tests/identity.rs +206 -0
  65. package/crates/team-agent/src/leader/tests/idle.rs +271 -0
  66. package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
  67. package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
  68. package/crates/team-agent/src/leader/tests/mod.rs +125 -0
  69. package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
  70. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
  71. package/crates/team-agent/src/leader/types.rs +487 -0
  72. package/crates/team-agent/src/lib.rs +85 -0
  73. package/crates/team-agent/src/lifecycle/display.rs +228 -0
  74. package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
  75. package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
  76. package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
  77. package/crates/team-agent/src/lifecycle/mod.rs +62 -0
  78. package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
  79. package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
  80. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
  81. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
  82. package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
  83. package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
  84. package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
  85. package/crates/team-agent/src/lifecycle/restart.rs +76 -0
  86. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
  88. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
  89. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
  90. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
  91. package/crates/team-agent/src/lifecycle/tests.rs +27 -0
  92. package/crates/team-agent/src/lifecycle/types.rs +685 -0
  93. package/crates/team-agent/src/main.rs +41 -0
  94. package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
  95. package/crates/team-agent/src/mcp_server/mod.rs +183 -0
  96. package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
  97. package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
  98. package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
  99. package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
  100. package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
  101. package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
  102. package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
  103. package/crates/team-agent/src/mcp_server/tests.rs +38 -0
  104. package/crates/team-agent/src/mcp_server/tools.rs +603 -0
  105. package/crates/team-agent/src/mcp_server/types.rs +421 -0
  106. package/crates/team-agent/src/mcp_server/wire.rs +388 -0
  107. package/crates/team-agent/src/message_store.rs +767 -0
  108. package/crates/team-agent/src/messaging/activity.rs +433 -0
  109. package/crates/team-agent/src/messaging/delivery.rs +542 -0
  110. package/crates/team-agent/src/messaging/helpers.rs +209 -0
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
  112. package/crates/team-agent/src/messaging/mod.rs +147 -0
  113. package/crates/team-agent/src/messaging/peers.rs +32 -0
  114. package/crates/team-agent/src/messaging/results.rs +537 -0
  115. package/crates/team-agent/src/messaging/scheduler.rs +344 -0
  116. package/crates/team-agent/src/messaging/selftest.rs +100 -0
  117. package/crates/team-agent/src/messaging/send.rs +582 -0
  118. package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
  119. package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
  121. package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
  122. package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
  123. package/crates/team-agent/src/messaging/trust.rs +192 -0
  124. package/crates/team-agent/src/messaging/types.rs +355 -0
  125. package/crates/team-agent/src/messaging/watchers.rs +591 -0
  126. package/crates/team-agent/src/model/enums.rs +311 -0
  127. package/crates/team-agent/src/model/errors.rs +17 -0
  128. package/crates/team-agent/src/model/ids.rs +155 -0
  129. package/crates/team-agent/src/model/mod.rs +22 -0
  130. package/crates/team-agent/src/model/paths.rs +228 -0
  131. package/crates/team-agent/src/model/permissions.rs +567 -0
  132. package/crates/team-agent/src/model/routing.rs +340 -0
  133. package/crates/team-agent/src/model/spec.rs +680 -0
  134. package/crates/team-agent/src/model/task_graph.rs +380 -0
  135. package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
  136. package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
  137. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
  138. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
  139. package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
  140. package/crates/team-agent/src/model/yaml/tests.rs +288 -0
  141. package/crates/team-agent/src/model/yaml.rs +800 -0
  142. package/crates/team-agent/src/packaging/install.rs +305 -0
  143. package/crates/team-agent/src/packaging/migrate.rs +30 -0
  144. package/crates/team-agent/src/packaging/mod.rs +82 -0
  145. package/crates/team-agent/src/packaging/repair.rs +24 -0
  146. package/crates/team-agent/src/packaging/tests.rs +829 -0
  147. package/crates/team-agent/src/packaging/types.rs +369 -0
  148. package/crates/team-agent/src/provider/adapter.rs +801 -0
  149. package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
  150. package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
  151. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
  152. package/crates/team-agent/src/provider/classify.rs +456 -0
  153. package/crates/team-agent/src/provider/faults.rs +136 -0
  154. package/crates/team-agent/src/provider/helpers.rs +41 -0
  155. package/crates/team-agent/src/provider/mod.rs +53 -0
  156. package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
  157. package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
  158. package/crates/team-agent/src/provider/tests/classify.rs +240 -0
  159. package/crates/team-agent/src/provider/tests/faults.rs +120 -0
  160. package/crates/team-agent/src/provider/tests/idle.rs +208 -0
  161. package/crates/team-agent/src/provider/tests/wire.rs +213 -0
  162. package/crates/team-agent/src/provider/tests.rs +31 -0
  163. package/crates/team-agent/src/provider/types.rs +424 -0
  164. package/crates/team-agent/src/state/identity.rs +656 -0
  165. package/crates/team-agent/src/state/mod.rs +58 -0
  166. package/crates/team-agent/src/state/owner_gate.rs +423 -0
  167. package/crates/team-agent/src/state/persist.rs +712 -0
  168. package/crates/team-agent/src/state/projection.rs +657 -0
  169. package/crates/team-agent/src/state/selector.rs +105 -0
  170. package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
  171. package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
  172. package/crates/team-agent/src/tmux_backend.rs +758 -0
  173. package/crates/team-agent/src/transport/test_support.rs +252 -0
  174. package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
  175. package/crates/team-agent/src/transport/tests/mod.rs +199 -0
  176. package/crates/team-agent/src/transport/tests/wire.rs +527 -0
  177. package/crates/team-agent/src/transport.rs +774 -0
  178. package/npm/install.mjs +90 -106
  179. package/package.json +15 -13
  180. package/crates/team-agent-core/Cargo.toml +0 -12
  181. package/crates/team-agent-core/src/lib.rs +0 -332
  182. package/crates/team-agent-core/src/main.rs +0 -152
  183. package/pyproject.toml +0 -18
  184. package/scripts/install.py +0 -88
  185. package/scripts/run_regression_tests.py +0 -83
  186. package/src/team_agent/__init__.py +0 -3
  187. package/src/team_agent/__main__.py +0 -5
  188. package/src/team_agent/_legacy_pane_discovery.py +0 -186
  189. package/src/team_agent/abnormal_track.py +0 -253
  190. package/src/team_agent/approvals/__init__.py +0 -65
  191. package/src/team_agent/approvals/constants.py +0 -6
  192. package/src/team_agent/approvals/parsing.py +0 -176
  193. package/src/team_agent/approvals/runtime_prompts.py +0 -171
  194. package/src/team_agent/approvals/status.py +0 -176
  195. package/src/team_agent/cli/__init__.py +0 -137
  196. package/src/team_agent/cli/commands.py +0 -481
  197. package/src/team_agent/cli/e2e.py +0 -202
  198. package/src/team_agent/cli/helpers.py +0 -226
  199. package/src/team_agent/cli/parser.py +0 -540
  200. package/src/team_agent/compiler.py +0 -334
  201. package/src/team_agent/coordinator/__init__.py +0 -53
  202. package/src/team_agent/coordinator/__main__.py +0 -83
  203. package/src/team_agent/coordinator/lifecycle.py +0 -363
  204. package/src/team_agent/coordinator/metadata.py +0 -61
  205. package/src/team_agent/coordinator/paths.py +0 -17
  206. package/src/team_agent/diagnose/__init__.py +0 -48
  207. package/src/team_agent/diagnose/checks.py +0 -101
  208. package/src/team_agent/diagnose/comms.py +0 -213
  209. package/src/team_agent/diagnose/health.py +0 -241
  210. package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
  211. package/src/team_agent/diagnose/preflight.py +0 -194
  212. package/src/team_agent/diagnose/quick_start.py +0 -324
  213. package/src/team_agent/display/__init__.py +0 -92
  214. package/src/team_agent/display/adaptive.py +0 -511
  215. package/src/team_agent/display/backend.py +0 -46
  216. package/src/team_agent/display/close.py +0 -154
  217. package/src/team_agent/display/ghostty.py +0 -77
  218. package/src/team_agent/display/rebuild.py +0 -102
  219. package/src/team_agent/display/tiling.py +0 -156
  220. package/src/team_agent/display/worker_window.py +0 -114
  221. package/src/team_agent/display/workspace.py +0 -382
  222. package/src/team_agent/errors.py +0 -10
  223. package/src/team_agent/events.py +0 -84
  224. package/src/team_agent/fake_worker.py +0 -80
  225. package/src/team_agent/idle_predicate.py +0 -200
  226. package/src/team_agent/idle_takeover.py +0 -59
  227. package/src/team_agent/idle_takeover_wiring.py +0 -111
  228. package/src/team_agent/launch/__init__.py +0 -41
  229. package/src/team_agent/launch/bootstrap.py +0 -85
  230. package/src/team_agent/launch/config.py +0 -106
  231. package/src/team_agent/launch/core.py +0 -301
  232. package/src/team_agent/launch/requirements.py +0 -57
  233. package/src/team_agent/leader/__init__.py +0 -926
  234. package/src/team_agent/leader_binding.py +0 -183
  235. package/src/team_agent/lifecycle/__init__.py +0 -5
  236. package/src/team_agent/lifecycle/agents.py +0 -278
  237. package/src/team_agent/lifecycle/operations.py +0 -411
  238. package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
  239. package/src/team_agent/lifecycle/start.py +0 -363
  240. package/src/team_agent/mcp_server/__init__.py +0 -42
  241. package/src/team_agent/mcp_server/__main__.py +0 -7
  242. package/src/team_agent/mcp_server/contracts.py +0 -148
  243. package/src/team_agent/mcp_server/normalize.py +0 -257
  244. package/src/team_agent/mcp_server/server.py +0 -150
  245. package/src/team_agent/mcp_server/tools.py +0 -352
  246. package/src/team_agent/message_store/__init__.py +0 -23
  247. package/src/team_agent/message_store/agent_health.py +0 -113
  248. package/src/team_agent/message_store/core.py +0 -497
  249. package/src/team_agent/message_store/leader_notification_log.py +0 -198
  250. package/src/team_agent/message_store/result_watchers.py +0 -251
  251. package/src/team_agent/message_store/schema.py +0 -308
  252. package/src/team_agent/message_store/schema_migration.py +0 -448
  253. package/src/team_agent/messaging/__init__.py +0 -1
  254. package/src/team_agent/messaging/activity_detector.py +0 -254
  255. package/src/team_agent/messaging/delivery.py +0 -473
  256. package/src/team_agent/messaging/deps.py +0 -247
  257. package/src/team_agent/messaging/idle_alerts.py +0 -423
  258. package/src/team_agent/messaging/internal_delivery.py +0 -46
  259. package/src/team_agent/messaging/leader.py +0 -497
  260. package/src/team_agent/messaging/leader_api_errors.py +0 -216
  261. package/src/team_agent/messaging/leader_panes.py +0 -673
  262. package/src/team_agent/messaging/owner_bypass.py +0 -29
  263. package/src/team_agent/messaging/result_delivery.py +0 -539
  264. package/src/team_agent/messaging/results.py +0 -447
  265. package/src/team_agent/messaging/scheduler.py +0 -450
  266. package/src/team_agent/messaging/send.py +0 -532
  267. package/src/team_agent/messaging/session_drift.py +0 -94
  268. package/src/team_agent/messaging/tmux_io.py +0 -506
  269. package/src/team_agent/messaging/tmux_prompt.py +0 -338
  270. package/src/team_agent/messaging/trust_auto_answer.py +0 -52
  271. package/src/team_agent/orchestrator/__init__.py +0 -376
  272. package/src/team_agent/orchestrator/plan.py +0 -122
  273. package/src/team_agent/orchestrator/state.py +0 -128
  274. package/src/team_agent/paths.py +0 -45
  275. package/src/team_agent/permissions.py +0 -123
  276. package/src/team_agent/profiles/__init__.py +0 -82
  277. package/src/team_agent/profiles/constants.py +0 -19
  278. package/src/team_agent/profiles/core.py +0 -407
  279. package/src/team_agent/profiles/helpers.py +0 -69
  280. package/src/team_agent/profiles/provider_env.py +0 -188
  281. package/src/team_agent/profiles/smoke.py +0 -201
  282. package/src/team_agent/provider_cli/__init__.py +0 -43
  283. package/src/team_agent/provider_cli/adapter.py +0 -172
  284. package/src/team_agent/provider_cli/base.py +0 -48
  285. package/src/team_agent/provider_cli/claude.py +0 -457
  286. package/src/team_agent/provider_cli/codex.py +0 -336
  287. package/src/team_agent/provider_cli/copilot.py +0 -8
  288. package/src/team_agent/provider_cli/fake.py +0 -39
  289. package/src/team_agent/provider_cli/gemini.py +0 -95
  290. package/src/team_agent/provider_cli/opencode.py +0 -8
  291. package/src/team_agent/provider_cli/prompt.py +0 -62
  292. package/src/team_agent/provider_cli/registry.py +0 -18
  293. package/src/team_agent/provider_cli/unsupported.py +0 -32
  294. package/src/team_agent/provider_state/README.md +0 -78
  295. package/src/team_agent/provider_state/__init__.py +0 -86
  296. package/src/team_agent/provider_state/claude.py +0 -86
  297. package/src/team_agent/provider_state/codex.py +0 -84
  298. package/src/team_agent/provider_state/common.py +0 -207
  299. package/src/team_agent/provider_state/registry.py +0 -118
  300. package/src/team_agent/providers.py +0 -163
  301. package/src/team_agent/quality_gates.py +0 -104
  302. package/src/team_agent/restart/__init__.py +0 -34
  303. package/src/team_agent/restart/orchestration.py +0 -554
  304. package/src/team_agent/restart/selection.py +0 -89
  305. package/src/team_agent/restart/snapshot.py +0 -70
  306. package/src/team_agent/routing.py +0 -84
  307. package/src/team_agent/runtime.py +0 -1239
  308. package/src/team_agent/rust_core.py +0 -327
  309. package/src/team_agent/sessions/__init__.py +0 -25
  310. package/src/team_agent/sessions/capture.py +0 -143
  311. package/src/team_agent/sessions/inventory.py +0 -44
  312. package/src/team_agent/sessions/resume.py +0 -135
  313. package/src/team_agent/simple_yaml.py +0 -236
  314. package/src/team_agent/spec.py +0 -370
  315. package/src/team_agent/state.py +0 -602
  316. package/src/team_agent/status/__init__.py +0 -63
  317. package/src/team_agent/status/approvals.py +0 -52
  318. package/src/team_agent/status/compact.py +0 -158
  319. package/src/team_agent/status/constants.py +0 -18
  320. package/src/team_agent/status/inbox.py +0 -58
  321. package/src/team_agent/status/peek.py +0 -117
  322. package/src/team_agent/status/queries.py +0 -199
  323. package/src/team_agent/task_graph.py +0 -80
  324. package/src/team_agent/terminal.py +0 -57
  325. package/src/team_agent/wake.py +0 -58
  326. package/src/team_agent/watch/__init__.py +0 -145
@@ -1,602 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import hashlib
4
- import json
5
- import os
6
- import copy
7
- import subprocess
8
- import uuid
9
- from datetime import datetime, timezone
10
- from pathlib import Path
11
- from typing import Any
12
-
13
- from team_agent.paths import runtime_dir
14
- from team_agent.simple_yaml import dumps
15
-
16
-
17
- SESSION_CAPTURE_FIELDS = [
18
- "session_id",
19
- "rollout_path",
20
- "captured_at",
21
- "captured_via",
22
- "attribution_confidence",
23
- ]
24
- SESSION_STATE_FIELDS = [
25
- *SESSION_CAPTURE_FIELDS,
26
- "spawn_cwd",
27
- ]
28
- _UUID_SEPARATOR = "\0"
29
- _RUNTIME_STATE_CACHE: dict[str, dict[str, Any]] = {}
30
-
31
-
32
- def derive_leader_session_uuid(machine_fingerprint: str, workspace_abspath: str, os_user: str, team_id: str) -> str:
33
- parts = [machine_fingerprint, workspace_abspath, os_user, team_id]
34
- if any(_UUID_SEPARATOR in part for part in parts):
35
- raise ValueError("leader_session_uuid inputs must not contain NUL")
36
- return hashlib.sha256(_UUID_SEPARATOR.join(parts).encode("utf-8")).hexdigest()[:32]
37
-
38
-
39
- def runtime_state_path(workspace: Path) -> Path:
40
- return runtime_dir(workspace) / "state.json"
41
-
42
-
43
- def normalize_agent_session_state(state: dict[str, Any]) -> None:
44
- agents = state.get("agents", {})
45
- if not isinstance(agents, dict):
46
- return
47
- for agent_state in agents.values():
48
- if isinstance(agent_state, dict):
49
- for field in SESSION_STATE_FIELDS:
50
- agent_state.setdefault(field, None)
51
-
52
-
53
- def load_runtime_state(workspace: Path) -> dict[str, Any]:
54
- path = runtime_state_path(workspace)
55
- if not path.exists():
56
- cached = _RUNTIME_STATE_CACHE.get(str(path))
57
- if cached is not None:
58
- return copy.deepcopy(cached)
59
- return {"agents": {}, "tasks": [], "session_name": None, "active_team_key": None}
60
- state = json.loads(path.read_text(encoding="utf-8"))
61
- normalize_agent_session_state(state)
62
- changed = _migrate_state_identity(state, workspace)
63
- if _migrate_active_team_key(state):
64
- changed = True
65
- if changed:
66
- save_runtime_state(workspace, state)
67
- _RUNTIME_STATE_CACHE[str(path)] = copy.deepcopy(state)
68
- return state
69
-
70
-
71
- def _migrate_active_team_key(state: dict[str, Any]) -> bool:
72
- """0.2.6 Family B (C6): legacy states with a top-level ``session_name``
73
- but no ``active_team_key`` get the active pointer seeded once. After
74
- this, ``active_team_key`` is the single explicit source of truth and
75
- callers mutate it through CLI verbs (claim-leader / takeover /
76
- shutdown / restart)."""
77
- if "active_team_key" in state:
78
- return False
79
- teams = state.get("teams") if isinstance(state.get("teams"), dict) else {}
80
- if state.get("session_name"):
81
- seed = team_state_key(state)
82
- state["active_team_key"] = seed if seed in teams or not teams else seed
83
- return True
84
- if isinstance(teams, dict) and len(teams) == 1:
85
- state["active_team_key"] = next(iter(teams))
86
- return True
87
- state["active_team_key"] = None
88
- return True
89
-
90
-
91
- def team_state_key(state: dict[str, Any]) -> str:
92
- for field in ("team_dir", "spec_path"):
93
- value = state.get(field)
94
- if not value:
95
- continue
96
- path = Path(str(value))
97
- key = path.name if field == "team_dir" else path.parent.name
98
- if key and key not in {".team", "runtime"}:
99
- return key
100
- return str(state.get("session_name") or "current")
101
-
102
-
103
- def compact_team_state(state: dict[str, Any]) -> dict[str, Any]:
104
- compact = copy.deepcopy(state)
105
- compact.pop("teams", None)
106
- return compact
107
-
108
-
109
- def merge_workspace_team_state(existing: dict[str, Any], launched: dict[str, Any]) -> dict[str, Any]:
110
- launched_key = team_state_key(launched)
111
- if not existing.get("session_name"):
112
- merged = copy.deepcopy(launched)
113
- merged.setdefault("teams", {})[launched_key] = compact_team_state(launched)
114
- return merged
115
- existing_key = team_state_key(existing)
116
- if existing_key == launched_key:
117
- merged = copy.deepcopy(launched)
118
- teams = copy.deepcopy(existing.get("teams") or {})
119
- teams[launched_key] = compact_team_state(launched)
120
- merged["teams"] = teams
121
- return merged
122
- merged = copy.deepcopy(existing)
123
- teams = merged.setdefault("teams", {})
124
- teams.setdefault(existing_key, compact_team_state(existing))
125
- teams[launched_key] = compact_team_state(launched)
126
- return merged
127
-
128
-
129
- def team_state_candidates(state: dict[str, Any]) -> dict[str, dict[str, Any]]:
130
- """0.2.6 Family B (C7): the only candidate source is ``state.teams``
131
- filtered by ``status == "alive"``. Top-level ``session_name`` /
132
- ``team_dir`` are a derived view of the active team and never count as
133
- an independent candidate. Shutdown/legacy entries with non-alive
134
- status are excluded."""
135
- out: dict[str, dict[str, Any]] = {}
136
- teams = state.get("teams") if isinstance(state.get("teams"), dict) else {}
137
- for key, value in teams.items():
138
- if not isinstance(value, dict):
139
- continue
140
- if str(value.get("status") or "alive").lower() != "alive":
141
- continue
142
- out[str(key)] = value
143
- return out
144
-
145
-
146
- def format_team_candidates(team_states: dict[str, dict[str, Any]]) -> str:
147
- if not team_states:
148
- return "No team state was found."
149
- parts = []
150
- for key in sorted(team_states):
151
- st = team_states[key]
152
- agents = ",".join(sorted(st.get("agents", {}).keys())) or "-"
153
- parts.append(f"{key} session={st.get('session_name') or '-'} agents={agents}")
154
- return "Candidates: " + "; ".join(parts)
155
-
156
-
157
- def _team_entry_from_state(state: dict[str, Any], team_key: str) -> dict[str, Any] | None:
158
- teams = state.get("teams") if isinstance(state.get("teams"), dict) else {}
159
- entry = teams.get(team_key)
160
- if not isinstance(entry, dict):
161
- return None
162
- return entry
163
-
164
-
165
- def _project_top_level_view(state: dict[str, Any], team_key: str) -> dict[str, Any]:
166
- """0.2.6 Family B (C8): when picking a team for use, the top-level
167
- keys (``session_name`` / ``team_dir`` / ``agents`` / ``tasks``) are a
168
- derived view of ``teams[team_key]``. We copy the team entry into a
169
- flat dict and preserve any auxiliary state (``team_owner`` /
170
- ``leader_receiver`` / ``coordinator`` already pinned to the team)."""
171
- entry = _team_entry_from_state(state, team_key) or {}
172
- projection = copy.deepcopy(entry)
173
- projection.setdefault("session_name", entry.get("session_name"))
174
- projection.setdefault("team_dir", entry.get("team_dir"))
175
- projection["active_team_key"] = team_key
176
- # Preserve the full teams dict so consumers can introspect siblings.
177
- projection["teams"] = copy.deepcopy(state.get("teams") or {})
178
- if "team_owner" in entry:
179
- projection["team_owner"] = copy.deepcopy(entry["team_owner"])
180
- elif state.get("team_owner") is not None:
181
- projection["team_owner"] = copy.deepcopy(state["team_owner"])
182
- if "leader_receiver" in entry:
183
- projection["leader_receiver"] = copy.deepcopy(entry["leader_receiver"])
184
- elif state.get("leader_receiver") is not None:
185
- projection["leader_receiver"] = copy.deepcopy(state["leader_receiver"])
186
- if "coordinator" in state:
187
- projection.setdefault("coordinator", copy.deepcopy(state["coordinator"]))
188
- return projection
189
-
190
-
191
- def select_runtime_state(workspace: Path, team: str | None = None) -> dict[str, Any]:
192
- state = load_runtime_state(workspace)
193
- alive = team_state_candidates(state)
194
- if team:
195
- if not alive and team in {str(state.get("active_team_key") or ""), team_state_key(state)}:
196
- projection = copy.deepcopy(state)
197
- projection["active_team_key"] = str(team)
198
- return projection
199
- matches = [
200
- (key, value)
201
- for key, value in alive.items()
202
- if team in {key, str(value.get("session_name") or ""), str(value.get("team_dir") or "")}
203
- ]
204
- if len(matches) == 1:
205
- return _project_top_level_view(state, matches[0][0])
206
- from team_agent.errors import RuntimeError
207
- if len(matches) > 1:
208
- raise RuntimeError("team selector is ambiguous. " + format_team_candidates(alive))
209
- raise RuntimeError(f"team {team!r} not found. " + format_team_candidates(alive))
210
- active = state.get("active_team_key")
211
- if active and active in alive:
212
- return _project_top_level_view(state, str(active))
213
- if len(alive) == 1:
214
- return _project_top_level_view(state, next(iter(alive)))
215
- if not alive:
216
- return copy.deepcopy(state)
217
- from team_agent.errors import RuntimeError
218
- raise RuntimeError(
219
- "multiple teams found in this workspace; pass --team <team> to choose. "
220
- + format_team_candidates(alive)
221
- )
222
-
223
-
224
- def ambiguous_team_target_result(state: dict[str, Any]) -> dict[str, Any] | None:
225
- alive = team_state_candidates(state)
226
- active = state.get("active_team_key")
227
- if active and active in alive:
228
- return None
229
- if len(alive) <= 1:
230
- return None
231
- return {
232
- "ok": False,
233
- "status": "refused",
234
- "reason": "team_target_ambiguous",
235
- "candidates": sorted(alive.keys()),
236
- "message": "multiple teams found in this workspace; pass --team <team> to choose. "
237
- + format_team_candidates(alive),
238
- }
239
-
240
-
241
- def resolve_team_scoped_state(
242
- workspace: Path,
243
- team: str | None,
244
- ) -> tuple[dict[str, Any] | None, dict[str, Any] | None]:
245
- if team is None:
246
- ambiguous = ambiguous_team_target_result(load_runtime_state(workspace))
247
- if ambiguous:
248
- return None, ambiguous
249
- try:
250
- from team_agent.errors import RuntimeError as _TeamAgentRuntimeError
251
- return select_runtime_state(workspace, team), None
252
- except _TeamAgentRuntimeError as exc:
253
- return None, {
254
- "ok": False,
255
- "status": "refused",
256
- "reason": "team_target_unresolved",
257
- "team": team,
258
- "error": str(exc),
259
- }
260
-
261
-
262
- def _identity_workspace_abspath(state: dict[str, Any], workspace: Path | None = None) -> str:
263
- if state.get("workspace"):
264
- return str(Path(str(state["workspace"])).resolve())
265
- if state.get("team_dir"):
266
- return str(Path(str(state["team_dir"])).resolve().parent.parent)
267
- if state.get("spec_path"):
268
- spec_path = Path(str(state["spec_path"])).resolve()
269
- return str(spec_path.parent.parent.parent if spec_path.parent.parent.name == ".team" else spec_path.parent)
270
- return str((workspace or Path(os.environ.get("TEAM_AGENT_WORKSPACE") or os.getcwd())).resolve())
271
-
272
-
273
- def _identity_os_user() -> str:
274
- return os.environ.get("USER") or os.environ.get("USERNAME") or ""
275
-
276
-
277
- def _identity_machine_fingerprint(state: dict[str, Any]) -> str:
278
- for record in (state.get("team_owner"), state.get("leader_receiver")):
279
- if isinstance(record, dict) and record.get("machine_fingerprint"):
280
- return str(record["machine_fingerprint"])
281
- return os.environ.get("TEAM_AGENT_MACHINE_FINGERPRINT") or ""
282
-
283
-
284
- def _leader_session_uuid_for_state(state: dict[str, Any], workspace: Path | None = None, team_id: str | None = None) -> str:
285
- return derive_leader_session_uuid(
286
- _identity_machine_fingerprint(state),
287
- _identity_workspace_abspath(state, workspace),
288
- _identity_os_user(),
289
- team_id or team_state_key(state),
290
- )
291
-
292
-
293
- def _migrate_team_identity(state: dict[str, Any], workspace: Path, team_id: str | None = None) -> bool:
294
- leader_uuid = _leader_session_uuid_for_state(state, workspace, team_id)
295
- changed = False
296
- for key in ("team_owner", "leader_receiver"):
297
- record = state.get(key)
298
- if isinstance(record, dict) and not record.get("leader_session_uuid"):
299
- record["leader_session_uuid"] = leader_uuid
300
- changed = True
301
- return changed
302
-
303
-
304
- def _migrate_state_identity(state: dict[str, Any], workspace: Path) -> bool:
305
- changed = _migrate_team_identity(state, workspace) if state.get("session_name") else False
306
- teams = state.get("teams")
307
- if isinstance(teams, dict):
308
- for team_id, team_state in teams.items():
309
- if isinstance(team_state, dict):
310
- changed = _migrate_team_identity(team_state, workspace, str(team_id)) or changed
311
- return changed
312
-
313
-
314
- def _caller_identity_from_env(state: dict[str, Any] | None = None, team_id: str | None = None, workspace: Path | None = None) -> dict[str, str]:
315
- state = state or {}
316
- machine_fingerprint = os.environ.get("TEAM_AGENT_MACHINE_FINGERPRINT") or ""
317
- override = os.environ.get("TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE") or ""
318
- env_uuid = os.environ.get("TEAM_AGENT_LEADER_SESSION_UUID") or ""
319
- leader_uuid = override or env_uuid or derive_leader_session_uuid(
320
- machine_fingerprint,
321
- _identity_workspace_abspath(state, workspace),
322
- _identity_os_user(),
323
- team_id or os.environ.get("TEAM_AGENT_TEAM_ID") or team_state_key(state),
324
- )
325
- return {
326
- "pane_id": os.environ.get("TEAM_AGENT_LEADER_PANE_ID") or os.environ.get("TMUX_PANE") or "",
327
- "provider": os.environ.get("TEAM_AGENT_LEADER_PROVIDER") or "",
328
- "machine_fingerprint": machine_fingerprint,
329
- "leader_session_uuid": leader_uuid,
330
- "leader_session_uuid_source": "explicit-override" if override else ("env" if env_uuid else "derived"),
331
- }
332
-
333
-
334
- _TMUX_PANE_LIVE = "live"
335
- _TMUX_PANE_DEAD = "dead"
336
- _TMUX_PANE_UNKNOWN = "unknown"
337
-
338
-
339
- def _tmux_pane_liveness(pane_id: str) -> str:
340
- if not pane_id:
341
- return _TMUX_PANE_UNKNOWN
342
- try:
343
- from team_agent.runtime import run_cmd
344
- proc = run_cmd(["tmux", "display-message", "-p", "-t", pane_id, "#{pane_id}"], timeout=3)
345
- except Exception:
346
- try:
347
- proc = subprocess.run(
348
- ["tmux", "display-message", "-p", "-t", pane_id, "#{pane_id}"],
349
- text=True,
350
- capture_output=True,
351
- timeout=3,
352
- check=False,
353
- )
354
- except Exception:
355
- return _TMUX_PANE_UNKNOWN
356
- if proc.returncode == 0:
357
- return _TMUX_PANE_LIVE
358
- stderr = str(getattr(proc, "stderr", "") or "").lower()
359
- if "can't find pane" in stderr or "can't find window" in stderr or "can't find session" in stderr:
360
- return _TMUX_PANE_DEAD
361
- return _TMUX_PANE_UNKNOWN
362
-
363
-
364
- def check_team_owner(state: dict[str, Any]) -> dict[str, Any] | None:
365
- owner = state.get("team_owner") or {}
366
- if not owner:
367
- return None
368
- _migrate_team_identity(state, Path(_identity_workspace_abspath(state)), team_state_key(state))
369
- caller = _caller_identity_from_env(state, team_state_key(state))
370
- owner_uuid = str(owner.get("leader_session_uuid") or "")
371
- caller_uuid = caller["leader_session_uuid"]
372
- owner_pane = str(owner.get("pane_id") or "")
373
- caller_pane = caller.get("pane_id") or ""
374
- if caller_pane and caller_pane == owner_pane:
375
- return None
376
- if (
377
- caller_pane
378
- and not os.environ.get("TEAM_AGENT_ID")
379
- and owner_pane
380
- and _tmux_pane_liveness(owner_pane) != _TMUX_PANE_LIVE
381
- ):
382
- return None
383
- if caller_uuid == owner_uuid and (not caller_pane or caller_pane == owner_pane):
384
- return None
385
- same_uuid = caller_uuid == owner_uuid
386
- return {
387
- "ok": False,
388
- "status": "refused",
389
- "reason": "team_owner_mismatch",
390
- "reason_kind": "sticky_bind_collision" if same_uuid else "owner_takeover_required",
391
- "error": "not_owner",
392
- "action": "team-agent claim-leader --confirm" if same_uuid else "team-agent takeover --confirm",
393
- "team_owner": owner,
394
- "caller": caller,
395
- }
396
-
397
-
398
- def worker_sender_bypasses_owner_gate(state: dict[str, Any], sender: str | None) -> str | None:
399
- if not sender:
400
- return None
401
- leader_id = (state.get("leader") or {}).get("id") or "leader"
402
- if sender == leader_id or sender in {"leader", "Leader"}:
403
- return None
404
- if sender not in (state.get("agents") or {}):
405
- return None
406
- env_agent_id = os.environ.get("TEAM_AGENT_ID") or ""
407
- if env_agent_id and env_agent_id != sender:
408
- return None
409
- return env_agent_id or sender
410
-
411
-
412
- def populate_team_owner_from_env(state: dict[str, Any], source: str = "autopopulate") -> dict[str, Any] | None:
413
- # Lease mutation convergence marker: _write_lease_dual_state.
414
- if state.get("team_owner"):
415
- _migrate_team_identity(state, Path(_identity_workspace_abspath(state)), team_state_key(state))
416
- return state["team_owner"]
417
- caller = _caller_identity_from_env(state, team_state_key(state))
418
- if not caller["pane_id"]:
419
- return None
420
- owner = {
421
- "pane_id": caller["pane_id"],
422
- "provider": caller["provider"],
423
- "machine_fingerprint": caller["machine_fingerprint"],
424
- "leader_session_uuid": caller["leader_session_uuid"],
425
- "claimed_at": datetime.now(timezone.utc).isoformat(),
426
- "claimed_via": source,
427
- }
428
- state["team_owner"] = owner
429
- return owner
430
-
431
-
432
- def apply_first_time_leader_binding(
433
- workspace: Path,
434
- state: dict[str, Any],
435
- receiver: dict[str, Any],
436
- pane_info: dict[str, Any],
437
- identity: dict[str, Any],
438
- source: str,
439
- ) -> dict[str, Any]:
440
- # Lease mutation convergence marker: _write_lease_dual_state.
441
- from team_agent.messaging.leader_panes import _leader_command_looks_usable
442
- command = pane_info.get("pane_current_command", "")
443
- provider = str(receiver.get("provider") or "")
444
- if not _leader_command_looks_usable(command, provider):
445
- return {"ok": False, "reason": "leader_pane_wrong_command", "error": f"pane command {command!r} is not a leader host", "pane": pane_info}
446
- current_path = pane_info.get("pane_current_path")
447
- if not current_path or os.path.realpath(current_path) != os.path.realpath(str(workspace.resolve())):
448
- return {"ok": False, "reason": "leader_pane_wrong_workspace", "error": f"pane cwd {current_path!r} does not match workspace {str(workspace.resolve())!r}", "pane": pane_info}
449
- receiver.update({
450
- "leader_session_uuid": identity["leader_session_uuid"],
451
- "machine_fingerprint": identity["machine_fingerprint"],
452
- "owner_epoch": 0,
453
- })
454
- state["team_owner"] = {
455
- "pane_id": receiver["pane_id"],
456
- "provider": provider,
457
- "machine_fingerprint": identity["machine_fingerprint"],
458
- "leader_session_uuid": identity["leader_session_uuid"],
459
- "owner_epoch": 0,
460
- "claimed_at": datetime.now(timezone.utc).isoformat(),
461
- "claimed_via": source,
462
- }
463
- state["leader_receiver"] = receiver
464
- return {"ok": True, "pane": pane_info, "warning": None, "first_time": True}
465
-
466
-
467
- def leader_env_exports(receiver: dict[str, Any], identity: dict[str, Any]) -> dict[str, str]:
468
- return {
469
- "TEAM_AGENT_LEADER_PANE_ID": str(receiver.get("pane_id") or ""),
470
- "TEAM_AGENT_LEADER_PROVIDER": str(receiver.get("provider") or ""),
471
- "TEAM_AGENT_LEADER_SESSION_UUID": str(identity.get("leader_session_uuid") or ""),
472
- "TEAM_AGENT_MACHINE_FINGERPRINT": str(identity.get("machine_fingerprint") or ""),
473
- "TEAM_AGENT_WORKSPACE": str(identity.get("workspace_abspath") or ""),
474
- "TEAM_AGENT_TEAM_ID": str(identity.get("team_id") or ""),
475
- }
476
-
477
-
478
- def validate_leader_uuid_from_targets(receiver: dict[str, Any], targets: dict[str, Any]) -> dict[str, Any]:
479
- if receiver.get("provider") == "fake":
480
- return {"ok": True}
481
- if not targets.get("ok"):
482
- return {"ok": False, "reason": "leader_uuid_lookup_failed", "error": targets.get("error") or "tmux target scan failed"}
483
- pane_id = receiver.get("pane_id")
484
- target = next((item for item in targets.get("targets", []) if item.get("pane_id") == pane_id), None)
485
- if not target:
486
- return {"ok": False, "reason": "leader_pane_missing", "error": "tmux pane does not exist"}
487
- return {"ok": True, "pane": target}
488
-
489
-
490
- def save_runtime_state(workspace: Path, state: dict[str, Any]) -> None:
491
- _migrate_state_identity(state, workspace)
492
- path = runtime_state_path(workspace)
493
- path.parent.mkdir(parents=True, exist_ok=True)
494
- tmp_path = path.with_name(f"{path.name}.{os.getpid()}.{uuid.uuid4().hex}.tmp")
495
- try:
496
- tmp_path.write_text(json.dumps(state, indent=2, ensure_ascii=False), encoding="utf-8")
497
- os.replace(tmp_path, path)
498
- _RUNTIME_STATE_CACHE[str(path)] = copy.deepcopy(state)
499
- finally:
500
- tmp_path.unlink(missing_ok=True)
501
-
502
-
503
- def save_team_scoped_state(workspace: Path, team_state: dict[str, Any]) -> None:
504
- target_key = team_state_key(team_state)
505
- existing = load_runtime_state(workspace)
506
- existing_primary_key = team_state_key(existing) if existing.get("session_name") else None
507
- if (
508
- existing_primary_key is not None
509
- and existing_primary_key != target_key
510
- and existing.get("session_name")
511
- and existing.get("session_name") == team_state.get("session_name")
512
- ):
513
- existing_primary_key = target_key
514
- existing_teams = existing.get("teams") or {}
515
- incoming_teams = team_state.get("teams") if isinstance(team_state.get("teams"), dict) else None
516
- if not existing_teams and existing_primary_key == target_key:
517
- merged = copy.deepcopy(team_state)
518
- merged.pop("teams", None)
519
- save_runtime_state(workspace, merged)
520
- return
521
- teams = copy.deepcopy(incoming_teams or existing_teams)
522
- teams[target_key] = compact_team_state(team_state)
523
- if existing_primary_key is None or existing_primary_key == target_key:
524
- merged = copy.deepcopy(team_state)
525
- merged["teams"] = teams
526
- else:
527
- merged = copy.deepcopy(existing)
528
- merged["teams"] = teams
529
- if not merged.get("teams"):
530
- merged.pop("teams", None)
531
- save_runtime_state(workspace, merged)
532
-
533
-
534
- def write_team_state(workspace: Path, spec: dict[str, Any], runtime: dict[str, Any], results: list[dict[str, Any]] | None = None) -> Path:
535
- path = workspace / spec.get("context", {}).get("state_file", "team_state.md")
536
- path.parent.mkdir(parents=True, exist_ok=True)
537
- lines = [
538
- "# Team State",
539
- "",
540
- f"Updated: {datetime.now(timezone.utc).isoformat()}",
541
- "",
542
- "## Objective",
543
- "",
544
- spec.get("team", {}).get("objective", ""),
545
- "",
546
- "## Team",
547
- "",
548
- f"- Name: {spec.get('team', {}).get('name')}",
549
- f"- Runtime session: {runtime.get('session_name')}",
550
- ]
551
- receiver = runtime.get("leader_receiver") or {}
552
- if receiver:
553
- if receiver.get("mode") == "direct_tmux":
554
- lines.append(
555
- f"- Leader receiver: direct tmux {receiver.get('pane_id')} "
556
- f"({receiver.get('provider')}, {receiver.get('status')})"
557
- )
558
- else:
559
- lines.append(f"- Leader inbox fallback: {receiver.get('session')}:{receiver.get('window')} ({receiver.get('status')})")
560
- lines.append(f"- Leader inbox log: {receiver.get('path')}")
561
- lines.extend(["", "## Agents", ""])
562
- for agent in spec.get("agents", []):
563
- status = runtime.get("agents", {}).get(agent["id"], {}).get("status", "unknown")
564
- lines.append(f"- {agent['id']}: {agent['role']} on {agent['provider']} ({status})")
565
- lines.extend(["", "## Task Graph", ""])
566
- for task in runtime.get("tasks", spec.get("tasks", [])):
567
- deps = ", ".join(task.get("deps", [])) or "none"
568
- assignee = task.get("assignee") or "unassigned"
569
- lines.append(f"- {task['id']} [{task.get('status', 'pending')}], assignee={assignee}, deps={deps}: {task['title']}")
570
- if task.get("last_result_summary"):
571
- lines.append(f" Summary: {task['last_result_summary']}")
572
- if task.get("artifact_refs"):
573
- for ref in task["artifact_refs"]:
574
- if isinstance(ref, dict):
575
- lines.append(f" Artifact: {ref.get('path')} - {ref.get('description', '')}")
576
- else:
577
- lines.append(f" Artifact: INVALID artifact ref {ref!r}")
578
- lines.extend(["", "## Latest Results", ""])
579
- for result in results or []:
580
- envelope = json.loads(result["envelope"]) if isinstance(result.get("envelope"), str) else result
581
- lines.append(f"- {envelope.get('task_id')} from {envelope.get('agent_id')}: {envelope.get('status')} - {envelope.get('summary')}")
582
- lines.extend(["", "## Blockers", ""])
583
- blockers = [
584
- task
585
- for task in runtime.get("tasks", spec.get("tasks", []))
586
- if task.get("status") in {"blocked", "failed", "needs_retry"}
587
- ]
588
- if blockers:
589
- for task in blockers:
590
- lines.append(f"- {task['id']}: {task.get('last_result_summary', task.get('title'))}")
591
- else:
592
- lines.append("- None")
593
- lines.extend(["", "## Next Step", "", "- Continue routing ready tasks and collect result envelopes."])
594
- path.write_text("\n".join(lines) + "\n", encoding="utf-8")
595
- return path
596
-
597
-
598
- def write_spec(path: Path, spec: dict[str, Any]) -> None:
599
- path.parent.mkdir(parents=True, exist_ok=True)
600
- tmp_path = path.with_suffix(path.suffix + ".tmp")
601
- tmp_path.write_text(dumps(spec), encoding="utf-8")
602
- os.replace(tmp_path, path)
@@ -1,63 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from team_agent.status.approvals import approvals, format_approvals
4
- from team_agent.status.compact import (
5
- compact_agent_state,
6
- compact_event,
7
- compact_mapping,
8
- compact_status,
9
- compact_task,
10
- compact_value,
11
- )
12
- from team_agent.status.constants import (
13
- APPROVAL_SCAN_LINES,
14
- PEEK_MAX_LINES,
15
- PEEK_MAX_MATCHES,
16
- PEEK_SEARCH_SCAN_LINES,
17
- PENDING_DELIVERY_STATUSES,
18
- STATUS_EVENT_LIMIT,
19
- STATUS_TEXT_LIMIT,
20
- )
21
- from team_agent.status.inbox import format_inbox, inbox
22
- from team_agent.status.peek import (
23
- format_search_matches,
24
- peek,
25
- search_lines,
26
- validate_line_count,
27
- )
28
- from team_agent.status.queries import (
29
- format_status,
30
- latest_result_summaries,
31
- queued_message_statuses,
32
- result_summary_from_row,
33
- status,
34
- )
35
-
36
- __all__ = [
37
- "APPROVAL_SCAN_LINES",
38
- "PEEK_MAX_LINES",
39
- "PEEK_MAX_MATCHES",
40
- "PEEK_SEARCH_SCAN_LINES",
41
- "PENDING_DELIVERY_STATUSES",
42
- "STATUS_EVENT_LIMIT",
43
- "STATUS_TEXT_LIMIT",
44
- "approvals",
45
- "compact_agent_state",
46
- "compact_event",
47
- "compact_mapping",
48
- "compact_status",
49
- "compact_task",
50
- "compact_value",
51
- "format_approvals",
52
- "format_inbox",
53
- "format_search_matches",
54
- "format_status",
55
- "inbox",
56
- "latest_result_summaries",
57
- "peek",
58
- "queued_message_statuses",
59
- "result_summary_from_row",
60
- "search_lines",
61
- "status",
62
- "validate_line_count",
63
- ]