@team-agent/installer 0.2.11 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (326) hide show
  1. package/Cargo.lock +744 -0
  2. package/Cargo.toml +34 -0
  3. package/crates/team-agent/Cargo.toml +33 -0
  4. package/crates/team-agent/src/cli/adapters.rs +1343 -0
  5. package/crates/team-agent/src/cli/diagnose.rs +554 -0
  6. package/crates/team-agent/src/cli/emit.rs +1077 -0
  7. package/crates/team-agent/src/cli/helpers.rs +88 -0
  8. package/crates/team-agent/src/cli/leader.rs +216 -0
  9. package/crates/team-agent/src/cli/mod.rs +1141 -0
  10. package/crates/team-agent/src/cli/profile.rs +306 -0
  11. package/crates/team-agent/src/cli/send.rs +215 -0
  12. package/crates/team-agent/src/cli/status.rs +179 -0
  13. package/crates/team-agent/src/cli/status_port.rs +502 -0
  14. package/crates/team-agent/src/cli/tests/base.rs +616 -0
  15. package/crates/team-agent/src/cli/tests/compile.rs +96 -0
  16. package/crates/team-agent/src/cli/tests/divergence.rs +509 -0
  17. package/crates/team-agent/src/cli/tests/lane_c.rs +333 -0
  18. package/crates/team-agent/src/cli/tests/leader_watch.rs +395 -0
  19. package/crates/team-agent/src/cli/tests/main_preserved.rs +675 -0
  20. package/crates/team-agent/src/cli/tests/missing_subcommands.rs +390 -0
  21. package/crates/team-agent/src/cli/tests/mod.rs +97 -0
  22. package/crates/team-agent/src/cli/tests/peer_allow.rs +137 -0
  23. package/crates/team-agent/src/cli/tests/repair_state_byte_lock.rs +302 -0
  24. package/crates/team-agent/src/cli/tests/run_delegation.rs +305 -0
  25. package/crates/team-agent/src/cli/tests/status_send.rs +385 -0
  26. package/crates/team-agent/src/cli/tests/verb_profile.rs +182 -0
  27. package/crates/team-agent/src/cli/tests/verb_settle.rs +236 -0
  28. package/crates/team-agent/src/cli/tests/verb_validate.rs +184 -0
  29. package/crates/team-agent/src/cli/types.rs +605 -0
  30. package/crates/team-agent/src/compiler/tests.rs +701 -0
  31. package/crates/team-agent/src/compiler.rs +489 -0
  32. package/crates/team-agent/src/coordinator/backoff.rs +153 -0
  33. package/crates/team-agent/src/coordinator/health.rs +436 -0
  34. package/crates/team-agent/src/coordinator/mod.rs +80 -0
  35. package/crates/team-agent/src/coordinator/orphan.rs +179 -0
  36. package/crates/team-agent/src/coordinator/tests/abnormal.rs +255 -0
  37. package/crates/team-agent/src/coordinator/tests/basics.rs +262 -0
  38. package/crates/team-agent/src/coordinator/tests/daemon.rs +323 -0
  39. package/crates/team-agent/src/coordinator/tests/health_sync.rs +263 -0
  40. package/crates/team-agent/src/coordinator/tests/main_preserved.rs +136 -0
  41. package/crates/team-agent/src/coordinator/tests/mod.rs +310 -0
  42. package/crates/team-agent/src/coordinator/tests/spine.rs +261 -0
  43. package/crates/team-agent/src/coordinator/tests/takeover.rs +227 -0
  44. package/crates/team-agent/src/coordinator/tests/tick_core.rs +256 -0
  45. package/crates/team-agent/src/coordinator/tests/watch.rs +167 -0
  46. package/crates/team-agent/src/coordinator/tick.rs +2032 -0
  47. package/crates/team-agent/src/coordinator/types.rs +584 -0
  48. package/crates/team-agent/src/db/migration.rs +716 -0
  49. package/crates/team-agent/src/db/mod.rs +23 -0
  50. package/crates/team-agent/src/db/schema.rs +378 -0
  51. package/crates/team-agent/src/event_log.rs +375 -0
  52. package/crates/team-agent/src/fake_worker.rs +253 -0
  53. package/crates/team-agent/src/leader/helpers.rs +190 -0
  54. package/crates/team-agent/src/leader/inject.rs +33 -0
  55. package/crates/team-agent/src/leader/lease.rs +1063 -0
  56. package/crates/team-agent/src/leader/mod.rs +99 -0
  57. package/crates/team-agent/src/leader/owner_bind.rs +292 -0
  58. package/crates/team-agent/src/leader/rediscover/tests.rs +525 -0
  59. package/crates/team-agent/src/leader/rediscover.rs +1099 -0
  60. package/crates/team-agent/src/leader/start.rs +273 -0
  61. package/crates/team-agent/src/leader/takeover.rs +235 -0
  62. package/crates/team-agent/src/leader/tests/basics.rs +183 -0
  63. package/crates/team-agent/src/leader/tests/byte_findings.rs +234 -0
  64. package/crates/team-agent/src/leader/tests/identity.rs +206 -0
  65. package/crates/team-agent/src/leader/tests/idle.rs +271 -0
  66. package/crates/team-agent/src/leader/tests/lease_api.rs +225 -0
  67. package/crates/team-agent/src/leader/tests/lease_claim.rs +253 -0
  68. package/crates/team-agent/src/leader/tests/mod.rs +125 -0
  69. package/crates/team-agent/src/leader/tests/rediscover.rs +351 -0
  70. package/crates/team-agent/src/leader/tests/wake_start_owner.rs +204 -0
  71. package/crates/team-agent/src/leader/types.rs +487 -0
  72. package/crates/team-agent/src/lib.rs +85 -0
  73. package/crates/team-agent/src/lifecycle/display.rs +228 -0
  74. package/crates/team-agent/src/lifecycle/helpers.rs +112 -0
  75. package/crates/team-agent/src/lifecycle/launch/plan.rs +227 -0
  76. package/crates/team-agent/src/lifecycle/launch.rs +1833 -0
  77. package/crates/team-agent/src/lifecycle/mod.rs +62 -0
  78. package/crates/team-agent/src/lifecycle/restart/agent.rs +533 -0
  79. package/crates/team-agent/src/lifecycle/restart/common.rs +517 -0
  80. package/crates/team-agent/src/lifecycle/restart/orchestrator.rs +41 -0
  81. package/crates/team-agent/src/lifecycle/restart/rebuild.rs +268 -0
  82. package/crates/team-agent/src/lifecycle/restart/remove.rs +780 -0
  83. package/crates/team-agent/src/lifecycle/restart/selection.rs +208 -0
  84. package/crates/team-agent/src/lifecycle/restart/team_state.rs +242 -0
  85. package/crates/team-agent/src/lifecycle/restart.rs +76 -0
  86. package/crates/team-agent/src/lifecycle/tests/agent_ops.rs +455 -0
  87. package/crates/team-agent/src/lifecycle/tests/core.rs +989 -0
  88. package/crates/team-agent/src/lifecycle/tests/lane_ops.rs +583 -0
  89. package/crates/team-agent/src/lifecycle/tests/launch_spawn.rs +933 -0
  90. package/crates/team-agent/src/lifecycle/tests/main_preserved.rs +265 -0
  91. package/crates/team-agent/src/lifecycle/tests.rs +27 -0
  92. package/crates/team-agent/src/lifecycle/types.rs +685 -0
  93. package/crates/team-agent/src/main.rs +41 -0
  94. package/crates/team-agent/src/mcp_server/helpers.rs +228 -0
  95. package/crates/team-agent/src/mcp_server/mod.rs +183 -0
  96. package/crates/team-agent/src/mcp_server/normalize.rs +312 -0
  97. package/crates/team-agent/src/mcp_server/tests/golden.rs +283 -0
  98. package/crates/team-agent/src/mcp_server/tests/normalize.rs +244 -0
  99. package/crates/team-agent/src/mcp_server/tests/scoped.rs +189 -0
  100. package/crates/team-agent/src/mcp_server/tests/send.rs +222 -0
  101. package/crates/team-agent/src/mcp_server/tests/tools.rs +158 -0
  102. package/crates/team-agent/src/mcp_server/tests/wire.rs +159 -0
  103. package/crates/team-agent/src/mcp_server/tests.rs +38 -0
  104. package/crates/team-agent/src/mcp_server/tools.rs +603 -0
  105. package/crates/team-agent/src/mcp_server/types.rs +421 -0
  106. package/crates/team-agent/src/mcp_server/wire.rs +388 -0
  107. package/crates/team-agent/src/message_store.rs +767 -0
  108. package/crates/team-agent/src/messaging/activity.rs +433 -0
  109. package/crates/team-agent/src/messaging/delivery.rs +542 -0
  110. package/crates/team-agent/src/messaging/helpers.rs +209 -0
  111. package/crates/team-agent/src/messaging/leader_receiver.rs +340 -0
  112. package/crates/team-agent/src/messaging/mod.rs +147 -0
  113. package/crates/team-agent/src/messaging/peers.rs +32 -0
  114. package/crates/team-agent/src/messaging/results.rs +537 -0
  115. package/crates/team-agent/src/messaging/scheduler.rs +344 -0
  116. package/crates/team-agent/src/messaging/selftest.rs +100 -0
  117. package/crates/team-agent/src/messaging/send.rs +582 -0
  118. package/crates/team-agent/src/messaging/tests/basic.rs +357 -0
  119. package/crates/team-agent/src/messaging/tests/main_preserved.rs +122 -0
  120. package/crates/team-agent/src/messaging/tests/mod.rs +293 -0
  121. package/crates/team-agent/src/messaging/tests/runtime.rs +1422 -0
  122. package/crates/team-agent/src/messaging/tests/spine.rs +437 -0
  123. package/crates/team-agent/src/messaging/trust.rs +192 -0
  124. package/crates/team-agent/src/messaging/types.rs +355 -0
  125. package/crates/team-agent/src/messaging/watchers.rs +591 -0
  126. package/crates/team-agent/src/model/enums.rs +311 -0
  127. package/crates/team-agent/src/model/errors.rs +17 -0
  128. package/crates/team-agent/src/model/ids.rs +155 -0
  129. package/crates/team-agent/src/model/mod.rs +22 -0
  130. package/crates/team-agent/src/model/paths.rs +228 -0
  131. package/crates/team-agent/src/model/permissions.rs +567 -0
  132. package/crates/team-agent/src/model/routing.rs +340 -0
  133. package/crates/team-agent/src/model/spec.rs +680 -0
  134. package/crates/team-agent/src/model/task_graph.rs +380 -0
  135. package/crates/team-agent/src/model/testdata/fuzz.golden.yaml +43 -0
  136. package/crates/team-agent/src/model/testdata/fuzz.yaml +43 -0
  137. package/crates/team-agent/src/model/testdata/spec_invalid_a.yaml +207 -0
  138. package/crates/team-agent/src/model/testdata/team.spec.golden.yaml +206 -0
  139. package/crates/team-agent/src/model/testdata/team.spec.yaml +206 -0
  140. package/crates/team-agent/src/model/yaml/tests.rs +288 -0
  141. package/crates/team-agent/src/model/yaml.rs +800 -0
  142. package/crates/team-agent/src/packaging/install.rs +305 -0
  143. package/crates/team-agent/src/packaging/migrate.rs +30 -0
  144. package/crates/team-agent/src/packaging/mod.rs +82 -0
  145. package/crates/team-agent/src/packaging/repair.rs +24 -0
  146. package/crates/team-agent/src/packaging/tests.rs +829 -0
  147. package/crates/team-agent/src/packaging/types.rs +369 -0
  148. package/crates/team-agent/src/provider/adapter.rs +801 -0
  149. package/crates/team-agent/src/provider/approvals/mod.rs +2 -0
  150. package/crates/team-agent/src/provider/approvals/parsing.rs +452 -0
  151. package/crates/team-agent/src/provider/approvals/runtime_prompts.rs +163 -0
  152. package/crates/team-agent/src/provider/classify.rs +456 -0
  153. package/crates/team-agent/src/provider/faults.rs +136 -0
  154. package/crates/team-agent/src/provider/helpers.rs +41 -0
  155. package/crates/team-agent/src/provider/mod.rs +53 -0
  156. package/crates/team-agent/src/provider/startup_prompt.rs +423 -0
  157. package/crates/team-agent/src/provider/tests/adapter.rs +239 -0
  158. package/crates/team-agent/src/provider/tests/classify.rs +240 -0
  159. package/crates/team-agent/src/provider/tests/faults.rs +120 -0
  160. package/crates/team-agent/src/provider/tests/idle.rs +208 -0
  161. package/crates/team-agent/src/provider/tests/wire.rs +213 -0
  162. package/crates/team-agent/src/provider/tests.rs +31 -0
  163. package/crates/team-agent/src/provider/types.rs +424 -0
  164. package/crates/team-agent/src/state/identity.rs +656 -0
  165. package/crates/team-agent/src/state/mod.rs +58 -0
  166. package/crates/team-agent/src/state/owner_gate.rs +423 -0
  167. package/crates/team-agent/src/state/persist.rs +712 -0
  168. package/crates/team-agent/src/state/projection.rs +657 -0
  169. package/crates/team-agent/src/state/selector.rs +105 -0
  170. package/crates/team-agent/src/state/testdata/state-rich.canonical.json +133 -0
  171. package/crates/team-agent/src/tmux_backend/tests.rs +586 -0
  172. package/crates/team-agent/src/tmux_backend.rs +758 -0
  173. package/crates/team-agent/src/transport/test_support.rs +252 -0
  174. package/crates/team-agent/src/transport/tests/behavior.rs +327 -0
  175. package/crates/team-agent/src/transport/tests/mod.rs +199 -0
  176. package/crates/team-agent/src/transport/tests/wire.rs +527 -0
  177. package/crates/team-agent/src/transport.rs +774 -0
  178. package/npm/install.mjs +90 -106
  179. package/package.json +15 -13
  180. package/crates/team-agent-core/Cargo.toml +0 -12
  181. package/crates/team-agent-core/src/lib.rs +0 -332
  182. package/crates/team-agent-core/src/main.rs +0 -152
  183. package/pyproject.toml +0 -18
  184. package/scripts/install.py +0 -88
  185. package/scripts/run_regression_tests.py +0 -83
  186. package/src/team_agent/__init__.py +0 -3
  187. package/src/team_agent/__main__.py +0 -5
  188. package/src/team_agent/_legacy_pane_discovery.py +0 -186
  189. package/src/team_agent/abnormal_track.py +0 -253
  190. package/src/team_agent/approvals/__init__.py +0 -65
  191. package/src/team_agent/approvals/constants.py +0 -6
  192. package/src/team_agent/approvals/parsing.py +0 -176
  193. package/src/team_agent/approvals/runtime_prompts.py +0 -171
  194. package/src/team_agent/approvals/status.py +0 -176
  195. package/src/team_agent/cli/__init__.py +0 -137
  196. package/src/team_agent/cli/commands.py +0 -481
  197. package/src/team_agent/cli/e2e.py +0 -202
  198. package/src/team_agent/cli/helpers.py +0 -226
  199. package/src/team_agent/cli/parser.py +0 -540
  200. package/src/team_agent/compiler.py +0 -334
  201. package/src/team_agent/coordinator/__init__.py +0 -53
  202. package/src/team_agent/coordinator/__main__.py +0 -119
  203. package/src/team_agent/coordinator/lifecycle.py +0 -411
  204. package/src/team_agent/coordinator/metadata.py +0 -61
  205. package/src/team_agent/coordinator/paths.py +0 -17
  206. package/src/team_agent/diagnose/__init__.py +0 -48
  207. package/src/team_agent/diagnose/checks.py +0 -101
  208. package/src/team_agent/diagnose/comms.py +0 -213
  209. package/src/team_agent/diagnose/health.py +0 -241
  210. package/src/team_agent/diagnose/orphan_cleanup.py +0 -364
  211. package/src/team_agent/diagnose/preflight.py +0 -194
  212. package/src/team_agent/diagnose/quick_start.py +0 -324
  213. package/src/team_agent/display/__init__.py +0 -92
  214. package/src/team_agent/display/adaptive.py +0 -511
  215. package/src/team_agent/display/backend.py +0 -46
  216. package/src/team_agent/display/close.py +0 -154
  217. package/src/team_agent/display/ghostty.py +0 -77
  218. package/src/team_agent/display/rebuild.py +0 -102
  219. package/src/team_agent/display/tiling.py +0 -156
  220. package/src/team_agent/display/worker_window.py +0 -114
  221. package/src/team_agent/display/workspace.py +0 -382
  222. package/src/team_agent/errors.py +0 -10
  223. package/src/team_agent/events.py +0 -84
  224. package/src/team_agent/fake_worker.py +0 -80
  225. package/src/team_agent/idle_predicate.py +0 -218
  226. package/src/team_agent/idle_takeover.py +0 -59
  227. package/src/team_agent/idle_takeover_wiring.py +0 -114
  228. package/src/team_agent/launch/__init__.py +0 -41
  229. package/src/team_agent/launch/bootstrap.py +0 -85
  230. package/src/team_agent/launch/config.py +0 -106
  231. package/src/team_agent/launch/core.py +0 -301
  232. package/src/team_agent/launch/requirements.py +0 -57
  233. package/src/team_agent/leader/__init__.py +0 -926
  234. package/src/team_agent/leader_binding.py +0 -183
  235. package/src/team_agent/lifecycle/__init__.py +0 -5
  236. package/src/team_agent/lifecycle/agents.py +0 -278
  237. package/src/team_agent/lifecycle/operations.py +0 -411
  238. package/src/team_agent/lifecycle/paste_buffer_hygiene.py +0 -39
  239. package/src/team_agent/lifecycle/start.py +0 -363
  240. package/src/team_agent/mcp_server/__init__.py +0 -42
  241. package/src/team_agent/mcp_server/__main__.py +0 -7
  242. package/src/team_agent/mcp_server/contracts.py +0 -148
  243. package/src/team_agent/mcp_server/normalize.py +0 -257
  244. package/src/team_agent/mcp_server/server.py +0 -150
  245. package/src/team_agent/mcp_server/tools.py +0 -352
  246. package/src/team_agent/message_store/__init__.py +0 -23
  247. package/src/team_agent/message_store/agent_health.py +0 -113
  248. package/src/team_agent/message_store/core.py +0 -497
  249. package/src/team_agent/message_store/leader_notification_log.py +0 -198
  250. package/src/team_agent/message_store/result_watchers.py +0 -251
  251. package/src/team_agent/message_store/schema.py +0 -308
  252. package/src/team_agent/message_store/schema_migration.py +0 -448
  253. package/src/team_agent/messaging/__init__.py +0 -1
  254. package/src/team_agent/messaging/activity_detector.py +0 -262
  255. package/src/team_agent/messaging/delivery.py +0 -504
  256. package/src/team_agent/messaging/deps.py +0 -247
  257. package/src/team_agent/messaging/idle_alerts.py +0 -423
  258. package/src/team_agent/messaging/internal_delivery.py +0 -46
  259. package/src/team_agent/messaging/leader.py +0 -497
  260. package/src/team_agent/messaging/leader_api_errors.py +0 -216
  261. package/src/team_agent/messaging/leader_panes.py +0 -673
  262. package/src/team_agent/messaging/owner_bypass.py +0 -29
  263. package/src/team_agent/messaging/result_delivery.py +0 -539
  264. package/src/team_agent/messaging/results.py +0 -447
  265. package/src/team_agent/messaging/scheduler.py +0 -450
  266. package/src/team_agent/messaging/send.py +0 -532
  267. package/src/team_agent/messaging/session_drift.py +0 -94
  268. package/src/team_agent/messaging/tmux_io.py +0 -506
  269. package/src/team_agent/messaging/tmux_prompt.py +0 -338
  270. package/src/team_agent/messaging/trust_auto_answer.py +0 -52
  271. package/src/team_agent/orchestrator/__init__.py +0 -376
  272. package/src/team_agent/orchestrator/plan.py +0 -122
  273. package/src/team_agent/orchestrator/state.py +0 -128
  274. package/src/team_agent/paths.py +0 -45
  275. package/src/team_agent/permissions.py +0 -123
  276. package/src/team_agent/profiles/__init__.py +0 -82
  277. package/src/team_agent/profiles/constants.py +0 -19
  278. package/src/team_agent/profiles/core.py +0 -407
  279. package/src/team_agent/profiles/helpers.py +0 -69
  280. package/src/team_agent/profiles/provider_env.py +0 -188
  281. package/src/team_agent/profiles/smoke.py +0 -201
  282. package/src/team_agent/provider_cli/__init__.py +0 -43
  283. package/src/team_agent/provider_cli/adapter.py +0 -172
  284. package/src/team_agent/provider_cli/base.py +0 -48
  285. package/src/team_agent/provider_cli/claude.py +0 -503
  286. package/src/team_agent/provider_cli/codex.py +0 -336
  287. package/src/team_agent/provider_cli/copilot.py +0 -8
  288. package/src/team_agent/provider_cli/fake.py +0 -39
  289. package/src/team_agent/provider_cli/gemini.py +0 -95
  290. package/src/team_agent/provider_cli/opencode.py +0 -8
  291. package/src/team_agent/provider_cli/prompt.py +0 -62
  292. package/src/team_agent/provider_cli/registry.py +0 -18
  293. package/src/team_agent/provider_cli/unsupported.py +0 -32
  294. package/src/team_agent/provider_state/README.md +0 -78
  295. package/src/team_agent/provider_state/__init__.py +0 -91
  296. package/src/team_agent/provider_state/claude.py +0 -86
  297. package/src/team_agent/provider_state/codex.py +0 -84
  298. package/src/team_agent/provider_state/common.py +0 -207
  299. package/src/team_agent/provider_state/registry.py +0 -118
  300. package/src/team_agent/providers.py +0 -163
  301. package/src/team_agent/quality_gates.py +0 -104
  302. package/src/team_agent/restart/__init__.py +0 -34
  303. package/src/team_agent/restart/orchestration.py +0 -554
  304. package/src/team_agent/restart/selection.py +0 -89
  305. package/src/team_agent/restart/snapshot.py +0 -70
  306. package/src/team_agent/routing.py +0 -84
  307. package/src/team_agent/runtime.py +0 -1243
  308. package/src/team_agent/rust_core.py +0 -327
  309. package/src/team_agent/sessions/__init__.py +0 -25
  310. package/src/team_agent/sessions/capture.py +0 -144
  311. package/src/team_agent/sessions/inventory.py +0 -44
  312. package/src/team_agent/sessions/resume.py +0 -135
  313. package/src/team_agent/simple_yaml.py +0 -236
  314. package/src/team_agent/spec.py +0 -370
  315. package/src/team_agent/state.py +0 -693
  316. package/src/team_agent/status/__init__.py +0 -63
  317. package/src/team_agent/status/approvals.py +0 -52
  318. package/src/team_agent/status/compact.py +0 -158
  319. package/src/team_agent/status/constants.py +0 -18
  320. package/src/team_agent/status/inbox.py +0 -58
  321. package/src/team_agent/status/peek.py +0 -117
  322. package/src/team_agent/status/queries.py +0 -199
  323. package/src/team_agent/task_graph.py +0 -80
  324. package/src/team_agent/terminal.py +0 -57
  325. package/src/team_agent/wake.py +0 -58
  326. package/src/team_agent/watch/__init__.py +0 -145
@@ -1,327 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- import platform
5
- import re
6
- import shutil
7
- import subprocess
8
- from pathlib import Path
9
- from typing import Any
10
-
11
- from team_agent.paths import repo_root
12
-
13
-
14
- _LEADER_ENV_KEYS = (
15
- "TEAM_AGENT_LEADER_SESSION_UUID",
16
- "TEAM_AGENT_LEADER_PANE_ID",
17
- "TEAM_AGENT_LEADER_PROVIDER",
18
- "TEAM_AGENT_MACHINE_FINGERPRINT",
19
- "TEAM_AGENT_LEADER_SESSION_UUID_OVERRIDE",
20
- )
21
- _LEADER_SHAPED_COMMANDS = {"codex", "claude", "claude.exe", "node", "nodejs"}
22
- _PANE_ENV_SCAN_TIMEOUT_SECONDS = 2.0
23
- _run_subprocess = subprocess.run # test-injectable indirection
24
-
25
-
26
- def core_binary() -> Path | None:
27
- configured = shutil.which("team-agent-core")
28
- if configured:
29
- return Path(configured)
30
- local = repo_root() / "crates" / "team-agent-core" / "target" / "debug" / "team-agent-core"
31
- if local.exists():
32
- return local
33
- return None
34
-
35
-
36
- def call_core(command: str, payload: dict[str, Any] | str | None = None) -> dict[str, Any]:
37
- binary = core_binary()
38
- if not binary:
39
- return {"ok": False, "error": "team-agent-core binary not found", "fallback": True}
40
- raw = json.dumps(payload, ensure_ascii=False) if isinstance(payload, dict) else (payload or "")
41
- proc = subprocess.run(
42
- [str(binary), command, "--json"],
43
- input=raw,
44
- text=True,
45
- capture_output=True,
46
- timeout=10,
47
- check=False,
48
- )
49
- try:
50
- result = json.loads(proc.stdout or "{}")
51
- except json.JSONDecodeError:
52
- result = {"ok": False, "error": proc.stdout.strip() or proc.stderr.strip()}
53
- if proc.returncode != 0:
54
- result.setdefault("ok", False)
55
- result.setdefault("error", proc.stderr.strip() or "team-agent-core failed")
56
- result["engine"] = "rust" if result.get("ok") else "rust_failed"
57
- return result
58
-
59
-
60
- def render_message(payload: dict[str, Any]) -> dict[str, Any]:
61
- result = call_core("render-message", payload)
62
- if result.get("ok"):
63
- return result
64
- sender = payload.get("from") or payload.get("sender") or "unknown"
65
- task_id = payload.get("task_id")
66
- content = payload.get("content") or ""
67
- token = payload.get("message_id") or "missing"
68
- header = f"Team Agent message from {sender}"
69
- if task_id:
70
- header += f" for {task_id}"
71
- return {
72
- "ok": True,
73
- "text": f"{header}:\n\n{content}\n\n[team-agent-token:{token}]",
74
- "token": token,
75
- "engine": "python_fallback",
76
- "fallback_reason": result.get("error"),
77
- }
78
-
79
-
80
- def redact_text(text: str) -> dict[str, Any]:
81
- result = call_core("redact", {"text": text})
82
- if result.get("ok"):
83
- return result
84
- redacted = []
85
- for chunk in text.split():
86
- lower = chunk.lower()
87
- if (
88
- "api_key" in lower
89
- or "apikey" in lower
90
- or "token=" in lower
91
- or "secret" in lower
92
- or lower == "bearer"
93
- or chunk.startswith("sk-")
94
- or _looks_base64_secret(chunk)
95
- ):
96
- redacted.append("[REDACTED]")
97
- else:
98
- redacted.append(chunk)
99
- return {"ok": True, "text": " ".join(redacted), "engine": "python_fallback", "fallback_reason": result.get("error")}
100
-
101
-
102
- def validate_profile_metadata(profile: dict[str, Any]) -> dict[str, Any]:
103
- result = call_core("validate-profile", profile)
104
- if result.get("ok") or result.get("errors"):
105
- return result
106
- errors: list[str] = []
107
- if profile.get("auth_mode") not in {"subscription", "official_api", "compatible_api"}:
108
- errors.append("auth_mode must be subscription, official_api, or compatible_api")
109
- for field in ["provider", "model", "profile"]:
110
- if not profile.get(field):
111
- errors.append(f"{field} must not be empty")
112
- if contains_inline_secret(str(profile.get(field) or "")):
113
- errors.append("profile metadata contains a probable inline secret")
114
- return {"ok": not errors, "errors": errors, "engine": "python_fallback", "fallback_reason": result.get("error")}
115
-
116
-
117
- def list_targets() -> dict[str, Any]:
118
- result = call_core("list-targets")
119
- if result.get("ok"):
120
- return result
121
- proc = _run_subprocess(
122
- [
123
- "tmux",
124
- "list-panes",
125
- "-a",
126
- "-F",
127
- "#{pane_id}\t#{session_name}\t#{window_index}\t#{window_name}\t#{pane_index}\t#{pane_tty}\t#{pane_current_command}\t#{pane_active}\t#{pane_pid}",
128
- ],
129
- text=True,
130
- capture_output=True,
131
- timeout=5,
132
- check=False,
133
- )
134
- if proc.returncode != 0:
135
- return {"ok": False, "error": proc.stderr.strip() or "tmux list-panes failed", "engine": "python_fallback"}
136
- targets = []
137
- for line in proc.stdout.splitlines():
138
- parts = line.split("\t")
139
- if len(parts) not in {8, 9}:
140
- continue
141
- target = {
142
- "pane_id": parts[0],
143
- "session_name": parts[1],
144
- "window_index": parts[2],
145
- "window_name": parts[3],
146
- "pane_index": parts[4],
147
- "pane_tty": parts[5],
148
- "pane_current_command": parts[6],
149
- "pane_active": parts[7] == "1",
150
- }
151
- pane_pid = parts[8].strip() if len(parts) == 9 else ""
152
- if pane_pid:
153
- target["pane_pid"] = pane_pid
154
- target["fingerprint"] = f"{target['session_name']}|{target['window_index']}|{target['pane_index']}|{target['pane_tty']}"
155
- _attach_leader_env(target)
156
- targets.append(target)
157
- return {"ok": True, "targets": targets, "engine": "python_fallback", "fallback_reason": result.get("error")}
158
-
159
-
160
- def _attach_leader_env(target: dict[str, Any]) -> None:
161
- pane_pid = str(target.get("pane_pid") or "").strip()
162
- if not pane_pid:
163
- target["leader_env"] = None
164
- return
165
- env = _read_process_env(pane_pid)
166
- if env is None:
167
- target["leader_env"] = None
168
- return
169
- leader_env = {key: env[key] for key in _LEADER_ENV_KEYS if key in env}
170
- if "TEAM_AGENT_LEADER_SESSION_UUID" not in leader_env:
171
- for child_pid in _walk_leader_shaped_children(pane_pid):
172
- child_env = _read_process_env(child_pid)
173
- if child_env is None:
174
- continue
175
- for key in _LEADER_ENV_KEYS:
176
- if key not in leader_env and key in child_env:
177
- leader_env[key] = child_env[key]
178
- if "TEAM_AGENT_LEADER_SESSION_UUID" in leader_env:
179
- break
180
- target["leader_env"] = leader_env
181
- uuid_value = leader_env.get("TEAM_AGENT_LEADER_SESSION_UUID")
182
- if uuid_value:
183
- target["leader_session_uuid"] = uuid_value
184
-
185
-
186
- def _read_process_env(pid: str) -> dict[str, str] | None:
187
- if platform.system() == "Linux":
188
- return _read_proc_environ(pid)
189
- return _read_ps_eww_env(pid)
190
-
191
-
192
- def _read_proc_environ(pid: str) -> dict[str, str] | None:
193
- path = Path(f"/proc/{pid}/environ")
194
- try:
195
- raw = path.read_bytes()
196
- except (FileNotFoundError, PermissionError, OSError):
197
- return None
198
- env: dict[str, str] = {}
199
- for token in raw.split(b"\x00"):
200
- if not token or b"=" not in token:
201
- continue
202
- try:
203
- text = token.decode("utf-8", errors="replace")
204
- except Exception:
205
- continue
206
- key, _, value = text.partition("=")
207
- env[key] = value
208
- return env
209
-
210
-
211
- def _read_ps_eww_env(pid: str) -> dict[str, str] | None:
212
- try:
213
- proc = _run_subprocess(
214
- ["ps", "-E", "-ww", "-p", str(pid)],
215
- text=True,
216
- capture_output=True,
217
- timeout=_PANE_ENV_SCAN_TIMEOUT_SECONDS,
218
- check=False,
219
- )
220
- except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
221
- return None
222
- if proc.returncode != 0 or not proc.stdout:
223
- return None
224
- return _parse_ps_eww_output(proc.stdout, pid)
225
-
226
-
227
- def _parse_ps_eww_output(text: str, pid: str) -> dict[str, str]:
228
- env: dict[str, str] = {}
229
- lines = text.splitlines()
230
- if len(lines) < 2:
231
- return env
232
- target_row = None
233
- for line in lines[1:]:
234
- stripped = line.lstrip()
235
- if stripped.split(" ", 1)[0] == str(pid):
236
- target_row = stripped
237
- break
238
- if target_row is None:
239
- # Spark MEDIUM #2 (da436a3): never fall back to lines[1] — that row may belong to
240
- # an unrelated process and would leak its env (incl. another team's
241
- # TEAM_AGENT_LEADER_SESSION_UUID) into this pane's leader_env, corrupting rediscovery.
242
- return env
243
- for token in target_row.split():
244
- if "=" not in token:
245
- continue
246
- key, _, value = token.partition("=")
247
- if not key or " " in key:
248
- continue
249
- if not (key[0].isalpha() or key[0] == "_"):
250
- continue
251
- if not all(ch.isalnum() or ch == "_" for ch in key):
252
- continue
253
- env[key] = value
254
- return env
255
-
256
-
257
- def _walk_leader_shaped_children(parent_pid: str) -> list[str]:
258
- try:
259
- proc = _run_subprocess(
260
- ["ps", "-o", "pid=,ppid=,comm="],
261
- text=True,
262
- capture_output=True,
263
- timeout=_PANE_ENV_SCAN_TIMEOUT_SECONDS,
264
- check=False,
265
- )
266
- except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
267
- return []
268
- if proc.returncode != 0 or not proc.stdout:
269
- return []
270
- return _select_leader_shaped_descendants(proc.stdout, parent_pid)
271
-
272
-
273
- def _select_leader_shaped_descendants(ps_output: str, parent_pid: str) -> list[str]:
274
- rows: list[tuple[str, str, str]] = []
275
- for line in ps_output.splitlines():
276
- parts = line.split()
277
- if len(parts) < 3:
278
- continue
279
- pid, ppid, command = parts[0], parts[1], " ".join(parts[2:])
280
- rows.append((pid, ppid, Path(command).name))
281
- descendants: set[str] = set()
282
- frontier = {str(parent_pid)}
283
- while frontier:
284
- next_frontier: set[str] = set()
285
- for pid, ppid, _ in rows:
286
- if ppid in frontier and pid not in descendants:
287
- descendants.add(pid)
288
- next_frontier.add(pid)
289
- frontier = next_frontier
290
- return [
291
- pid
292
- for pid, _, command in rows
293
- if pid in descendants and command in _LEADER_SHAPED_COMMANDS
294
- ]
295
-
296
-
297
- def contains_inline_secret(value: str) -> bool:
298
- return (
299
- _contains_secret_assignment(value)
300
- or _contains_bearer_secret(value)
301
- or any(chunk.startswith("sk-") or _looks_base64_secret(chunk) for chunk in value.split())
302
- or value.startswith("sk-")
303
- or _looks_base64_secret(value)
304
- )
305
-
306
-
307
- def _contains_secret_assignment(value: str) -> bool:
308
- for line in value.splitlines():
309
- for separator in ("=", ":"):
310
- if separator not in line:
311
- continue
312
- key, raw = line.split(separator, 1)
313
- normalized = re.sub(r"[^a-z0-9]", "", key.lower())
314
- if normalized not in {"apikey", "token", "secret", "password", "credential"}:
315
- continue
316
- candidate = raw.strip().strip("'\"")
317
- if candidate.startswith("sk-") or len(candidate) >= 8 or _looks_base64_secret(candidate):
318
- return True
319
- return False
320
-
321
-
322
- def _contains_bearer_secret(value: str) -> bool:
323
- return re.search(r"(?i)\bbearer\s+[A-Za-z0-9._~+/=-]{16,}", value) is not None
324
-
325
-
326
- def _looks_base64_secret(value: str) -> bool:
327
- return len(value) >= 32 and re.fullmatch(r"[A-Za-z0-9+/=_-]+", value) is not None
@@ -1,25 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from team_agent.sessions.capture import (
4
- capture_agent_session,
5
- capture_missing_sessions,
6
- clear_session_capture_fields,
7
- copy_session_metadata,
8
- )
9
- from team_agent.sessions.inventory import sessions_overview
10
- from team_agent.sessions.resume import (
11
- attach_profile_resume_root,
12
- prepare_resume_state,
13
- recover_resume_session_from_events,
14
- )
15
-
16
- __all__ = [
17
- "attach_profile_resume_root",
18
- "capture_agent_session",
19
- "capture_missing_sessions",
20
- "clear_session_capture_fields",
21
- "copy_session_metadata",
22
- "prepare_resume_state",
23
- "recover_resume_session_from_events",
24
- "sessions_overview",
25
- ]
@@ -1,144 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import time
4
- from datetime import datetime, timezone
5
- from pathlib import Path
6
- from typing import Any
7
-
8
- from team_agent.errors import RuntimeError as TeamAgentRuntimeError
9
- from team_agent.events import EventLog
10
- from team_agent.providers import get_adapter
11
- from team_agent.state import SESSION_CAPTURE_FIELDS, SESSION_STATE_FIELDS
12
-
13
-
14
- # Stage 7 S6 (2026-05-27): capture_agent_session used to do a single adapter
15
- # call and silently return None on miss, leaving status='running' workers with
16
- # session_id=null. Slow worker startups (Codex writing the rollout file a few
17
- # tenths of a second after window creation) raced this check. We now poll on a
18
- # small interval inside the caller's timeout_s budget so the adapter's own
19
- # fast-path call doesn't have to absorb all the latency on its own.
20
- _CAPTURE_POLL_INTERVAL_SECONDS = 0.05
21
-
22
-
23
- def capture_missing_sessions(
24
- workspace: Path,
25
- state: dict[str, Any],
26
- event_log: EventLog,
27
- timeout_s: float,
28
- log_miss: bool = True,
29
- ) -> list[str]:
30
- captured: list[str] = []
31
- for agent_id, agent_state in state.get("agents", {}).items():
32
- if agent_state.get("session_id"):
33
- continue
34
- known_session_ids = {
35
- str(item.get("session_id"))
36
- for aid, item in state.get("agents", {}).items()
37
- if aid != agent_id and item.get("session_id")
38
- }
39
- # capture_missing_sessions is invoked from coordinator_tick, diagnose,
40
- # status, etc. with very short timeouts; a transient miss should NOT
41
- # crash those paths. The loud raise contract belongs to direct callers
42
- # (e.g. lifecycle start/restart) who own the worker's atomicity.
43
- result = capture_agent_session(
44
- workspace,
45
- agent_id,
46
- agent_state,
47
- event_log,
48
- timeout_s=timeout_s,
49
- exclude_session_ids=known_session_ids,
50
- raise_on_missed=False,
51
- )
52
- if result:
53
- captured.append(agent_id)
54
- elif log_miss:
55
- event_log.write(
56
- "session.capture_timeout",
57
- agent_id=agent_id,
58
- provider=agent_state.get("provider"),
59
- timeout_s=timeout_s,
60
- spawn_cwd=agent_state.get("spawn_cwd"),
61
- )
62
- return captured
63
-
64
-
65
- def capture_agent_session(
66
- workspace: Path,
67
- agent_id: str,
68
- agent_state: dict[str, Any],
69
- event_log: EventLog,
70
- timeout_s: float,
71
- exclude_session_ids: set[str] | None = None,
72
- raise_on_missed: bool = True,
73
- ) -> dict[str, Any] | None:
74
- if agent_state.get("session_id"):
75
- return None
76
- adapter = get_adapter(agent_state["provider"])
77
- spawn_context = {
78
- "agent_id": agent_id,
79
- "cwd": agent_state.get("spawn_cwd") or str(workspace),
80
- "spawn_time": agent_state.get("spawned_at") or datetime.now(timezone.utc).isoformat(),
81
- "tmux_target": f"{agent_state.get('session_name', '')}:{agent_state.get('window', agent_id)}",
82
- "predetermined_session_id": agent_state.get("_pending_session_id"),
83
- "exclude_session_ids": sorted(exclude_session_ids or set()),
84
- "claude_projects_root": agent_state.get("claude_projects_root"),
85
- "auth_mode": agent_state.get("auth_mode"),
86
- }
87
- deadline = time.monotonic() + max(timeout_s, 0.0)
88
- while True:
89
- # Pass timeout_s=0 so the adapter does a single fast-path check; the
90
- # outer loop owns the polling budget so behaviour stays consistent
91
- # whether or not the adapter has its own internal sleep.
92
- result = adapter.capture_session_id(agent_id, spawn_context, timeout_s=0)
93
- if isinstance(result, dict) and (result.get("session_id") or result.get("rollout_path")):
94
- copy_session_metadata(agent_state, result)
95
- agent_state.pop("_pending_session_id", None)
96
- event_log.write(
97
- "session.captured",
98
- agent_id=agent_id,
99
- provider=agent_state.get("provider"),
100
- session_id=agent_state.get("session_id"),
101
- rollout_path=agent_state.get("rollout_path"),
102
- captured_via=agent_state.get("captured_via"),
103
- attribution_confidence=agent_state.get("attribution_confidence"),
104
- )
105
- return result
106
- if time.monotonic() >= deadline:
107
- break
108
- time.sleep(_CAPTURE_POLL_INTERVAL_SECONDS)
109
- # Timeout. Slice 1 atomicity contract: a worker whose status is 'running'
110
- # must NEVER be left with session_id=null — that half-state is what made
111
- # Mac mini Stage 7 S5/S6 unreproducible and breaks resume on next restart.
112
- # Emit a structured attention event so the coordinator/operator sees the
113
- # miss, then raise so callers cannot accidentally treat the None as a
114
- # silent "no-op". Non-running workers (still starting, paused, stopped)
115
- # legitimately have no session yet, so they still get the silent-None
116
- # return that existing callers expect.
117
- if agent_state.get("status") == "running":
118
- event_log.write(
119
- "session.capture_required_attention",
120
- agent_id=agent_id,
121
- provider=agent_state.get("provider"),
122
- timeout_s=timeout_s,
123
- spawn_cwd=agent_state.get("spawn_cwd"),
124
- session_name=agent_state.get("session_name"),
125
- window=agent_state.get("window", agent_id),
126
- )
127
- if raise_on_missed:
128
- raise TeamAgentRuntimeError(
129
- f"Failed to capture session_id for agent {agent_id}: adapter "
130
- f"did not produce a session within {timeout_s}s. Worker is "
131
- "running but unidentifiable; this is a Slice 1 atomicity "
132
- "violation."
133
- )
134
- return None
135
-
136
-
137
- def copy_session_metadata(target: dict[str, Any], source: dict[str, Any]) -> None:
138
- for key in SESSION_STATE_FIELDS:
139
- target[key] = source.get(key)
140
-
141
-
142
- def clear_session_capture_fields(target: dict[str, Any]) -> None:
143
- for key in SESSION_CAPTURE_FIELDS:
144
- target[key] = None
@@ -1,44 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from pathlib import Path
4
- from typing import Any
5
-
6
- from team_agent.spec import load_spec
7
- from team_agent.state import load_runtime_state
8
-
9
-
10
- def sessions_overview(workspace: Path) -> dict[str, Any]:
11
- state = load_runtime_state(workspace)
12
- spec_path = Path(state.get("spec_path", workspace / "team.spec.yaml"))
13
- spec = load_spec(spec_path) if spec_path.exists() else {}
14
- tasks = state.get("tasks", [])
15
- rows = []
16
- for agent in spec.get("agents", []):
17
- agent_state = state.get("agents", {}).get(agent["id"], {})
18
- last_task = next((task.get("id") for task in reversed(tasks) if task.get("assignee") == agent["id"]), None)
19
- rows.append(
20
- {
21
- "agent_id": agent["id"],
22
- "provider": agent.get("provider"),
23
- "model": agent.get("model"),
24
- "profile": agent.get("profile"),
25
- "session_id": agent_state.get("session_id"),
26
- "resume_id": agent_state.get("resume_id"),
27
- "rollout_path": agent_state.get("rollout_path"),
28
- "captured_at": agent_state.get("captured_at"),
29
- "captured_via": agent_state.get("captured_via"),
30
- "attribution_confidence": agent_state.get("attribution_confidence"),
31
- "spawn_cwd": agent_state.get("spawn_cwd"),
32
- "context_usage": agent_state.get("context_usage"),
33
- "status": agent_state.get("status", "unknown"),
34
- "last_task": last_task,
35
- "handoff_path": agent_state.get("handoff_path"),
36
- "display_target": agent_state.get("display"),
37
- "terminal_target": {
38
- "session": state.get("session_name"),
39
- "window": agent_state.get("window", agent["id"]),
40
- "pane": agent_state.get("pane_id"),
41
- },
42
- }
43
- )
44
- return {"ok": True, "sessions": rows, "workspace": str(workspace)}
@@ -1,135 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import json
4
- from pathlib import Path
5
- from typing import Any
6
-
7
- from team_agent.events import EventLog
8
- from team_agent.paths import logs_dir
9
- from team_agent.profiles import prepare_agent_profile_launch
10
- from team_agent.providers import ResumeUnavailable
11
- from team_agent.sessions.capture import clear_session_capture_fields, copy_session_metadata
12
-
13
-
14
- def attach_profile_resume_root(workspace: Path, command_agent: dict[str, Any], previous: dict[str, Any]) -> dict[str, Any]:
15
- profile_launch = command_agent.get("_provider_profile") or prepare_agent_profile_launch(workspace, command_agent)
16
- if not profile_launch:
17
- return previous
18
- command_agent["_provider_profile"] = profile_launch
19
- root = profile_launch.get("claude_projects_root")
20
- if not root:
21
- return previous
22
- prepared = dict(previous)
23
- prepared["claude_projects_root"] = root
24
- return prepared
25
-
26
-
27
- def prepare_resume_state(
28
- workspace: Path,
29
- agent_id: str,
30
- previous: dict[str, Any],
31
- adapter: Any,
32
- event_log: EventLog,
33
- exclude_session_ids: set[str] | None = None,
34
- allow_fresh_on_resume_failure: bool = False,
35
- ) -> dict[str, Any]:
36
- prepared = dict(previous)
37
- session_id = prepared.get("session_id")
38
- if session_id and adapter.session_is_resumable(prepared, workspace):
39
- return prepared
40
- if session_id:
41
- event_log.write(
42
- "resume.session_unverified",
43
- agent_id=agent_id,
44
- provider=prepared.get("provider"),
45
- session_id=session_id,
46
- captured_via=prepared.get("captured_via"),
47
- spawn_cwd=prepared.get("spawn_cwd"),
48
- )
49
- else:
50
- event_log.write(
51
- "resume.session_missing_repair_attempt",
52
- agent_id=agent_id,
53
- provider=prepared.get("provider"),
54
- spawn_cwd=prepared.get("spawn_cwd"),
55
- )
56
- repaired = recover_resume_session_from_events(workspace, agent_id, prepared, adapter, exclude_session_ids or set())
57
- if not repaired:
58
- repaired = adapter.recover_session_id(agent_id, prepared, workspace, exclude_session_ids or set())
59
- if repaired:
60
- copy_session_metadata(prepared, repaired)
61
- event_log.write(
62
- "resume.session_repaired",
63
- agent_id=agent_id,
64
- provider=prepared.get("provider"),
65
- old_session_id=session_id,
66
- session_id=prepared.get("session_id"),
67
- rollout_path=prepared.get("rollout_path"),
68
- captured_via=prepared.get("captured_via"),
69
- attribution_confidence=prepared.get("attribution_confidence"),
70
- )
71
- return prepared
72
- if session_id and not allow_fresh_on_resume_failure:
73
- event_log.write(
74
- "resume.session_required_missing",
75
- agent_id=agent_id,
76
- provider=prepared.get("provider"),
77
- old_session_id=session_id,
78
- rollout_path=prepared.get("rollout_path"),
79
- reason="provider transcript not found",
80
- )
81
- raise ResumeUnavailable(
82
- f"Cannot resume agent {agent_id}: stored session {session_id} is not available. "
83
- "Use --allow-fresh only if losing that worker context is acceptable."
84
- )
85
- clear_session_capture_fields(prepared)
86
- event_log.write(
87
- "resume.session_unavailable",
88
- agent_id=agent_id,
89
- provider=prepared.get("provider"),
90
- old_session_id=session_id,
91
- reason="provider transcript not found",
92
- )
93
- return prepared
94
-
95
-
96
- def recover_resume_session_from_events(
97
- workspace: Path,
98
- agent_id: str,
99
- previous: dict[str, Any],
100
- adapter: Any,
101
- exclude_session_ids: set[str],
102
- ) -> dict[str, Any] | None:
103
- events_path = logs_dir(workspace) / "events.jsonl"
104
- try:
105
- lines = events_path.read_text(encoding="utf-8").splitlines()
106
- except OSError:
107
- return None
108
- current_session_id = str(previous.get("session_id") or "")
109
- for line in reversed(lines):
110
- try:
111
- event = json.loads(line)
112
- except json.JSONDecodeError:
113
- continue
114
- if event.get("agent_id") != agent_id:
115
- continue
116
- if event.get("event") == "discard.session_tombstone":
117
- return None
118
- if event.get("event") != "session.captured":
119
- continue
120
- session_id = str(event.get("session_id") or "")
121
- if not session_id or session_id == current_session_id or session_id in exclude_session_ids:
122
- continue
123
- candidate = dict(previous)
124
- candidate.update(
125
- {
126
- "session_id": session_id,
127
- "rollout_path": event.get("rollout_path"),
128
- "captured_at": event.get("ts"),
129
- "captured_via": "event_log_repair",
130
- "attribution_confidence": event.get("attribution_confidence"),
131
- }
132
- )
133
- if adapter.session_is_resumable(candidate, workspace):
134
- return candidate
135
- return None