@team-agent/installer 0.2.11 → 0.3.1

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 +1204 -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 +1207 -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 +557 -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 +1084 -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 +526 -0
  59. package/crates/team-agent/src/leader/rediscover.rs +1101 -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 +237 -0
  64. package/crates/team-agent/src/leader/tests/identity.rs +206 -0
  65. package/crates/team-agent/src/leader/tests/idle.rs +272 -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 +410 -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 +489 -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 +2109 -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 +985 -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 +710 -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 +187 -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 +468 -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 +743 -0
  110. package/crates/team-agent/src/messaging/helpers.rs +209 -0
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +329 -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 +553 -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 +578 -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 +659 -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 +765 -0
  172. package/crates/team-agent/src/tmux_backend.rs +810 -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 +118 -112
  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,376 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from datetime import datetime, timezone
5
- from pathlib import Path
6
- from typing import Any
7
-
8
- from team_agent.events import EventLog
9
- from team_agent.orchestrator.plan import (
10
- InvalidPlanError,
11
- evaluate_condition,
12
- load_plan,
13
- stage_matches_result,
14
- )
15
- from team_agent.orchestrator.state import (
16
- InvalidPlanIdError,
17
- artifact_path,
18
- list_plan_states,
19
- load_plan_state,
20
- sanitize_plan_id,
21
- save_plan_state,
22
- state_path,
23
- )
24
-
25
-
26
- def start_plan(workspace: Path, plan_path: Path, *, start: bool = True) -> dict[str, Any]:
27
- workspace = Path(workspace)
28
- try:
29
- plan = load_plan(plan_path)
30
- except InvalidPlanError as exc:
31
- return {"ok": False, "error": str(exc), "reason": exc.reason, "plan_path": str(plan_path)}
32
- try:
33
- plan_id = sanitize_plan_id(plan.get("id"))
34
- except InvalidPlanIdError as exc:
35
- return {"ok": False, "error": str(exc), "reason": "invalid_plan_id", "plan_path": str(plan_path)}
36
- stages = list(plan.get("stages") or [])
37
- if not stages:
38
- return {"ok": False, "error": "plan has no stages", "plan_id": plan_id}
39
- plan_team = _text_or_none(plan.get("team"))
40
- event_log = EventLog(workspace)
41
- existing = load_plan_state(workspace, plan_id)
42
- if existing and existing.get("status") in {"running", "halted", "completed"}:
43
- return {
44
- "ok": True,
45
- "status": existing.get("status"),
46
- "plan_id": plan_id,
47
- "current_stage": existing.get("current_stage"),
48
- "already_started": True,
49
- "state_path": str(state_path(workspace, plan_id)),
50
- }
51
- state: dict[str, Any] = {
52
- "plan_id": plan_id,
53
- "plan_path": str(plan_path),
54
- "team": plan_team,
55
- "current_stage": 1,
56
- "completed_stages": [],
57
- "status": "running",
58
- "halt_reason": None,
59
- "halt_artifact": None,
60
- "started_at": datetime.now(timezone.utc).isoformat(),
61
- "stages": stages,
62
- "current_dispatch": None,
63
- }
64
- save_plan_state(workspace, state)
65
- event_log.write("orchestrator.plan_started", plan_id=plan_id, stage_count=len(stages), team=plan_team)
66
- if start:
67
- outcome = _dispatch_stage(workspace, state, stages[0], event_log)
68
- if outcome.get("status") == "halted":
69
- return outcome
70
- return {
71
- "ok": True,
72
- "status": state.get("status", "running"),
73
- "plan_id": plan_id,
74
- "current_stage": state["current_stage"],
75
- "state_path": str(state_path(workspace, plan_id)),
76
- }
77
-
78
-
79
- def handle_report_result(workspace: Path, envelope: dict[str, Any]) -> dict[str, Any]:
80
- workspace = Path(workspace)
81
- event_log = EventLog(workspace)
82
- matched = None
83
- for state in list_plan_states(workspace):
84
- if state.get("status") != "running":
85
- continue
86
- idx = int(state.get("current_stage") or 1) - 1
87
- stages = state.get("stages") or []
88
- if idx < 0 or idx >= len(stages):
89
- continue
90
- stage = stages[idx]
91
- if not stage_matches_result(stage, envelope, current_dispatch=state.get("current_dispatch")):
92
- continue
93
- matched = state
94
- break
95
- if matched is None:
96
- return {"ok": True, "status": "no_match", "matched": False}
97
- idx = int(matched["current_stage"]) - 1
98
- stages = matched["stages"]
99
- stage = stages[idx]
100
- halt_expr = stage.get("halt_on")
101
- advance_expr = stage.get("advance_on")
102
- try:
103
- halt_hit = bool(halt_expr) and evaluate_condition(str(halt_expr), envelope)
104
- except InvalidPlanError as exc:
105
- return _halt_plan(workspace, matched, stage, envelope, f"invalid_condition: halt_on {exc.expr!r}", event_log)
106
- if halt_hit:
107
- return _halt_plan(workspace, matched, stage, envelope, str(halt_expr), event_log)
108
- try:
109
- advance_hit = bool(advance_expr) and evaluate_condition(str(advance_expr), envelope)
110
- except InvalidPlanError as exc:
111
- return _halt_plan(workspace, matched, stage, envelope, f"invalid_condition: advance_on {exc.expr!r}", event_log)
112
- if advance_hit:
113
- matched["completed_stages"].append(matched["current_stage"])
114
- matched["current_stage"] += 1
115
- matched["current_dispatch"] = None
116
- if matched["current_stage"] > len(stages):
117
- matched["status"] = "completed"
118
- matched["completed_at"] = datetime.now(timezone.utc).isoformat()
119
- save_plan_state(workspace, matched)
120
- event_log.write("orchestrator.plan_completed", plan_id=matched["plan_id"])
121
- return {
122
- "ok": True,
123
- "status": "completed",
124
- "plan_id": matched["plan_id"],
125
- "current_stage": matched["current_stage"],
126
- }
127
- save_plan_state(workspace, matched)
128
- next_stage = stages[matched["current_stage"] - 1]
129
- outcome = _dispatch_stage(workspace, matched, next_stage, event_log)
130
- if outcome.get("status") == "halted":
131
- return outcome
132
- return {
133
- "ok": True,
134
- "status": "running",
135
- "plan_id": matched["plan_id"],
136
- "current_stage": matched["current_stage"],
137
- }
138
- return {
139
- "ok": True,
140
- "status": "waiting",
141
- "plan_id": matched["plan_id"],
142
- "current_stage": matched["current_stage"],
143
- "matched": True,
144
- }
145
-
146
-
147
- def resume_plans(workspace: Path) -> dict[str, Any]:
148
- workspace = Path(workspace)
149
- return {"ok": True, "plans": list_plan_states(workspace)}
150
-
151
-
152
- def halt_plan(workspace: Path, plan_id: str, *, reason: str = "user_requested") -> dict[str, Any]:
153
- workspace = Path(workspace)
154
- try:
155
- safe_id = sanitize_plan_id(plan_id)
156
- except InvalidPlanIdError as exc:
157
- return {"ok": False, "error": str(exc), "reason": "invalid_plan_id", "plan_id": str(plan_id)}
158
- state = load_plan_state(workspace, safe_id)
159
- if state is None:
160
- return {"ok": False, "error": "plan not found", "plan_id": safe_id}
161
- if state.get("status") != "running":
162
- return {
163
- "ok": True,
164
- "plan_id": safe_id,
165
- "status": state.get("status"),
166
- "halt_reason": state.get("halt_reason"),
167
- "halt_artifact": state.get("halt_artifact"),
168
- "already_terminal": True,
169
- }
170
- event_log = EventLog(workspace)
171
- idx = int(state.get("current_stage") or 1) - 1
172
- stages = state.get("stages") or []
173
- stage = stages[idx] if 0 <= idx < len(stages) else {"id": "unknown"}
174
- return _halt_plan(workspace, state, stage, {"reason": reason}, reason, event_log)
175
-
176
-
177
- def plan_status(workspace: Path, plan_id: str | None = None) -> dict[str, Any]:
178
- workspace = Path(workspace)
179
- plans = list_plan_states(workspace)
180
- if plan_id is not None:
181
- try:
182
- safe_id = sanitize_plan_id(plan_id)
183
- except InvalidPlanIdError as exc:
184
- return {"ok": False, "error": str(exc), "reason": "invalid_plan_id", "plan_id": str(plan_id)}
185
- match = next((state for state in plans if state.get("plan_id") == safe_id), None)
186
- if match is None:
187
- return {"ok": False, "error": "plan not found", "plan_id": safe_id}
188
- return {"ok": True, "plan": match}
189
- return {"ok": True, "plans": plans}
190
-
191
-
192
- def _text_or_none(value: Any) -> str | None:
193
- if value is None:
194
- return None
195
- text = str(value).strip()
196
- return text or None
197
-
198
-
199
- def _dispatch_stage(
200
- workspace: Path,
201
- state: dict[str, Any],
202
- stage: dict[str, Any],
203
- event_log: EventLog,
204
- ) -> dict[str, Any]:
205
- from team_agent.messaging.internal_delivery import deliver_stored_message
206
- dispatch = stage.get("dispatch") or {}
207
- to = dispatch.get("to")
208
- content = dispatch.get("content")
209
- if not to or content is None:
210
- event_log.write(
211
- "orchestrator.stage_dispatch_skipped",
212
- plan_id=state["plan_id"],
213
- stage_id=stage.get("id"),
214
- reason="missing dispatch fields",
215
- )
216
- return _halt_plan(
217
- workspace,
218
- state,
219
- stage,
220
- {"reason": "missing dispatch fields"},
221
- "dispatch_misconfigured",
222
- event_log,
223
- )
224
- stage_team = _text_or_none(stage.get("team")) or _text_or_none(state.get("team"))
225
- dispatch_task_id = _text_or_none(dispatch.get("task_id"))
226
- event_log.write(
227
- "orchestrator.stage_dispatch_internal",
228
- plan_id=state["plan_id"],
229
- stage_id=stage.get("id"),
230
- to=to,
231
- team=stage_team,
232
- delivery="internal_delivery.deliver_stored_message",
233
- owner_gate="bypassed_framework_internal",
234
- )
235
- try:
236
- result = deliver_stored_message(
237
- workspace,
238
- to,
239
- str(content),
240
- task_id=dispatch_task_id,
241
- sender="orchestrator",
242
- requires_ack=False,
243
- wait_visible=False,
244
- team=stage_team,
245
- )
246
- except Exception as exc:
247
- event_log.write(
248
- "orchestrator.stage_dispatch_failed",
249
- plan_id=state["plan_id"],
250
- stage_id=stage.get("id"),
251
- error=str(exc),
252
- )
253
- return _halt_plan(
254
- workspace,
255
- state,
256
- stage,
257
- {"error": str(exc)},
258
- "dispatch_failed",
259
- event_log,
260
- )
261
- if not result.get("ok"):
262
- event_log.write(
263
- "orchestrator.stage_dispatch_refused",
264
- plan_id=state["plan_id"],
265
- stage_id=stage.get("id"),
266
- send_status=result.get("status"),
267
- send_reason=result.get("reason"),
268
- )
269
- return _halt_plan(
270
- workspace,
271
- state,
272
- stage,
273
- {"send_result": result},
274
- f"dispatch_refused:{result.get('reason') or result.get('status') or 'unknown'}",
275
- event_log,
276
- )
277
- state["current_dispatch"] = {
278
- "stage_id": stage.get("id"),
279
- "to": to,
280
- "message_id": result.get("message_id"),
281
- "task_id": dispatch.get("task_id"),
282
- "team": stage_team,
283
- "dispatched_at": datetime.now(timezone.utc).isoformat(),
284
- "send_status": result.get("status"),
285
- }
286
- save_plan_state(workspace, state)
287
- event_log.write(
288
- "orchestrator.stage_dispatched",
289
- plan_id=state["plan_id"],
290
- stage_id=stage.get("id"),
291
- to=to,
292
- team=stage_team,
293
- message_id=result.get("message_id"),
294
- status=result.get("status"),
295
- )
296
- return {
297
- "ok": True,
298
- "status": "running",
299
- "plan_id": state["plan_id"],
300
- "current_stage": state["current_stage"],
301
- }
302
-
303
-
304
- def _halt_plan(
305
- workspace: Path,
306
- state: dict[str, Any],
307
- stage: dict[str, Any],
308
- envelope: dict[str, Any],
309
- halt_reason: str,
310
- event_log: EventLog,
311
- ) -> dict[str, Any]:
312
- now = datetime.now(timezone.utc)
313
- ts = now.strftime("%Y%m%dT%H%M%SZ")
314
- artifact = artifact_path(workspace, state["plan_id"], ts)
315
- artifact.parent.mkdir(parents=True, exist_ok=True)
316
- artifact.write_text(_format_halt_artifact(state, stage, envelope, halt_reason, now), encoding="utf-8")
317
- state["status"] = "halted"
318
- state["halt_reason"] = halt_reason
319
- state["halt_artifact"] = str(artifact)
320
- state["halted_at"] = now.isoformat()
321
- state["halt_envelope"] = envelope
322
- save_plan_state(workspace, state)
323
- event_log.write(
324
- "orchestrator.plan_halted",
325
- plan_id=state["plan_id"],
326
- stage_id=stage.get("id"),
327
- halt_reason=halt_reason,
328
- artifact=str(artifact),
329
- )
330
- return {
331
- "ok": True,
332
- "status": "halted",
333
- "plan_id": state["plan_id"],
334
- "current_stage": state["current_stage"],
335
- "halt_reason": halt_reason,
336
- "halt_artifact": str(artifact),
337
- }
338
-
339
-
340
- def _format_halt_artifact(
341
- state: dict[str, Any],
342
- stage: dict[str, Any],
343
- envelope: dict[str, Any],
344
- halt_reason: str,
345
- now: datetime,
346
- ) -> str:
347
- lines = [
348
- f"# Plan halt: {state['plan_id']}",
349
- "",
350
- f"Stage: {stage.get('id', state['current_stage'])}",
351
- f"Halt reason: {halt_reason}",
352
- f"Halted at: {now.isoformat()}",
353
- "",
354
- "## Stage definition",
355
- "",
356
- "```json",
357
- json.dumps(stage, indent=2, ensure_ascii=False),
358
- "```",
359
- "",
360
- "## Report envelope",
361
- "",
362
- "```json",
363
- json.dumps(envelope, indent=2, ensure_ascii=False),
364
- "```",
365
- "",
366
- ]
367
- return "\n".join(lines)
368
-
369
-
370
- __all__ = [
371
- "halt_plan",
372
- "handle_report_result",
373
- "plan_status",
374
- "resume_plans",
375
- "start_plan",
376
- ]
@@ -1,122 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import re
4
- from pathlib import Path
5
- from typing import Any
6
-
7
- from team_agent import simple_yaml
8
-
9
- _CONDITION_RE = re.compile(
10
- r"^\s*report_result\.(\w+)\s*==\s*['\"]([^'\"]+)['\"]\s*$"
11
- )
12
-
13
-
14
- class InvalidPlanError(ValueError):
15
- def __init__(self, reason: str, *, plan_id: str | None = None, stage_id: str | None = None, expr: str | None = None) -> None:
16
- self.reason = reason
17
- self.plan_id = plan_id
18
- self.stage_id = stage_id
19
- self.expr = expr
20
- parts = [reason]
21
- if plan_id:
22
- parts.append(f"plan_id={plan_id}")
23
- if stage_id:
24
- parts.append(f"stage_id={stage_id}")
25
- if expr is not None:
26
- parts.append(f"expr={expr!r}")
27
- super().__init__(" ".join(parts))
28
-
29
-
30
- def load_plan(plan_path: Path) -> dict[str, Any]:
31
- text = Path(plan_path).read_text(encoding="utf-8")
32
- data = simple_yaml.loads(text)
33
- if not isinstance(data, dict):
34
- raise InvalidPlanError("invalid_plan: top-level must be a YAML mapping")
35
- plan_id = str(data.get("id") or "").strip()
36
- if not plan_id:
37
- raise InvalidPlanError("invalid_plan: missing 'id'")
38
- stages = data.get("stages")
39
- if not isinstance(stages, list) or not stages:
40
- raise InvalidPlanError("invalid_plan: stages must be a non-empty list", plan_id=plan_id)
41
- for stage in stages:
42
- if not isinstance(stage, dict):
43
- raise InvalidPlanError("invalid_plan: each stage must be a mapping", plan_id=plan_id)
44
- stage_id = str(stage.get("id") or "").strip() or None
45
- for key in ("advance_on", "halt_on"):
46
- expr = stage.get(key)
47
- if expr is None:
48
- continue
49
- text_expr = str(expr).strip()
50
- if not _is_supported_condition(text_expr):
51
- raise InvalidPlanError(
52
- f"invalid_condition: {key} grammar must be \"any\" or "
53
- "\"report_result.<field> == '<value>'\"",
54
- plan_id=plan_id,
55
- stage_id=stage_id,
56
- expr=text_expr,
57
- )
58
- return data
59
-
60
-
61
- def stage_matches_result(
62
- stage: dict[str, Any],
63
- envelope: dict[str, Any],
64
- *,
65
- current_dispatch: dict[str, Any] | None = None,
66
- ) -> bool:
67
- agent_id = str(envelope.get("agent_id") or "").strip()
68
- task_id = str(envelope.get("task_id") or "").strip()
69
- if current_dispatch:
70
- expected_agent = str(current_dispatch.get("to") or "").strip()
71
- expected_task = str(current_dispatch.get("task_id") or "").strip()
72
- if expected_agent and expected_agent != agent_id:
73
- return False
74
- if expected_task:
75
- return bool(task_id) and expected_task == task_id
76
- if expected_agent:
77
- stage_id = str(stage.get("id") or "").strip()
78
- if stage_id and task_id and stage_id != task_id:
79
- return False
80
- return True
81
- return False
82
- dispatch = stage.get("dispatch") or {}
83
- expected_to = str(dispatch.get("to") or "").strip()
84
- stage_id = str(stage.get("id") or "").strip()
85
- if expected_to and agent_id and expected_to == agent_id:
86
- return True
87
- if stage_id and task_id and stage_id == task_id:
88
- return True
89
- return False
90
-
91
-
92
- def _is_supported_condition(expr: str) -> bool:
93
- text = (expr or "").strip()
94
- if not text:
95
- return False
96
- if text.lower() == "any":
97
- return True
98
- return bool(_CONDITION_RE.match(text))
99
-
100
-
101
- def evaluate_condition(expr: str, envelope: dict[str, Any]) -> bool:
102
- text = (expr or "").strip()
103
- if not text:
104
- return False
105
- if text.lower() == "any":
106
- return True
107
- match = _CONDITION_RE.match(text)
108
- if not match:
109
- raise InvalidPlanError("invalid_condition", expr=text)
110
- field, expected = match.group(1), match.group(2)
111
- actual = envelope.get(field)
112
- if actual is None:
113
- return False
114
- return str(actual) == expected
115
-
116
-
117
- __all__ = [
118
- "InvalidPlanError",
119
- "evaluate_condition",
120
- "load_plan",
121
- "stage_matches_result",
122
- ]
@@ -1,128 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import os
5
- import re
6
- import tempfile
7
- from contextlib import contextmanager
8
- from pathlib import Path
9
- from typing import Any
10
-
11
- _PLAN_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9_.-]{0,63}$")
12
-
13
-
14
- class InvalidPlanIdError(ValueError):
15
- pass
16
-
17
-
18
- def sanitize_plan_id(plan_id: Any) -> str:
19
- text = "" if plan_id is None else str(plan_id).strip()
20
- if not _PLAN_ID_RE.match(text):
21
- raise InvalidPlanIdError(
22
- f"invalid plan id {text!r}: must match [A-Za-z0-9][A-Za-z0-9_.-]{{0,63}} "
23
- "(no slashes, no spaces, no path-traversal characters)"
24
- )
25
- return text
26
-
27
-
28
- def _orchestrator_runtime_dir(workspace: Path) -> Path:
29
- return Path(workspace) / ".team" / "runtime" / "orchestrator"
30
-
31
-
32
- def _orchestrator_artifact_dir(workspace: Path) -> Path:
33
- return Path(workspace) / ".team" / "artifacts" / "orchestrator"
34
-
35
-
36
- def state_path(workspace: Path, plan_id: str) -> Path:
37
- safe = sanitize_plan_id(plan_id)
38
- return _orchestrator_runtime_dir(workspace) / f"plan-{safe}.state.json"
39
-
40
-
41
- def artifact_path(workspace: Path, plan_id: str, ts: str) -> Path:
42
- safe = sanitize_plan_id(plan_id)
43
- return _orchestrator_artifact_dir(workspace) / f"halt-{safe}-{ts}.md"
44
-
45
-
46
- @contextmanager
47
- def _plan_state_lock(workspace: Path, plan_id: str, timeout: float = 5.0):
48
- import fcntl
49
- import time
50
- safe = sanitize_plan_id(plan_id)
51
- lock_dir = _orchestrator_runtime_dir(workspace)
52
- lock_dir.mkdir(parents=True, exist_ok=True)
53
- lock_path = lock_dir / f"plan-{safe}.lock"
54
- deadline = time.monotonic() + timeout
55
- with lock_path.open("w", encoding="utf-8") as lock_file:
56
- while True:
57
- try:
58
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
59
- break
60
- except BlockingIOError:
61
- if time.monotonic() >= deadline:
62
- raise RuntimeError(
63
- f"orchestrator plan {safe} state file is locked by another process; retry"
64
- )
65
- time.sleep(0.05)
66
- try:
67
- yield
68
- finally:
69
- fcntl.flock(lock_file.fileno(), fcntl.LOCK_UN)
70
-
71
-
72
- def load_plan_state(workspace: Path, plan_id: str) -> dict[str, Any] | None:
73
- path = state_path(workspace, plan_id)
74
- if not path.exists():
75
- return None
76
- with _plan_state_lock(workspace, plan_id):
77
- try:
78
- return json.loads(path.read_text(encoding="utf-8"))
79
- except json.JSONDecodeError:
80
- return None
81
-
82
-
83
- def save_plan_state(workspace: Path, state: dict[str, Any]) -> Path:
84
- plan_id = sanitize_plan_id(state.get("plan_id"))
85
- path = state_path(workspace, plan_id)
86
- path.parent.mkdir(parents=True, exist_ok=True)
87
- payload = json.dumps(state, indent=2, ensure_ascii=False, sort_keys=True)
88
- with _plan_state_lock(workspace, plan_id):
89
- fd, tmp_name = tempfile.mkstemp(
90
- prefix=f".plan-{plan_id}.", suffix=".tmp", dir=str(path.parent)
91
- )
92
- try:
93
- with os.fdopen(fd, "w", encoding="utf-8") as tmp_file:
94
- tmp_file.write(payload)
95
- tmp_file.flush()
96
- os.fsync(tmp_file.fileno())
97
- os.replace(tmp_name, path)
98
- except Exception:
99
- try:
100
- os.unlink(tmp_name)
101
- except FileNotFoundError:
102
- pass
103
- raise
104
- return path
105
-
106
-
107
- def list_plan_states(workspace: Path) -> list[dict[str, Any]]:
108
- directory = _orchestrator_runtime_dir(workspace)
109
- if not directory.exists():
110
- return []
111
- out: list[dict[str, Any]] = []
112
- for path in sorted(directory.glob("plan-*.state.json")):
113
- try:
114
- out.append(json.loads(path.read_text(encoding="utf-8")))
115
- except json.JSONDecodeError:
116
- continue
117
- return out
118
-
119
-
120
- __all__ = [
121
- "InvalidPlanIdError",
122
- "artifact_path",
123
- "list_plan_states",
124
- "load_plan_state",
125
- "sanitize_plan_id",
126
- "save_plan_state",
127
- "state_path",
128
- ]
@@ -1,45 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pathlib import Path
4
-
5
-
6
- PACKAGE_ROOT = Path(__file__).resolve().parents[2]
7
-
8
-
9
- def repo_root() -> Path:
10
- return PACKAGE_ROOT
11
-
12
-
13
- def schema_path(name: str) -> Path:
14
- return repo_root() / "schemas" / name
15
-
16
-
17
- def template_path(name: str) -> Path:
18
- return repo_root() / "templates" / name
19
-
20
-
21
- def example_path(name: str) -> Path:
22
- return repo_root() / "examples" / name
23
-
24
-
25
- def runtime_dir(workspace: Path) -> Path:
26
- return workspace / ".team" / "runtime"
27
-
28
-
29
- def logs_dir(workspace: Path) -> Path:
30
- return workspace / ".team" / "logs"
31
-
32
-
33
- def artifacts_dir(workspace: Path) -> Path:
34
- return workspace / ".team" / "artifacts"
35
-
36
-
37
- def messages_dir(workspace: Path) -> Path:
38
- return workspace / ".team" / "messages"
39
-
40
-
41
- def team_workspace(team_dir: Path) -> Path:
42
- team_dir = team_dir.resolve()
43
- if team_dir.parent.name == ".team":
44
- return team_dir.parent.parent
45
- return team_dir.parent