@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
@@ -0,0 +1,62 @@
1
+ //! step 13 · lifecycle — team 进程级生命周期编排器(ROUND-0.5 骨架)。
2
+ //!
3
+ //! 真相源 `team-agent-public` @ v0.2.11 (439bef8),只读。Card:
4
+ //! `docs/phase0/subsystems/13-lifecycle.md`。
5
+ //!
6
+ //! Python 源:
7
+ //! - `launch/core.py`(`launch`)、`launch/bootstrap.py`/`config.py`/`requirements.py`。
8
+ //! - `lifecycle/start.py`(`start_agent`)、`lifecycle/operations.py`
9
+ //! (`stop_agent`/`reset_agent`/`add_agent`/`fork_agent`)、`lifecycle/agents.py`
10
+ //! (`remove_agent` + `_RemoveRollback`)、`lifecycle/paste_buffer_hygiene.py`。
11
+ //! - `restart/orchestration.py`(`restart` Route B)、`restart/selection.py`、`restart/snapshot.py`。
12
+ //! - `display/backend.py`/`adaptive.py`/`tiling.py`/`workspace.py`/`worker_window.py`/
13
+ //! `ghostty.py`/`close.py`/`rebuild.py`。
14
+ //! - `orchestrator/__init__.py`/`plan.py`/`state.py`(plan 多 stage 状态机)。
15
+ //! - `diagnose/quick_start.py`(`quick_start`/`prepare_quick_start_team`/`wait_ready`)。
16
+ //!
17
+ //! 价值:把下层原语(transport step9 / provider step8 / leader step10 / messaging step11 /
18
+ //! state step5 / coordinator step12 / compiler step6)编排成**原子的、可回滚的、可审计的**
19
+ //! 用户级动作。本 module 不拥有底层原语,只**调用**它们。
20
+ //!
21
+ //! § 锁(机械化,leader 集成时上 `#![deny(unwrap/expect/panic)]`):本 module 调
22
+ //! `save_runtime_state`/`save_team_runtime_snapshot`/`save_plan_state`(`os.replace` 路径,
23
+ //! bug-084 高危)—— 所有写路径强制 `Result`,`EACCES/EPERM/EBUSY` 退避重试,绝不 unwrap。
24
+ //! § lifecycle **构造** provider 命令字符串经 step8 `ProviderAdapter` trait,**绝不**链接
25
+ //! provider client crate(anthropic/openai SDK)。
26
+ //!
27
+ //! ROUND-0.5:数据类型 + 行为入口 fn 签名齐备,body = `unimplemented!("step13 port: ...")`。
28
+ //! contracts blitz 可 NAME 这些类型并 CALL 这些 fn、断言其 rich return。
29
+
30
+ // ROUND-0 skeleton:fn body 全 unimplemented!() → import/field/param/大 Err 暂未落地;P2 porter 实现时移除。
31
+ #![allow(dead_code, unused_imports, unused_variables, clippy::result_large_err, clippy::doc_overindented_list_items, clippy::doc_lazy_continuation, clippy::io_other_error)]
32
+ // §10:lifecycle 写路径(save_runtime_state/snapshot/plan_state,bug-084 高危)实现层禁 unwrap/expect/panic
33
+ // (unimplemented!() stub 不被拦);tests 子模块各自 allow。
34
+ #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
35
+
36
+ pub mod display;
37
+ pub mod helpers;
38
+ pub mod launch;
39
+ pub mod restart;
40
+ pub mod types;
41
+
42
+ use std::collections::BTreeMap;
43
+ use std::path::PathBuf;
44
+
45
+ use crate::model::ids::AgentId;
46
+ use crate::provider::{RolloutPath, SessionId};
47
+ use crate::transport::{PaneId, SessionName, WindowName};
48
+
49
+ // 复用既有 enum(model::enums)。`DisplayBackend` 已在 step2 定义并带 `has_worker_views()`。
50
+ pub use crate::model::enums::DisplayBackend;
51
+
52
+ pub use types::*;
53
+
54
+ pub use display::*;
55
+ pub use launch::*;
56
+ pub use restart::*;
57
+
58
+ pub use helpers::save_team_runtime_snapshot;
59
+ pub(crate) use helpers::{plan_lock_path, plan_state_path, read_plan_state, save_plan_state};
60
+
61
+ #[cfg(test)]
62
+ mod tests;
@@ -0,0 +1,533 @@
1
+ use super::*;
2
+ use super::common::*;
3
+ use super::selection::decide_start_mode;
4
+ use super::team_state::write_team_state;
5
+
6
+ /// `start_agent(workspace, agent_id, force, open_display, allow_fresh, team)`
7
+ /// (`lifecycle/start.py:72`)。`_runtime_lock("start-agent")` 下串行:resume-or-fresh
8
+ /// 决策、resume 窗口退出回退 fresh、起后投递 pending message、起 coordinator。
9
+ /// bug-085:`(session_id, rollout_path)` 四象限穷尽 match,缺 rollout 的 codex 仅在
10
+ /// allow_fresh 时回退 fresh。
11
+ pub fn start_agent(
12
+ workspace: &Path,
13
+ agent_id: &AgentId,
14
+ force: bool,
15
+ open_display: bool,
16
+ allow_fresh: bool,
17
+ team: Option<&str>,
18
+ ) -> Result<StartAgentOutcome, LifecycleError> {
19
+ let paths = lifecycle_paths(workspace, team)?;
20
+ start_agent_at_paths(
21
+ &paths.run_workspace,
22
+ &paths.spec_workspace,
23
+ agent_id,
24
+ force,
25
+ open_display,
26
+ allow_fresh,
27
+ team,
28
+ &crate::tmux_backend::TmuxBackend::for_workspace(&paths.run_workspace),
29
+ )
30
+ }
31
+
32
+ /// `start_agent` with an injected transport — wires the single-worker resume/fresh spawn +
33
+ /// start_coordinator (rt-host-a sweep: was a stub returning RequirementUnmet at the spawn boundary).
34
+ #[allow(clippy::too_many_arguments)]
35
+ pub fn start_agent_with_transport(
36
+ workspace: &Path,
37
+ agent_id: &AgentId,
38
+ force: bool,
39
+ open_display: bool,
40
+ allow_fresh: bool,
41
+ team: Option<&str>,
42
+ transport: &dyn crate::transport::Transport,
43
+ ) -> Result<StartAgentOutcome, LifecycleError> {
44
+ start_agent_at_paths(
45
+ workspace,
46
+ workspace,
47
+ agent_id,
48
+ force,
49
+ open_display,
50
+ allow_fresh,
51
+ team,
52
+ transport,
53
+ )
54
+ }
55
+
56
+ #[allow(clippy::too_many_arguments)]
57
+ pub(crate) fn start_agent_at_paths(
58
+ workspace: &Path,
59
+ spec_workspace: &Path,
60
+ agent_id: &AgentId,
61
+ force: bool,
62
+ open_display: bool,
63
+ allow_fresh: bool,
64
+ team: Option<&str>,
65
+ transport: &dyn crate::transport::Transport,
66
+ ) -> Result<StartAgentOutcome, LifecycleError> {
67
+ let _ = open_display;
68
+ let mut state = if team.is_some() {
69
+ resolve_team_scoped_state_or_refuse(workspace, team)?
70
+ } else {
71
+ crate::state::persist::load_runtime_state(workspace)
72
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?
73
+ };
74
+ crate::lifecycle::launch::ensure_owner_allowed_for_state(&state, Some(agent_id))?;
75
+ let agent = state
76
+ .get("agents")
77
+ .and_then(|v| v.get(agent_id.as_str()))
78
+ .ok_or_else(|| LifecycleError::RequirementUnmet(format!("agent {agent_id} not found")))?;
79
+ if agent
80
+ .get("paused")
81
+ .and_then(serde_json::Value::as_bool)
82
+ .unwrap_or(false)
83
+ {
84
+ return Ok(StartAgentOutcome::Paused { agent_id: agent_id.clone() });
85
+ }
86
+ let session_name = state_session_name(&state);
87
+ let window = agent_window(agent, agent_id);
88
+ if !force && window_exists(transport, &session_name, &window) {
89
+ mark_agent_running_noop(&mut state, agent_id, &session_name, &window)?;
90
+ crate::state::projection::save_team_scoped_state(workspace, &state)
91
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
92
+ if let Ok(spec) = load_team_spec(spec_workspace) {
93
+ write_team_state(spec_workspace, &spec, &state)?;
94
+ }
95
+ let coordinator_started = start_coordinator_for_workspace(workspace)?;
96
+ let target = format!("{}:{window}", session_name.as_str());
97
+ write_start_agent_noop_event(workspace, agent_id, &target, coordinator_started)?;
98
+ return Ok(StartAgentOutcome::Noop {
99
+ env: AgentActionEnvelope {
100
+ agent_id: agent_id.clone(),
101
+ state_file: crate::state::persist::runtime_state_path(workspace),
102
+ coordinator_started,
103
+ },
104
+ target,
105
+ });
106
+ }
107
+ let provider = agent_provider(agent);
108
+ let session_id = agent_session_id(agent);
109
+ let rollout_path = agent_rollout_path(agent);
110
+ let rollout_exists = rollout_path
111
+ .as_ref()
112
+ .map(|p| p.as_path().exists())
113
+ .unwrap_or(false);
114
+ let start_mode = decide_start_mode(
115
+ provider_wire(provider),
116
+ session_id.as_ref(),
117
+ rollout_path.as_ref(),
118
+ rollout_exists,
119
+ allow_fresh,
120
+ );
121
+ let spawn_session_id = if matches!(start_mode, StartMode::Resumed) {
122
+ session_id.as_ref()
123
+ } else {
124
+ None
125
+ };
126
+ let into_existing_session =
127
+ session_live_or_default(transport, &session_name, session_name_present(&state));
128
+ let spawn = spawn_agent_window(
129
+ workspace,
130
+ &session_name,
131
+ agent_id,
132
+ agent,
133
+ spawn_session_id,
134
+ into_existing_session,
135
+ transport,
136
+ None,
137
+ )?;
138
+ write_start_agent_start_event(
139
+ workspace,
140
+ agent_id,
141
+ agent,
142
+ provider,
143
+ start_mode,
144
+ &session_name,
145
+ &window,
146
+ spawn_session_id,
147
+ into_existing_session,
148
+ )?;
149
+ let coordinator_started = start_coordinator_for_workspace(workspace)?;
150
+ Ok(StartAgentOutcome::Running {
151
+ env: AgentActionEnvelope {
152
+ agent_id: agent_id.clone(),
153
+ state_file: crate::state::persist::runtime_state_path(workspace),
154
+ coordinator_started,
155
+ },
156
+ start_mode,
157
+ target: spawn.pane_id.as_str().to_string(),
158
+ session_id,
159
+ rollout_path,
160
+ })
161
+ }
162
+
163
+ /// `stop_agent(workspace, agent_id, team)`(`lifecycle/operations.py:62`)。
164
+ /// owner-gate → kill window → **同时关显示** → 写 state。
165
+ pub fn stop_agent(
166
+ workspace: &Path,
167
+ agent_id: &AgentId,
168
+ team: Option<&str>,
169
+ ) -> Result<StopAgentReport, LifecycleError> {
170
+ stop_agent_with_transport(
171
+ workspace,
172
+ agent_id,
173
+ team,
174
+ &crate::tmux_backend::TmuxBackend::for_workspace(&lifecycle_run_workspace(workspace)?),
175
+ )
176
+ }
177
+
178
+ pub fn stop_agent_with_transport(
179
+ workspace: &Path,
180
+ agent_id: &AgentId,
181
+ team: Option<&str>,
182
+ transport: &dyn crate::transport::Transport,
183
+ ) -> Result<StopAgentReport, LifecycleError> {
184
+ let paths = lifecycle_paths(workspace, team)?;
185
+ stop_agent_at_paths(
186
+ &paths.run_workspace,
187
+ &paths.spec_workspace,
188
+ agent_id,
189
+ team,
190
+ transport,
191
+ )
192
+ }
193
+
194
+ pub(super) fn stop_agent_at_paths(
195
+ workspace: &Path,
196
+ spec_workspace: &Path,
197
+ agent_id: &AgentId,
198
+ team: Option<&str>,
199
+ transport: &dyn crate::transport::Transport,
200
+ ) -> Result<StopAgentReport, LifecycleError> {
201
+ // golden operations.py:64-66: resolve_team_scoped_state -> owner gate, BEFORE the unknown-worker raise.
202
+ let mut state = resolve_team_scoped_state_or_refuse(workspace, team)?;
203
+ crate::lifecycle::launch::ensure_owner_allowed_for_state(&state, Some(agent_id))?;
204
+ let spec = load_team_spec(spec_workspace)?;
205
+ let agent = find_spec_agent(&spec, agent_id)
206
+ .ok_or_else(|| unknown_worker(agent_id))?;
207
+ let session_name = state_session_name_from_spec(&state, &spec);
208
+ let window = state
209
+ .get("agents")
210
+ .and_then(|v| v.get(agent_id.as_str()))
211
+ .and_then(|v| v.get("window"))
212
+ .and_then(|v| v.as_str())
213
+ .filter(|s| !s.is_empty())
214
+ .unwrap_or_else(|| agent_id.as_str())
215
+ .to_string();
216
+ let target_str = format!("{}:{window}", session_name.as_str());
217
+ let stopped = window_exists(transport, &session_name, &window);
218
+ if stopped {
219
+ let target = Target::SessionWindow {
220
+ session: session_name.clone(),
221
+ window: WindowName::new(&window),
222
+ };
223
+ // golden operations.py:84-86: a non-zero kill-window raises
224
+ // RuntimeError(f"failed to stop agent {agent_id}: {proc.stderr.strip()}").
225
+ if let Err(e) = transport.kill_window(&target) {
226
+ let stderr = match &e {
227
+ crate::transport::TransportError::Subprocess { stderr, .. } => stderr.trim().to_string(),
228
+ other => other.to_string(),
229
+ };
230
+ let _ = write_stop_window_failed_event(workspace, agent_id, &target_str, &stderr);
231
+ return Err(LifecycleError::Transport(format!("failed to stop agent {agent_id}: {stderr}")));
232
+ }
233
+ }
234
+ close_agent_display(&mut state, agent_id);
235
+ mark_agent_stopped(&mut state, agent_id, agent, &window)?;
236
+ // golden operations.py:95: save_team_scoped_state (team projection) — NOT a raw save, so a
237
+ // multi-team workspace keeps the other teams' persisted runtime state instead of being clobbered.
238
+ crate::state::projection::save_team_scoped_state(workspace, &state)
239
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
240
+ // golden operations.py:96-99: snapshot (side-effect), then state_file = write_team_state path.
241
+ // snapshot.py:19-21 returns None silently when session_name is falsy — mirror that no-op here so
242
+ // a workspace whose persisted state lacks session_name proceeds to write team_state and return ok
243
+ // (do NOT swallow OTHER snapshot errors). The stop/reset/remove-force paths all inherit this guard.
244
+ if session_name_present(&state) {
245
+ crate::lifecycle::helpers::save_team_runtime_snapshot(workspace, &state)?;
246
+ }
247
+ let state_file = write_team_state(spec_workspace, &spec, &state)?;
248
+ write_stop_complete_event(workspace, agent_id, &target_str, stopped)?;
249
+ Ok(StopAgentReport {
250
+ agent_id: agent_id.clone(),
251
+ target: target_str,
252
+ stopped,
253
+ display_closed: true,
254
+ state_file,
255
+ })
256
+ }
257
+
258
+ /// golden `resolve_team_scoped_state` (state.py:243): returns the team-scoped projected state, or
259
+ /// surfaces the refusal dict (`team_target_ambiguous` / `team_target_unresolved`) as a typed error
260
+ /// BEFORE the owner gate / unknown-worker raise (operations.py:64-66). The lifecycle return types are
261
+ /// typed structs with no refusal-Value variant, so the observable refusal is carried in
262
+ /// `LifecycleError::TeamSelect`'s message (reason + error), which is the closest byte-faithful surface.
263
+ pub(super) fn resolve_team_scoped_state_or_refuse(
264
+ workspace: &Path,
265
+ team: Option<&str>,
266
+ ) -> Result<serde_json::Value, LifecycleError> {
267
+ let (state, refusal) = crate::state::projection::resolve_team_scoped_state(workspace, team)
268
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
269
+ if let Some(refusal) = refusal {
270
+ let reason = refusal
271
+ .get("reason")
272
+ .and_then(|v| v.as_str())
273
+ .unwrap_or("team_target_unresolved");
274
+ let detail = refusal
275
+ .get("error")
276
+ .or_else(|| refusal.get("message"))
277
+ .and_then(|v| v.as_str())
278
+ .unwrap_or("");
279
+ return Err(LifecycleError::TeamSelect(format!("{reason}: {detail}")));
280
+ }
281
+ state.ok_or_else(|| LifecycleError::StatePersist("resolve_team_scoped_state returned no state".to_string()))
282
+ }
283
+
284
+ /// `reset_agent(workspace, agent_id, discard_session, open_display, team)`
285
+ /// (`lifecycle/operations.py:102`)。discard + 重起;**未传 discard_session → 拒绝**。
286
+ pub fn reset_agent(
287
+ workspace: &Path,
288
+ agent_id: &AgentId,
289
+ discard_session: bool,
290
+ open_display: bool,
291
+ team: Option<&str>,
292
+ ) -> Result<ResetAgentOutcome, LifecycleError> {
293
+ reset_agent_with_transport(
294
+ workspace,
295
+ agent_id,
296
+ discard_session,
297
+ open_display,
298
+ team,
299
+ &crate::tmux_backend::TmuxBackend::for_workspace(&lifecycle_run_workspace(workspace)?),
300
+ )
301
+ }
302
+
303
+ pub fn reset_agent_with_transport(
304
+ workspace: &Path,
305
+ agent_id: &AgentId,
306
+ discard_session: bool,
307
+ open_display: bool,
308
+ team: Option<&str>,
309
+ transport: &dyn crate::transport::Transport,
310
+ ) -> Result<ResetAgentOutcome, LifecycleError> {
311
+ if !discard_session {
312
+ return Ok(ResetAgentOutcome::Refused {
313
+ reason: ResetRefusal::DiscardSessionRequired,
314
+ });
315
+ }
316
+ let paths = lifecycle_paths(workspace, team)?;
317
+ reset_agent_at_paths(
318
+ &paths.run_workspace,
319
+ &paths.spec_workspace,
320
+ agent_id,
321
+ discard_session,
322
+ open_display,
323
+ team,
324
+ transport,
325
+ )
326
+ }
327
+
328
+ fn reset_agent_at_paths(
329
+ workspace: &Path,
330
+ spec_workspace: &Path,
331
+ agent_id: &AgentId,
332
+ discard_session: bool,
333
+ open_display: bool,
334
+ team: Option<&str>,
335
+ transport: &dyn crate::transport::Transport,
336
+ ) -> Result<ResetAgentOutcome, LifecycleError> {
337
+ if !discard_session {
338
+ return Ok(ResetAgentOutcome::Refused {
339
+ reason: ResetRefusal::DiscardSessionRequired,
340
+ });
341
+ }
342
+ // golden operations.py:105-110: team-scope resolve + owner gate BEFORE the nested stop.
343
+ let state_before_stop = resolve_team_scoped_state_or_refuse(workspace, team)?;
344
+ let discarded_session_id = state_before_stop
345
+ .get("agents")
346
+ .and_then(|v| v.get(agent_id.as_str()))
347
+ .and_then(|v| v.get("session_id"))
348
+ .and_then(|v| v.as_str())
349
+ .unwrap_or("")
350
+ .to_string();
351
+ crate::lifecycle::launch::ensure_owner_allowed_for_state(&state_before_stop, Some(agent_id))?;
352
+ let stop = stop_agent_at_paths(workspace, spec_workspace, agent_id, team, transport)?;
353
+ let mut state = resolve_team_scoped_state_or_refuse(workspace, team)?;
354
+ let spec = load_team_spec(spec_workspace)?;
355
+ discard_agent_session_fields(&mut state, agent_id)?;
356
+ // golden operations.py (reset): save_team_scoped_state on the team projection — same multi-team
357
+ // preservation as stop, not a raw save_runtime_state.
358
+ crate::state::projection::save_team_scoped_state(workspace, &state)
359
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
360
+ // golden operations.py:125: write_team_state after the discard-save (the intermediate stopped snapshot).
361
+ write_team_state(spec_workspace, &spec, &state)?;
362
+ write_reset_tombstone_event(workspace, agent_id, &discarded_session_id)?;
363
+ let start = start_agent_at_paths(
364
+ workspace,
365
+ spec_workspace,
366
+ agent_id,
367
+ true,
368
+ open_display,
369
+ true,
370
+ team,
371
+ transport,
372
+ )?;
373
+ let started = matches!(start, StartAgentOutcome::Running { .. });
374
+ write_reset_complete_event(workspace, agent_id, stop.stopped, started)?;
375
+ match start {
376
+ StartAgentOutcome::Running {
377
+ env, start_mode, ..
378
+ } => Ok(ResetAgentOutcome::Reset { env, start_mode }),
379
+ StartAgentOutcome::Noop { env, .. } => Ok(ResetAgentOutcome::Reset {
380
+ env,
381
+ start_mode: StartMode::Noop,
382
+ }),
383
+ StartAgentOutcome::Paused { .. } => Ok(ResetAgentOutcome::Reset {
384
+ env: AgentActionEnvelope {
385
+ agent_id: agent_id.clone(),
386
+ state_file: crate::state::persist::runtime_state_path(workspace),
387
+ coordinator_started: false,
388
+ },
389
+ start_mode: StartMode::Noop,
390
+ }),
391
+ }
392
+ }
393
+
394
+ #[allow(clippy::too_many_arguments)]
395
+ fn write_start_agent_start_event(
396
+ workspace: &Path,
397
+ agent_id: &AgentId,
398
+ agent: &serde_json::Value,
399
+ provider: crate::provider::Provider,
400
+ start_mode: StartMode,
401
+ session_name: &SessionName,
402
+ window: &str,
403
+ session_id: Option<&SessionId>,
404
+ into_existing_session: bool,
405
+ ) -> Result<(), LifecycleError> {
406
+ let auth_mode = agent_auth_mode(agent);
407
+ let model = agent.get("model").and_then(|v| v.as_str());
408
+ let adapter = crate::provider::get_adapter(provider);
409
+ // Contract C / F6.4: event log must record the same context-aware argv that the
410
+ // actual spawn used — so the role/tools/MCP context appears in `start_agent.agent_start`.
411
+ let role = agent.get("role").and_then(|v| v.as_str());
412
+ let safety = crate::lifecycle::launch::effective_runtime_config_for_worker_spawn()?;
413
+ let tools = crate::lifecycle::launch::worker_tool_refs(agent_tool_strings(agent), &safety);
414
+ let tool_refs: Vec<&str> = tools.iter().map(String::as_str).collect();
415
+ let mcp_config = adapter
416
+ .mcp_config(auth_mode)
417
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?;
418
+ let mut argv = match session_id {
419
+ Some(session_id) => adapter
420
+ .build_resume_command_with_context(
421
+ Some(session_id),
422
+ auth_mode,
423
+ Some(&mcp_config),
424
+ role,
425
+ model,
426
+ &tool_refs,
427
+ )
428
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?,
429
+ None => adapter
430
+ .build_command_with_tools(auth_mode, Some(&mcp_config), role, model, &tool_refs)
431
+ .map_err(|e| LifecycleError::Provider(e.to_string()))?,
432
+ };
433
+ let team_id = agent
434
+ .get("owner_team_id")
435
+ .and_then(|v| v.as_str());
436
+ crate::lifecycle::launch::fill_spawn_placeholders_full(&mut argv, workspace, agent_id.as_str(), team_id);
437
+ let tmux_start_mode = if into_existing_session {
438
+ "new-window"
439
+ } else {
440
+ "new-session"
441
+ };
442
+ crate::event_log::EventLog::new(workspace)
443
+ .write(
444
+ "start_agent.agent_start",
445
+ serde_json::json!({
446
+ "agent_id": agent_id.as_str(),
447
+ "provider": provider_wire(provider),
448
+ "start_mode": start_mode,
449
+ "session_id": session_id.map(|s| s.as_str()),
450
+ "session": session_name.as_str(),
451
+ "window": window,
452
+ "tmux_start_mode": tmux_start_mode,
453
+ "command": argv,
454
+ "mcp_config": agent.get("mcp_config").cloned().unwrap_or(serde_json::Value::Null),
455
+ }),
456
+ )
457
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
458
+ Ok(())
459
+ }
460
+
461
+ fn write_stop_complete_event(
462
+ workspace: &Path,
463
+ agent_id: &AgentId,
464
+ target: &str,
465
+ stopped: bool,
466
+ ) -> Result<(), LifecycleError> {
467
+ crate::event_log::EventLog::new(workspace)
468
+ .write(
469
+ "stop_agent.complete",
470
+ serde_json::json!({
471
+ "agent_id": agent_id.as_str(),
472
+ "target": target,
473
+ "stopped": stopped,
474
+ }),
475
+ )
476
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
477
+ Ok(())
478
+ }
479
+
480
+ fn write_stop_window_failed_event(
481
+ workspace: &Path,
482
+ agent_id: &AgentId,
483
+ target: &str,
484
+ stderr: &str,
485
+ ) -> Result<(), LifecycleError> {
486
+ crate::event_log::EventLog::new(workspace)
487
+ .write(
488
+ "stop_agent.window_stop_failed",
489
+ serde_json::json!({
490
+ "agent_id": agent_id.as_str(),
491
+ "target": target,
492
+ "stderr": stderr,
493
+ }),
494
+ )
495
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
496
+ Ok(())
497
+ }
498
+
499
+ fn write_reset_tombstone_event(
500
+ workspace: &Path,
501
+ agent_id: &AgentId,
502
+ discarded_session_id: &str,
503
+ ) -> Result<(), LifecycleError> {
504
+ crate::event_log::EventLog::new(workspace)
505
+ .write(
506
+ "discard.session_tombstone",
507
+ serde_json::json!({
508
+ "agent_id": agent_id.as_str(),
509
+ "discarded_session_id": discarded_session_id,
510
+ }),
511
+ )
512
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
513
+ Ok(())
514
+ }
515
+
516
+ fn write_reset_complete_event(
517
+ workspace: &Path,
518
+ agent_id: &AgentId,
519
+ stopped: bool,
520
+ started: bool,
521
+ ) -> Result<(), LifecycleError> {
522
+ crate::event_log::EventLog::new(workspace)
523
+ .write(
524
+ "reset_agent.complete",
525
+ serde_json::json!({
526
+ "agent_id": agent_id.as_str(),
527
+ "stopped": stopped,
528
+ "started": started,
529
+ }),
530
+ )
531
+ .map_err(|e| LifecycleError::StatePersist(e.to_string()))?;
532
+ Ok(())
533
+ }