@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,218 +0,0 @@
1
- """Provider-neutral take-over reminder predicate (Gap 32 §3).
2
-
3
- Consumes already-classified node states only. Contains no provider knowledge.
4
- Rules: arm only after a worker has opened a turn (C1); fire one neutral ping when
5
- every node is idle for a monotonic debounce window (C2/C11); idle_interrupted
6
- counts as idle but is annotated (C12); re-arm on a real turn-open edge (C3).
7
- """
8
-
9
- from __future__ import annotations
10
-
11
- from typing import Any
12
-
13
- _IDLE_STATES = {"idle", "idle_interrupted"}
14
- _DELEGATED_STATES = {"working", "blocked_on_human", "idle_interrupted", "abnormal"}
15
-
16
- _ARM_KEY = "opened_worker_turn_since_ack"
17
- _SUPPRESS_KEY = "suppressed"
18
-
19
-
20
- def evaluate_takeover_reminder(
21
- nodes: list[dict[str, Any]],
22
- *,
23
- monitor_state: dict[str, Any] | None,
24
- now_monotonic: float,
25
- debounce_seconds: float,
26
- suspend_intervals: list[tuple[float, float]] | None = None,
27
- event_sink: Any = None,
28
- ) -> dict[str, Any]:
29
- state = dict(monitor_state or {})
30
- state.setdefault(_ARM_KEY, False)
31
- state.setdefault(_SUPPRESS_KEY, False)
32
- state.setdefault("all_idle_since", None)
33
- state.setdefault("pinged_for_episode", None)
34
-
35
- # C1: a WORKER turn-open (working / blocked / interrupted / faulted) is the
36
- # only thing that arms the watch. Leader-only activity never arms it.
37
- for node in nodes:
38
- if _role(node) == "leader":
39
- continue
40
- if node.get("state") in _DELEGATED_STATES:
41
- state[_ARM_KEY] = True
42
-
43
- # Any non-idle node blocks the ping; report which kind (C5 unknown / C14 working).
44
- for node in nodes:
45
- node_state = node.get("state")
46
- if node_state not in _IDLE_STATES:
47
- state["all_idle_since"] = None
48
- state["pinged_for_episode"] = None
49
- return _result(False, None, f"node_{node_state or 'unknown'}", _interrupted(nodes), state, event_sink=event_sink, node=node)
50
-
51
- if not nodes:
52
- return _result(False, None, "no_nodes", [], state, event_sink=event_sink)
53
-
54
- if state.get("all_idle_since") is None:
55
- state["all_idle_since"] = now_monotonic
56
- state["pinged_for_episode"] = None
57
- elapsed = _active_elapsed(state["all_idle_since"], now_monotonic, suspend_intervals)
58
- interrupted = _interrupted(nodes)
59
-
60
- if not state.get(_ARM_KEY):
61
- return _result(False, None, "not_armed_no_worker_turn", interrupted, state, event_sink=event_sink)
62
- if state.get(_SUPPRESS_KEY):
63
- return _result(False, None, "acknowledged", interrupted, state, event_sink=event_sink)
64
- if elapsed < debounce_seconds:
65
- return _result(False, None, "debounce_active", interrupted, state, event_sink=event_sink)
66
- if state.get("pinged_for_episode") == state.get("all_idle_since"):
67
- return _result(False, None, "already_pinged_this_episode", interrupted, state, event_sink=event_sink)
68
-
69
- state["pinged_for_episode"] = state["all_idle_since"]
70
- message = _neutral_message(len(nodes), elapsed, interrupted)
71
- _emit(event_sink, "idle_takeover.ping", nodes=len(nodes), elapsed_seconds=int(elapsed), interrupted=[i["node_id"] for i in interrupted])
72
- return _result(True, message, "all_idle_debounce_elapsed", interrupted, state, event_sink=event_sink)
73
-
74
-
75
- def record_turn_open_after_delivery(
76
- monitor_state: dict[str, Any] | None,
77
- *,
78
- node_id: str,
79
- turn_id: str | None,
80
- delivered_message_id: str | None,
81
- now_monotonic: float,
82
- event_sink: Any = None,
83
- ) -> dict[str, Any]:
84
- """A delivered inbound message produced a real turn-open edge (C3).
85
-
86
- Re-arms a previously acknowledged watch so delivered-but-unprocessed work
87
- can never leave it permanently suppressed. Returns the updated monitor_state
88
- directly (with the re-arm flags set).
89
- """
90
- state = dict(monitor_state or {})
91
- state[_ARM_KEY] = True
92
- state[_SUPPRESS_KEY] = False
93
- state["all_idle_since"] = None
94
- state["pinged_for_episode"] = None
95
- state["last_turn_open"] = {
96
- "node_id": node_id,
97
- "turn_id": turn_id,
98
- "delivered_message_id": delivered_message_id,
99
- "at": now_monotonic,
100
- }
101
- state["ok"] = True
102
- state["rearmed"] = True
103
- _emit(event_sink, "idle_takeover.turn_open_rearmed", node_id=node_id, turn_id=turn_id, delivered_message_id=delivered_message_id)
104
- return state
105
-
106
-
107
- def _role(node: dict[str, Any]) -> str:
108
- return str(node.get("role") or ("leader" if node.get("is_leader") else "worker"))
109
-
110
-
111
- def _interrupted(nodes: list[dict[str, Any]]) -> list[dict[str, Any]]:
112
- return [
113
- {
114
- "node_id": n.get("node_id"),
115
- "node": n.get("node_id"),
116
- "state": "idle_interrupted",
117
- "reason": "interrupted",
118
- "interrupted": True,
119
- "kind": "interrupted",
120
- "type": "interrupted",
121
- "annotation": "interrupted",
122
- }
123
- for n in nodes
124
- if n.get("state") == "idle_interrupted"
125
- ]
126
-
127
-
128
- def _active_elapsed(start: float, now: float, suspend_intervals: list[tuple[float, float]] | None) -> float:
129
- elapsed = max(0.0, now - start)
130
- if not suspend_intervals:
131
- return elapsed
132
- # C11: clip each window to [start, now], then MERGE overlapping/duplicate
133
- # windows before subtracting, so an overlap is never counted twice.
134
- clipped: list[tuple[float, float]] = []
135
- for interval in suspend_intervals:
136
- try:
137
- s, e = float(interval[0]), float(interval[1])
138
- except (TypeError, ValueError, IndexError):
139
- continue
140
- lo = max(s, start)
141
- hi = min(e, now)
142
- if hi > lo:
143
- clipped.append((lo, hi))
144
- suspended = 0.0
145
- for lo, hi in _merge_intervals(clipped):
146
- suspended += hi - lo
147
- return max(0.0, elapsed - suspended)
148
-
149
-
150
- def _merge_intervals(intervals: list[tuple[float, float]]) -> list[tuple[float, float]]:
151
- if not intervals:
152
- return []
153
- ordered = sorted(intervals)
154
- merged: list[tuple[float, float]] = [ordered[0]]
155
- for lo, hi in ordered[1:]:
156
- last_lo, last_hi = merged[-1]
157
- if lo <= last_hi: # overlapping or touching → merge
158
- merged[-1] = (last_lo, max(last_hi, hi))
159
- else:
160
- merged.append((lo, hi))
161
- return merged
162
-
163
-
164
- def _neutral_message(node_count: int, elapsed: float, interrupted: list[dict[str, Any]]) -> str:
165
- minutes = max(1, int(round(elapsed / 60.0)))
166
- base = (
167
- f"All nodes idle: {node_count} team nodes have had every turn closed for "
168
- f"about {minutes} min. If this idle state is intentional, run "
169
- f"team-agent acknowledge-idle to confirm it."
170
- )
171
- if interrupted:
172
- ids = ", ".join(str(i["node_id"]) for i in interrupted)
173
- base += f" Interrupted nodes: {ids}."
174
- return base
175
-
176
-
177
- def _result(
178
- should_ping: bool,
179
- message: str | None,
180
- reason: str,
181
- annotations: list[dict[str, Any]],
182
- state: dict[str, Any],
183
- *,
184
- event_sink: Any = None,
185
- node: dict[str, Any] | None = None,
186
- ) -> dict[str, Any]:
187
- if not should_ping and state.get("last_no_ping_reason") != reason:
188
- state["last_no_ping_reason"] = reason
189
- _emit(
190
- event_sink,
191
- "idle_takeover.no_ping",
192
- reason=reason,
193
- node_id=(node or {}).get("node_id"),
194
- armed=bool(state.get(_ARM_KEY)),
195
- )
196
- return {
197
- "should_ping": should_ping,
198
- "message": message,
199
- "reason": reason,
200
- "annotations": list(annotations),
201
- "interrupted_nodes": [a.get("node_id") for a in annotations],
202
- "interrupted": [a.get("node_id") for a in annotations],
203
- "monitor_state": state,
204
- }
205
-
206
-
207
- def _emit(event_sink: Any, name: str, **fields: Any) -> None:
208
- if event_sink is None:
209
- return
210
- try:
211
- event_sink(name, fields)
212
- except TypeError:
213
- try:
214
- event_sink({"event": name, **fields})
215
- except Exception:
216
- pass
217
- except Exception:
218
- pass
@@ -1,59 +0,0 @@
1
- """Gap 32 idle/take-over public facade.
2
-
3
- Thin surface that the runtime + acceptance contract import. Provider dispatch
4
- lives in ``provider_state``; the predicate / abnormal / wake logic lives in the
5
- provider-neutral modules. This module only wires them together.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- from typing import Any
11
-
12
- from team_agent.abnormal_track import detect_whole_team_gone, process_abnormal_records
13
- from team_agent.idle_predicate import evaluate_takeover_reminder, record_turn_open_after_delivery
14
- from team_agent.provider_state import read_turn_state
15
- from team_agent.provider_state.registry import get_provider_registry
16
-
17
-
18
- def classify_provider_turn_state(
19
- provider: str,
20
- session_log_text: str,
21
- *,
22
- process: Any = None,
23
- file_silence_seconds: float = 0,
24
- registry: Any = None,
25
- event_sink: Any = None,
26
- ) -> dict[str, Any]:
27
- """Classify one node's turn state from its provider session-log text."""
28
- result = read_turn_state(
29
- provider,
30
- session_log_text,
31
- process=process,
32
- file_silence_seconds=file_silence_seconds,
33
- registry=registry,
34
- )
35
- if event_sink is not None and result.get("state") in {"unknown", "abnormal"}:
36
- _emit(event_sink, "idle_takeover.classify", provider=provider, state=result.get("state"), reason=result.get("reason"))
37
- return result
38
-
39
-
40
- __all__ = [
41
- "classify_provider_turn_state",
42
- "evaluate_takeover_reminder",
43
- "record_turn_open_after_delivery",
44
- "process_abnormal_records",
45
- "detect_whole_team_gone",
46
- "get_provider_registry",
47
- ]
48
-
49
-
50
- def _emit(event_sink: Any, name: str, **fields: Any) -> None:
51
- try:
52
- event_sink(name, fields)
53
- except TypeError:
54
- try:
55
- event_sink({"event": name, **fields})
56
- except Exception:
57
- pass
58
- except Exception:
59
- pass
@@ -1,114 +0,0 @@
1
- """Gap 32 runtime wiring: drive the file-fact idle/takeover reminder from the
2
- coordinator tick. This is the glue that replaces the legacy screen-scrape
3
- `detect_idle_fallbacks` path — it classifies each node from its provider
4
- session-log file (never the pane) and runs the provider-neutral predicate.
5
- """
6
-
7
- from __future__ import annotations
8
-
9
- import time
10
- from pathlib import Path
11
- from typing import Any
12
-
13
- _TAIL_BYTES = 131072
14
- _DEBOUNCE_SECONDS = 60.0
15
-
16
-
17
- IDLE_DEBOUNCE_SECONDS = _DEBOUNCE_SECONDS
18
-
19
-
20
- def build_idle_nodes(state: dict[str, Any]) -> list[dict[str, Any]]:
21
- """Classify every live node from its provider session-log file fact (never
22
- the pane screen, never message-row status). The leader is read via its own
23
- transcript when its path is tracked (C13), else omitted rather than guessed.
24
- """
25
- from team_agent.provider_state import read_turn_state
26
-
27
- nodes: list[dict[str, Any]] = []
28
- for agent_id, agent_state in (state.get("agents") or {}).items():
29
- if str(agent_state.get("status") or "") in {"stopped", "paused"}:
30
- continue
31
- provider = str(agent_state.get("provider") or "")
32
- classification = read_turn_state(provider, _read_session_tail(agent_state.get("rollout_path")))
33
- nodes.append({
34
- "node_id": agent_id,
35
- "role": "worker",
36
- "state": classification.get("state"),
37
- "turn_id": classification.get("turn_id"),
38
- "annotations": classification.get("annotations"),
39
- "provider": provider,
40
- "auth_mode": agent_state.get("auth_mode"),
41
- "rollout_path": agent_state.get("rollout_path"),
42
- })
43
- leader_node = _leader_node(state)
44
- if leader_node is not None:
45
- nodes.append(leader_node)
46
- return nodes
47
-
48
-
49
- def push_idle_reminder(workspace: Path, state: dict[str, Any], event_log: Any, result: dict[str, Any]) -> None:
50
- """Deliver the one neutral take-over reminder to the leader when the
51
- predicate fired. No-op otherwise."""
52
- if not result.get("should_ping"):
53
- return
54
- from team_agent.messaging.internal_delivery import deliver_stored_message
55
- from team_agent.state import team_state_key
56
-
57
- leader_id = (state.get("leader") or {}).get("id") or "leader"
58
- try:
59
- deliver_stored_message(
60
- workspace,
61
- leader_id,
62
- result["message"],
63
- sender="coordinator",
64
- requires_ack=False,
65
- wait_visible=False,
66
- team=team_state_key(state),
67
- )
68
- except Exception as exc:
69
- event_log.write("idle_takeover.push_failed", error=str(exc))
70
- event_log.write(
71
- "idle_takeover.reminder",
72
- interrupted=result.get("interrupted_nodes"),
73
- reason=result.get("reason"),
74
- )
75
-
76
-
77
- def _leader_node(state: dict[str, Any]) -> dict[str, Any] | None:
78
- """Best-effort leader node from its own provider transcript (C13). If the
79
- leader's session file path is not tracked, the leader is omitted rather than
80
- guessed — the predicate then evaluates the workers (the leader is the ping
81
- recipient and acts on the reminder regardless)."""
82
- from team_agent.provider_state import read_turn_state
83
-
84
- leader = state.get("leader") if isinstance(state.get("leader"), dict) else {}
85
- receiver = state.get("leader_receiver") if isinstance(state.get("leader_receiver"), dict) else {}
86
- path = leader.get("rollout_path") or receiver.get("rollout_path")
87
- provider = str(leader.get("provider") or receiver.get("provider") or "")
88
- if not path or not provider:
89
- return None
90
- classification = read_turn_state(provider, _read_session_tail(path))
91
- return {
92
- "node_id": leader.get("id") or "leader",
93
- "role": "leader",
94
- "state": classification.get("state"),
95
- "turn_id": classification.get("turn_id"),
96
- "annotations": classification.get("annotations"),
97
- }
98
-
99
-
100
- def _read_session_tail(path: Any, max_bytes: int = _TAIL_BYTES) -> str:
101
- if not path:
102
- return ""
103
- try:
104
- p = Path(str(path))
105
- size = p.stat().st_size
106
- with p.open("rb") as handle:
107
- if size > max_bytes:
108
- handle.seek(size - max_bytes)
109
- # drop a possibly-partial first line after seeking mid-file
110
- handle.readline()
111
- data = handle.read()
112
- return data.decode("utf-8", errors="ignore")
113
- except (OSError, ValueError):
114
- return ""
@@ -1,41 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from team_agent.launch.bootstrap import (
4
- attach_team_profile_dirs,
5
- compile_team_dir_spec,
6
- init_workspace,
7
- is_team_doc_dir,
8
- spec_team_dir,
9
- tmux_session_conflict_error,
10
- validate_file,
11
- )
12
- from team_agent.launch.config import (
13
- DANGEROUS_LEADER_FLAGS,
14
- command_has_flag,
15
- detect_inherited_dangerous_permissions,
16
- effective_runtime_config,
17
- process_ancestry,
18
- process_info,
19
- requires_direct_leader_receiver,
20
- )
21
- from team_agent.launch.core import launch
22
- from team_agent.launch.requirements import ensure_agent_start_requirements
23
-
24
- __all__ = [
25
- "DANGEROUS_LEADER_FLAGS",
26
- "attach_team_profile_dirs",
27
- "command_has_flag",
28
- "compile_team_dir_spec",
29
- "detect_inherited_dangerous_permissions",
30
- "effective_runtime_config",
31
- "ensure_agent_start_requirements",
32
- "init_workspace",
33
- "is_team_doc_dir",
34
- "launch",
35
- "process_ancestry",
36
- "process_info",
37
- "requires_direct_leader_receiver",
38
- "spec_team_dir",
39
- "tmux_session_conflict_error",
40
- "validate_file",
41
- ]
@@ -1,85 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pathlib import Path
4
- from typing import Any
5
-
6
- from team_agent.events import EventLog
7
- from team_agent.simple_yaml import dumps
8
- from team_agent.spec import load_spec, workspace_from_spec
9
-
10
-
11
- def init_workspace(workspace: Path, force: bool = False) -> dict[str, Path]:
12
- from team_agent.runtime import ensure_workspace_dirs
13
- from team_agent.paths import example_path, template_path
14
-
15
- ensure_workspace_dirs(workspace)
16
- team_dir = workspace / ".team" / "current"
17
- team_dir.mkdir(parents=True, exist_ok=True)
18
- spec_path = team_dir / "team.spec.yaml"
19
- state_path = workspace / "team_state.md"
20
- if spec_path.exists() and not force:
21
- from team_agent.runtime import RuntimeError
22
- raise RuntimeError(f"{spec_path} already exists; pass --force to overwrite")
23
- spec_path.write_text(example_path("team.spec.yaml").read_text(encoding="utf-8"), encoding="utf-8")
24
- if not state_path.exists() or force:
25
- state_path.write_text(template_path("team_state.md").read_text(encoding="utf-8"), encoding="utf-8")
26
- EventLog(workspace).write("init", spec_path=str(spec_path), state_path=str(state_path))
27
- return {"spec": spec_path, "state": state_path}
28
-
29
-
30
- def validate_file(spec_path: Path) -> dict[str, Any]:
31
- if spec_path.is_dir():
32
- from team_agent.compiler import compile_team
33
-
34
- result = compile_team(spec_path)
35
- spec = result["spec"]
36
- return {
37
- "ok": True,
38
- "type": "team_dir",
39
- "workspace": str(Path(spec["team"]["workspace"]).resolve()),
40
- "team": spec["team"]["name"],
41
- "agents": [agent["id"] for agent in spec.get("agents", [])],
42
- }
43
- spec = load_spec(spec_path)
44
- workspace = workspace_from_spec(spec, spec_path)
45
- return {"ok": True, "workspace": str(workspace), "team": spec["team"]["name"]}
46
-
47
-
48
- def tmux_session_conflict_error(session_name: str) -> str:
49
- return (
50
- f"tmux session already exists: {session_name}. "
51
- "Startup will not terminate existing tmux sessions because they may belong to active teams. "
52
- "Use a different team name or runtime.session_name and start again."
53
- )
54
-
55
-
56
- def spec_team_dir(spec_path: Path, workspace: Path) -> Path:
57
- spec_dir = spec_path.resolve().parent
58
- if spec_dir.parent.name == ".team":
59
- return spec_dir
60
- return workspace.resolve() / ".team" / "current"
61
-
62
-
63
- def is_team_doc_dir(team_dir: Path) -> bool:
64
- return (team_dir / "TEAM.md").exists() and (team_dir / "agents").is_dir()
65
-
66
-
67
- def compile_team_dir_spec(team_dir: Path, workspace: Path) -> dict[str, Any]:
68
- from team_agent.compiler import compile_team
69
-
70
- spec_path = team_dir / "team.spec.yaml"
71
- compiled = compile_team(team_dir, spec_path)
72
- if compiled["spec"].get("context", {}).get("state_file") == "team_state.md":
73
- state_file = str(team_dir.relative_to(workspace) / "team_state.md") if team_dir.is_relative_to(workspace) else "team_state.md"
74
- compiled["spec"]["context"]["state_file"] = state_file
75
- spec_path.write_text(dumps(compiled["spec"]), encoding="utf-8")
76
- return compiled
77
-
78
-
79
- def attach_team_profile_dirs(spec: dict[str, Any], spec_path: Path, workspace: Path | None = None, team_dir: Path | None = None) -> None:
80
- workspace = workspace.resolve() if workspace else workspace_from_spec(spec, spec_path)
81
- team_dir = team_dir.resolve() if team_dir else spec_team_dir(spec_path, workspace)
82
- profiles_dir = team_dir / "profiles"
83
- for agent in spec.get("agents", []):
84
- if isinstance(agent, dict) and agent.get("profile"):
85
- agent["_profile_dir"] = str(profiles_dir)
@@ -1,106 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import os
4
- import re
5
- import subprocess
6
- from typing import Any
7
-
8
-
9
- DANGEROUS_LEADER_FLAGS = (
10
- ("claude", "--dangerously-skip-permissions"),
11
- ("claude", "--dangerously-skip-permission"),
12
- ("codex", "--dangerously-bypass-approvals-and-sandbox"),
13
- )
14
-
15
-
16
- def effective_runtime_config(runtime_cfg: dict[str, Any]) -> dict[str, Any]:
17
- # Route via the runtime alias surface so tests patching
18
- # team_agent.runtime._detect_inherited_dangerous_permissions still take effect.
19
- from team_agent.runtime import _detect_inherited_dangerous_permissions
20
- effective = dict(runtime_cfg)
21
- if effective.get("dangerous_auto_approve"):
22
- effective["dangerous_auto_approve_source"] = "runtime_config"
23
- effective["dangerous_auto_approve_inherited"] = False
24
- return effective
25
- inherited = _detect_inherited_dangerous_permissions()
26
- if not inherited.get("enabled"):
27
- effective["dangerous_auto_approve"] = False
28
- effective["dangerous_auto_approve_source"] = "disabled"
29
- effective["dangerous_auto_approve_inherited"] = False
30
- return effective
31
- effective["dangerous_auto_approve"] = True
32
- effective["dangerous_auto_approve_source"] = "leader_process"
33
- effective["dangerous_auto_approve_inherited"] = True
34
- effective["dangerous_auto_approve_provider"] = inherited.get("provider")
35
- effective["dangerous_auto_approve_flag"] = inherited.get("flag")
36
- return effective
37
-
38
-
39
- def requires_direct_leader_receiver(spec: dict[str, Any], runtime_cfg: dict[str, Any]) -> bool:
40
- if runtime_cfg.get("require_leader_receiver") is not None:
41
- return bool(runtime_cfg.get("require_leader_receiver"))
42
- return any(agent.get("provider") != "fake" for agent in spec.get("agents", []))
43
-
44
-
45
- def detect_inherited_dangerous_permissions() -> dict[str, Any]:
46
- # Route via runtime alias so existing patches of
47
- # team_agent.runtime._process_ancestry take effect at call time.
48
- from team_agent.runtime import _process_ancestry
49
- for proc in _process_ancestry(os.getpid()):
50
- command = str(proc.get("command") or "")
51
- for provider, flag in DANGEROUS_LEADER_FLAGS:
52
- if command_has_flag(command, flag):
53
- return {
54
- "enabled": True,
55
- "provider": provider,
56
- "flag": flag,
57
- "pid": proc.get("pid"),
58
- }
59
- return {"enabled": False}
60
-
61
-
62
- def command_has_flag(command: str, flag: str) -> bool:
63
- return re.search(rf"(?<!\S){re.escape(flag)}(?!\S)", command) is not None
64
-
65
-
66
- def process_ancestry(pid: int, max_depth: int = 12) -> list[dict[str, Any]]:
67
- ancestry: list[dict[str, Any]] = []
68
- current = pid
69
- seen: set[int] = set()
70
- for _ in range(max_depth):
71
- if current in seen or current <= 0:
72
- break
73
- seen.add(current)
74
- info = process_info(current)
75
- if not info:
76
- break
77
- ancestry.append(info)
78
- parent = info.get("ppid")
79
- if not isinstance(parent, int) or parent <= 1 or parent == current:
80
- break
81
- current = parent
82
- return ancestry
83
-
84
-
85
- def process_info(pid: int) -> dict[str, Any] | None:
86
- try:
87
- proc = subprocess.run(
88
- ["ps", "-p", str(pid), "-o", "ppid=", "-o", "command="],
89
- text=True,
90
- capture_output=True,
91
- timeout=2,
92
- check=False,
93
- )
94
- except (OSError, subprocess.TimeoutExpired):
95
- return None
96
- if proc.returncode != 0:
97
- return None
98
- line = proc.stdout.strip()
99
- if not line:
100
- return None
101
- parts = line.split(None, 1)
102
- try:
103
- ppid = int(parts[0])
104
- except (IndexError, ValueError):
105
- return None
106
- return {"pid": pid, "ppid": ppid, "command": parts[1] if len(parts) > 1 else ""}