@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,209 @@
1
+ //! 私有内部 helpers (status wire / envelope 校验 / scheduled kind 解析 / activity 信号
2
+ //! 解析 / id 生成 / MessageStatusShadow 占位)。跨子模块/测试可见者升 `pub(crate)`。
3
+
4
+ use std::sync::atomic::{AtomicU64, Ordering};
5
+
6
+ use rusqlite::{params, OptionalExtension};
7
+
8
+ use crate::message_store::MessageStore;
9
+
10
+ use super::{
11
+ ActivityStatus, AgentActivity, DeliveryOutcome, DeliveryRefusal, DeliveryStatus,
12
+ MessagingError, ScheduledKind,
13
+ };
14
+
15
+ /// **PLACEHOLDER** — step 7 `messages.status` 行态 enum (`message_store` lane 的 `MessageStatus`)。
16
+ /// step 7 已落地 core 但尚未导出该 enum (当前用裸 `&str`);本 lane 不猜其精确 variant 集,
17
+ /// 用本地最小占位让 [`DeliveryOutcome::message_status`] 编得过。leader 集成时换成 step 7 的
18
+ /// 权威 `MessageStatus`(`accepted`/`target_resolved`/`injected`/`visible`/`submitted`/
19
+ /// `submitted_unverified`/`delivered`/`acknowledged`/`failed`/`queued_*` …)。
20
+ #[derive(Debug, Clone, PartialEq, Eq)]
21
+ pub struct MessageStatusShadow(pub String);
22
+
23
+ static RESULT_COUNTER: AtomicU64 = AtomicU64::new(0);
24
+
25
+ pub(crate) fn status_wire(status: DeliveryStatus) -> &'static str {
26
+ match status {
27
+ DeliveryStatus::Delivered => "delivered",
28
+ DeliveryStatus::Failed => "failed",
29
+ DeliveryStatus::Queued => "queued",
30
+ DeliveryStatus::Blocked => "blocked",
31
+ DeliveryStatus::Refused => "refused",
32
+ DeliveryStatus::RetryScheduled => "retry_scheduled",
33
+ DeliveryStatus::TrustAutoAnswerExhausted => "trust_auto_answer_exhausted",
34
+ DeliveryStatus::AlreadyDelivered => "already_delivered",
35
+ DeliveryStatus::FallbackLog => "fallback_log",
36
+ DeliveryStatus::BroadcastDelivered => "broadcast_delivered",
37
+ DeliveryStatus::BroadcastPartial => "broadcast_partial",
38
+ DeliveryStatus::FanoutDelivered => "fanout_delivered",
39
+ DeliveryStatus::FanoutPartial => "fanout_partial",
40
+ }
41
+ }
42
+
43
+ pub(crate) fn message_exists(store: &MessageStore, message_id: &str) -> Result<bool, MessagingError> {
44
+ let conn = crate::db::schema::open_db(store.db_path())?;
45
+ let found: Option<String> = conn
46
+ .query_row(
47
+ "select message_id from messages where message_id = ?1",
48
+ params![message_id],
49
+ |row| row.get(0),
50
+ )
51
+ .optional()?;
52
+ Ok(found.is_some())
53
+ }
54
+
55
+ pub(crate) fn next_result_id() -> String {
56
+ let n = RESULT_COUNTER.fetch_add(1, Ordering::Relaxed);
57
+ let nanos = std::time::SystemTime::now()
58
+ .duration_since(std::time::UNIX_EPOCH)
59
+ .map_or(0, |d| d.as_nanos());
60
+ format!("res_{:012x}", (nanos ^ u128::from(n)) & 0xFFFF_FFFF_FFFF)
61
+ }
62
+
63
+ pub(crate) fn next_run_id() -> String {
64
+ let id = next_result_id();
65
+ id.chars().filter(|c| *c != '_').take(12).collect()
66
+ }
67
+
68
+ pub(crate) fn required_str<'a>(value: &'a serde_json::Value, key: &str) -> Result<&'a str, MessagingError> {
69
+ value
70
+ .get(key)
71
+ .and_then(|v| v.as_str())
72
+ .filter(|s| !s.is_empty())
73
+ .ok_or_else(|| MessagingError::Validation(format!("missing required field: {key}")))
74
+ }
75
+
76
+ pub(crate) fn validate_result_envelope(envelope: &serde_json::Value) -> Result<(), MessagingError> {
77
+ let schema = required_str(envelope, "schema_version")?;
78
+ if schema != "result_envelope_v1" {
79
+ return Err(MessagingError::Validation(format!(
80
+ "unsupported schema_version: {schema}"
81
+ )));
82
+ }
83
+ for key in ["task_id", "agent_id", "status", "summary"] {
84
+ let _ = required_str(envelope, key)?;
85
+ }
86
+ for key in ["changes", "tests", "risks", "artifacts", "next_actions"] {
87
+ if !envelope.get(key).is_some_and(serde_json::Value::is_array) {
88
+ return Err(MessagingError::Validation(format!("missing required array field: {key}")));
89
+ }
90
+ }
91
+ Ok(())
92
+ }
93
+
94
+ pub(crate) fn parse_scheduled_kind(kind: &str) -> Result<ScheduledKind, MessagingError> {
95
+ match kind {
96
+ "send" => Ok(ScheduledKind::Send),
97
+ "health_ping" => Ok(ScheduledKind::HealthPing),
98
+ "trust_retry" => Ok(ScheduledKind::TrustRetry),
99
+ other => Err(MessagingError::Validation(format!(
100
+ "unknown scheduled event kind: {other}"
101
+ ))),
102
+ }
103
+ }
104
+
105
+ pub(crate) fn working_seconds(scrollback: &str) -> Option<u64> {
106
+ let lower = scrollback.to_ascii_lowercase();
107
+ let start = lower.find("working (")?;
108
+ let rest = scrollback.get(start + "Working (".len()..)?;
109
+ let seconds = rest.split_once('s')?.0;
110
+ seconds.parse::<u64>().ok()
111
+ }
112
+
113
+ pub(crate) fn non_provider_command(command: &str) -> Option<&str> {
114
+ let base = command.rsplit('/').next().unwrap_or(command);
115
+ let normalized = base.to_ascii_lowercase();
116
+ match normalized.as_str() {
117
+ "" | "codex" | "claude" | "gemini" | "openai" | "team-agent" => None,
118
+ _ => Some(base),
119
+ }
120
+ }
121
+
122
+ pub(crate) fn latest_prompt_signal(scrollback: &str) -> Option<AgentActivity> {
123
+ let lower = scrollback.to_ascii_lowercase();
124
+ let idle_pos = latest_idle_prompt_pos(scrollback);
125
+ let working_pos = ["working", "thinking", "⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]
126
+ .iter()
127
+ .filter_map(|needle| lower.rfind(needle))
128
+ .max();
129
+ match (idle_pos, working_pos) {
130
+ (Some(i), Some(w)) if i > w => Some(idle_activity()),
131
+ (Some(_), None) => Some(idle_activity()),
132
+ (_, Some(_)) => Some(AgentActivity {
133
+ status: ActivityStatus::Working,
134
+ confidence: 0.9,
135
+ rationale: "working_indicator".to_string(),
136
+ }),
137
+ (None, None) => None,
138
+ }
139
+ }
140
+
141
+ fn latest_idle_prompt_pos(scrollback: &str) -> Option<usize> {
142
+ scrollback
143
+ .match_indices('❯')
144
+ .map(|(idx, _)| idx)
145
+ .chain(scrollback.match_indices('›').map(|(idx, _)| idx))
146
+ .max()
147
+ }
148
+
149
+ fn idle_activity() -> AgentActivity {
150
+ AgentActivity {
151
+ status: ActivityStatus::Idle,
152
+ confidence: 0.9,
153
+ rationale: "idle_prompt".to_string(),
154
+ }
155
+ }
156
+
157
+ /// `_fail_leader_delivery` (`leader.py:394`) — **diagnostic ONLY** (#230 I-4 退化):
158
+ /// 历史上返回 `ok=True/status=FallbackLog/channel="fallback_inbox"` 被上游误读为
159
+ /// "已交付的 fallback log"。现新 leader-delivery primitive(`leader_receiver::send_to_leader_receiver`)
160
+ /// 不再调本函数。本函数保留只供老代码路径(scheduler / 兼容旧测试)使用,且其
161
+ /// outcome **不被视为 success**(I-3 反向断言要求 `leader_receiver.rs` 源文件不得含
162
+ /// `DeliveryStatus::FallbackLog` 或 `fallback_inbox` 字面量,所以本函数从 `leader_receiver.rs`
163
+ /// 搬到本 helpers 模块,字面量在此被允许)。
164
+ pub fn fail_leader_delivery(
165
+ workspace: &std::path::Path,
166
+ payload: &serde_json::Value,
167
+ reason: DeliveryRefusal,
168
+ error: Option<&str>,
169
+ ) -> Result<DeliveryOutcome, MessagingError> {
170
+ let store = MessageStore::open(workspace)?;
171
+ let sender = payload.get("sender").and_then(serde_json::Value::as_str).unwrap_or("system");
172
+ let content = payload.get("content").and_then(serde_json::Value::as_str).unwrap_or("");
173
+ let task_id = payload.get("task_id").and_then(serde_json::Value::as_str);
174
+ let message_id = match payload.get("message_id").and_then(serde_json::Value::as_str) {
175
+ Some(existing) => existing.to_string(),
176
+ None => store.create_message(task_id, sender, "leader", content, None, false, None)?,
177
+ };
178
+ store.mark(&message_id, "failed", error)?;
179
+ crate::event_log::EventLog::new(workspace).write(
180
+ "leader_receiver.delivery_failed",
181
+ serde_json::json!({"message_id": message_id, "reason": serde_json::to_value(reason).ok(), "error": error}),
182
+ )?;
183
+ Ok(DeliveryOutcome {
184
+ ok: true,
185
+ status: DeliveryStatus::FallbackLog,
186
+ message_status: MessageStatusShadow("failed".to_string()),
187
+ message_id: Some(message_id),
188
+ verification: None,
189
+ stage: None,
190
+ reason: Some(reason),
191
+ channel: Some("fallback_inbox".to_string()),
192
+ })
193
+ }
194
+
195
+ pub(crate) fn recent_rfc3339(ts: &str, max_age_seconds: i64) -> bool {
196
+ let Ok(parsed) = chrono::DateTime::parse_from_rfc3339(ts) else {
197
+ return false;
198
+ };
199
+ let age = chrono::Utc::now().signed_duration_since(parsed.with_timezone(&chrono::Utc));
200
+ age.num_seconds() <= max_age_seconds
201
+ }
202
+
203
+ pub(crate) fn stale_rfc3339(ts: &str, min_age_seconds: i64) -> bool {
204
+ let Ok(parsed) = chrono::DateTime::parse_from_rfc3339(ts) else {
205
+ return false;
206
+ };
207
+ let age = chrono::Utc::now().signed_duration_since(parsed.with_timezone(&chrono::Utc));
208
+ age.num_seconds() >= min_age_seconds
209
+ }
@@ -0,0 +1,329 @@
1
+ //! leader.py — leader pane 注入边界 + 恰好一次去重门 (card §21/§72)。
2
+
3
+ use std::path::Path;
4
+
5
+ use serde_json::Value;
6
+
7
+ use crate::event_log::EventLog;
8
+ use crate::message_store::{MessageStore, NotificationClaimParams};
9
+ use crate::model::ids::{OwnerEpoch, TaskId};
10
+ use crate::transport::Transport;
11
+
12
+ use super::helpers::MessageStatusShadow;
13
+ use super::{DeliveryOutcome, DeliveryStatus, MessagingError};
14
+
15
+ /// `_send_to_leader_receiver` (`leader.py:69`) — **N31/N32 funnel primitive**:所有 leader-bound
16
+ /// caller(send_message(to=leader) / report_result / request_human / idle reminder /
17
+ /// broadcast-to-leader / peer-mirror / worker.abnormal_exit)统一经过这里。
18
+ ///
19
+ /// 职责 = create_message + leader_notification_log dedup(result_id 时)+ audit + emit
20
+ /// `deliver_to_leader.submit`(funnel 指纹,契约 grep 用)。**不**预 claim:状态留 `accepted`,
21
+ /// 让后续 `deliver_pending_messages` 同一管道做物理 inject(MUST-13 单注入点,leader/worker 同路径
22
+ /// → #229 step2-gate 的 trust-defer 对 leader pane 自然适用)。**不**调 `fail_leader_delivery`
23
+ /// 走 fallback inbox 假绿:无可用 leader pane → 返 `Blocked + channel=rebind_required`(I-4)。
24
+ #[allow(clippy::too_many_arguments)]
25
+ pub fn send_to_leader_receiver(
26
+ workspace: &Path,
27
+ state: &serde_json::Value,
28
+ leader_id: &str,
29
+ content: &str,
30
+ task_id: Option<&TaskId>,
31
+ sender: &str,
32
+ requires_ack: bool,
33
+ result_id: Option<&str>,
34
+ event_log: &EventLog,
35
+ ) -> Result<DeliveryOutcome, MessagingError> {
36
+ let store = MessageStore::open(workspace)?;
37
+ let owner_team = active_team_key(workspace, state);
38
+ if requires_ack {
39
+ event_log.write(
40
+ "leader_receiver.no_ack_forced",
41
+ serde_json::json!({"sender": sender, "leader_id": leader_id, "result_id": result_id}),
42
+ )?;
43
+ }
44
+ let message_id = store.create_message(
45
+ task_id.map(TaskId::as_str),
46
+ sender,
47
+ leader_id,
48
+ content,
49
+ None,
50
+ false,
51
+ Some(&owner_team),
52
+ )?;
53
+ // #231 exactly-once across rebind: insert the leader_notification_log PK BEFORE the
54
+ // unbound-pane check. That way, if the result row gets blocked (I-4) and later the
55
+ // leader rebinds + reclaim replays this row, the dedup PK still gates any second
56
+ // report_result attempt with the same result_id (no duplicate notification).
57
+ if let Some(result_id) = result_id {
58
+ let claim = store.claim_leader_notification_delivery(NotificationClaimParams {
59
+ result_id,
60
+ owner_team_id: Some(&owner_team),
61
+ owner_epoch: owner_epoch_i64(state),
62
+ leader_session_uuid: leader_session_uuid(state),
63
+ proposed_message_id: &message_id,
64
+ envelope_hash: "",
65
+ pane_id: leader_pane_id(state),
66
+ })?;
67
+ if claim.status != "claimed_by_you" {
68
+ // Duplicate result_id: keep first winner's notified_message_id, drop this row
69
+ // by marking failed (deliver_pending will then skip it on its claim guard).
70
+ store.mark(&message_id, "failed", Some("already_notified_by"))?;
71
+ event_log.write(
72
+ "deliver_to_leader.submit",
73
+ serde_json::json!({
74
+ "message_id": claim.notified_message_id,
75
+ "leader_id": leader_id,
76
+ "owner_team_id": owner_team,
77
+ "result_id": result_id,
78
+ "dedup": "already_notified_by",
79
+ }),
80
+ )?;
81
+ return Ok(DeliveryOutcome {
82
+ ok: true,
83
+ status: DeliveryStatus::AlreadyDelivered,
84
+ message_status: MessageStatusShadow("already_notified".to_string()),
85
+ message_id: Some(claim.notified_message_id),
86
+ verification: None,
87
+ stage: None,
88
+ reason: None,
89
+ channel: Some("leader_receiver".to_string()),
90
+ });
91
+ }
92
+ }
93
+ // #230 funnel fingerprint: deliver_to_leader.submit is emitted on EVERY funnel call,
94
+ // including the I-4 unbound case below — the worker's intent IS to submit to leader,
95
+ // the rebind path will replay this row through deliver_pending once the leader pane
96
+ // is bound. Emitting submit only on bound would skew the funnel audit and miss the
97
+ // exactly-once guarantee for blocked-then-reclaimed deliveries.
98
+ event_log.write(
99
+ "deliver_to_leader.submit",
100
+ serde_json::json!({
101
+ "message_id": message_id,
102
+ "leader_id": leader_id,
103
+ "owner_team_id": owner_team,
104
+ "sender": sender,
105
+ "result_id": result_id,
106
+ }),
107
+ )?;
108
+ event_log.write(
109
+ "leader_receiver.queued",
110
+ serde_json::json!({
111
+ "message_id": message_id,
112
+ "leader_id": leader_id,
113
+ "owner_team_id": owner_team,
114
+ "result_id": result_id,
115
+ }),
116
+ )?;
117
+ Ok(DeliveryOutcome {
118
+ ok: true,
119
+ status: DeliveryStatus::Queued,
120
+ message_status: MessageStatusShadow("accepted".to_string()),
121
+ message_id: Some(message_id),
122
+ verification: None,
123
+ stage: None,
124
+ reason: None,
125
+ channel: Some("leader_receiver".to_string()),
126
+ })
127
+ }
128
+
129
+ /// `claim_leader_receiver` (`leader.py:348`):认领/接管 leader pane + `owner_epoch++`。身份判定
130
+ /// 借 step 10 原语 (`_target_matches_owner_identity`/`_leader_command_looks_usable`)。
131
+ pub fn claim_leader_receiver(
132
+ workspace: &Path,
133
+ state: &mut serde_json::Value,
134
+ candidate: &serde_json::Value,
135
+ event_log: &EventLog,
136
+ confirm: bool,
137
+ expected_epoch: Option<OwnerEpoch>,
138
+ ) -> Result<serde_json::Value, MessagingError> {
139
+ if !confirm {
140
+ return Ok(serde_json::json!({
141
+ "ok": false,
142
+ "status": "refused",
143
+ "reason": "confirm_required",
144
+ "action": "team-agent claim-leader --confirm",
145
+ }));
146
+ }
147
+ if let Some(expected) = expected_epoch {
148
+ if owner_epoch(state).is_some_and(|current| current != expected.0) {
149
+ let owner_epoch = owner_epoch(state).unwrap_or(0);
150
+ let bound_pane_id = leader_pane_id(state).map(ToString::to_string);
151
+ let value = serde_json::json!({
152
+ "ok": false,
153
+ "status": "refused",
154
+ "reason": "owner_epoch_advanced",
155
+ "owner_epoch": owner_epoch,
156
+ "bound_pane_id": bound_pane_id,
157
+ });
158
+ event_log.write("leader_receiver.claim_refused", value.clone())?;
159
+ return Ok(value);
160
+ }
161
+ }
162
+ let candidate_pane = candidate.get("pane_id").and_then(Value::as_str);
163
+ if candidate_pane.is_some() && candidate_pane == leader_pane_id(state) {
164
+ return Ok(serde_json::json!({
165
+ "ok": true,
166
+ "status": "already_bound",
167
+ "pane_id": candidate_pane,
168
+ "owner_epoch": owner_epoch(state).unwrap_or(0),
169
+ }));
170
+ }
171
+ let next_epoch = owner_epoch(state).unwrap_or(0).saturating_add(1);
172
+ let Some(root) = state.as_object_mut() else {
173
+ return Err(MessagingError::Routing("runtime state root is not an object".to_string()));
174
+ };
175
+ let owner = root
176
+ .entry("team_owner")
177
+ .or_insert_with(|| serde_json::json!({}));
178
+ if let Some(owner) = owner.as_object_mut() {
179
+ owner.insert("owner_epoch".to_string(), serde_json::json!(next_epoch));
180
+ }
181
+ let receiver = root
182
+ .entry("leader_receiver")
183
+ .or_insert_with(|| serde_json::json!({}));
184
+ if let Some(receiver) = receiver.as_object_mut() {
185
+ receiver.insert("mode".to_string(), serde_json::json!("direct_tmux"));
186
+ receiver.insert("owner_epoch".to_string(), serde_json::json!(next_epoch));
187
+ copy_candidate_field(receiver, candidate, "pane_id");
188
+ copy_candidate_field(receiver, candidate, "provider");
189
+ copy_candidate_field(receiver, candidate, "leader_session_uuid");
190
+ if let Some(socket) = candidate
191
+ .get("tmux_socket")
192
+ .and_then(Value::as_str)
193
+ .filter(|socket| std::path::Path::new(socket).is_absolute())
194
+ .map(str::to_string)
195
+ .or_else(crate::tmux_backend::socket_name_from_tmux_env)
196
+ {
197
+ receiver.insert("tmux_socket".to_string(), serde_json::json!(socket));
198
+ }
199
+ }
200
+ crate::state::persist::save_runtime_state(workspace, state)?;
201
+ event_log.write(
202
+ "leader_receiver.claimed",
203
+ serde_json::json!({"owner_epoch": next_epoch, "candidate": candidate}),
204
+ )?;
205
+ let receiver = state
206
+ .get("leader_receiver")
207
+ .cloned()
208
+ .unwrap_or_else(|| candidate.clone());
209
+ Ok(serde_json::json!({
210
+ "ok": true,
211
+ "status": "claimed",
212
+ "leader_receiver": receiver,
213
+ "owner_epoch": next_epoch,
214
+ }))
215
+ }
216
+
217
+ /// `_mirror_peer_message_to_leader` (`leader.py:31`):peer→peer 消息镜像到 leader receiver。
218
+ pub fn mirror_peer_message_to_leader(
219
+ workspace: &Path,
220
+ state: &serde_json::Value,
221
+ sender: &str,
222
+ recipient: &str,
223
+ content: &str,
224
+ task_id: Option<&TaskId>,
225
+ event_log: &EventLog,
226
+ ) -> Result<(), MessagingError> {
227
+ let _ = send_to_leader_receiver(
228
+ workspace,
229
+ state,
230
+ "leader",
231
+ content,
232
+ task_id,
233
+ sender,
234
+ false,
235
+ None,
236
+ event_log,
237
+ )?;
238
+ let _ = recipient;
239
+ Ok(())
240
+ }
241
+
242
+ pub(crate) fn active_team_key(workspace: &Path, state: &Value) -> String {
243
+ state
244
+ .get("active_team_key")
245
+ .and_then(Value::as_str)
246
+ .filter(|team| !team.is_empty())
247
+ .map(ToString::to_string)
248
+ .or_else(|| workspace.file_name().map(|name| name.to_string_lossy().to_string()))
249
+ .unwrap_or_else(|| "current".to_string())
250
+ }
251
+
252
+ fn owner_epoch(state: &Value) -> Option<u64> {
253
+ receiver_or_owner_field(state, "team_owner", "owner_epoch")
254
+ .and_then(Value::as_u64)
255
+ .or_else(|| receiver_or_owner_field(state, "leader_receiver", "owner_epoch").and_then(Value::as_u64))
256
+ }
257
+
258
+ fn receiver_or_owner_field<'a>(state: &'a Value, record: &str, field: &str) -> Option<&'a Value> {
259
+ state
260
+ .get(record)
261
+ .and_then(|v| v.get(field))
262
+ .or_else(|| {
263
+ let active = state.get("active_team_key").and_then(Value::as_str)?;
264
+ state
265
+ .get("teams")
266
+ .and_then(Value::as_object)
267
+ .and_then(|teams| teams.get(active))
268
+ .and_then(|team| team.get(record))
269
+ .and_then(|v| v.get(field))
270
+ })
271
+ }
272
+
273
+ fn leader_record_field<'a>(state: &'a Value, field: &str) -> Option<&'a Value> {
274
+ receiver_or_owner_field(state, "leader_receiver", field)
275
+ .or_else(|| receiver_or_owner_field(state, "team_owner", field))
276
+ }
277
+
278
+ fn owner_epoch_i64(state: &Value) -> Option<i64> {
279
+ owner_epoch(state).and_then(|epoch| i64::try_from(epoch).ok())
280
+ }
281
+
282
+ fn leader_session_uuid(state: &Value) -> Option<&str> {
283
+ leader_record_field(state, "leader_session_uuid").and_then(Value::as_str)
284
+ }
285
+
286
+ pub(crate) fn leader_pane_bound_but_not_live(workspace: &Path, state: &Value) -> bool {
287
+ leader_pane_id(state)
288
+ .is_some_and(|pane_id| !leader_pane_is_live(workspace, state, pane_id))
289
+ }
290
+
291
+ fn leader_pane_is_live(workspace: &Path, state: &Value, pane_id: &str) -> bool {
292
+ if let Some(socket) = leader_tmux_socket(state) {
293
+ return crate::tmux_backend::TmuxBackend::for_tmux_endpoint(socket)
294
+ .list_targets()
295
+ .unwrap_or_default()
296
+ .iter()
297
+ .any(|target| target.pane_id.as_str() == pane_id);
298
+ }
299
+ let mut targets = crate::tmux_backend::TmuxBackend::for_workspace(workspace)
300
+ .list_targets()
301
+ .unwrap_or_default();
302
+ targets.extend(
303
+ crate::tmux_backend::TmuxBackend::new()
304
+ .list_targets()
305
+ .unwrap_or_default(),
306
+ );
307
+ targets.iter().any(|target| target.pane_id.as_str() == pane_id)
308
+ }
309
+
310
+ fn leader_pane_id(state: &Value) -> Option<&str> {
311
+ leader_record_field(state, "pane_id").and_then(Value::as_str)
312
+ }
313
+
314
+ fn leader_tmux_socket(state: &Value) -> Option<&str> {
315
+ leader_record_field(state, "tmux_socket")
316
+ .and_then(Value::as_str)
317
+ .filter(|socket| !socket.is_empty())
318
+ .filter(|socket| std::path::Path::new(socket).is_absolute())
319
+ }
320
+
321
+ fn copy_candidate_field(
322
+ out: &mut serde_json::Map<String, Value>,
323
+ candidate: &Value,
324
+ key: &str,
325
+ ) {
326
+ if let Some(value) = candidate.get(key) {
327
+ out.insert(key.to_string(), value.clone());
328
+ }
329
+ }
@@ -0,0 +1,147 @@
1
+ //! step 11 · messaging — delivery / scheduler / results / collect / report_result /
2
+ //! watchers / leader-receiver / selftest 编排状态机 (ROUND-0 SKELETON).
3
+ //!
4
+ //! Card: `docs/phase0/subsystems/11-messaging.md` (type table §38-53, public API §60-75,
5
+ //! bug/trap §118-132, module map §134-149).
6
+ //! Truth source (READ-ONLY) `team-agent-public` @ v0.2.11 / `439bef8`:
7
+ //! - `messaging/send.py` · `delivery.py` · `internal_delivery.py` · `scheduler.py`
8
+ //! - `messaging/results.py` · `result_delivery.py` · `leader.py` · `leader_panes.py`
9
+ //! - `messaging/idle_alerts.py` · `activity_detector.py` · `trust_auto_answer.py`
10
+ //! - `messaging/session_drift.py` · `owner_bypass.py` · `routing.py`
11
+ //! - `diagnose/comms.py` (`run_comms_selftest` / `evaluate_idle_behavior`)
12
+ //!
13
+ //! SCOPE — this is the type/interface LAYER (so RED contracts can NAME these and
14
+ //! compile), NOT the implementation. It mirrors only the CONTRACT-NAMED public
15
+ //! surface; the 18 Python files' internal helpers are deferred (see
16
+ //! `cross_deps_or_placeholders`). Bodies are `unimplemented!("step11 port: …")`.
17
+ //!
18
+ //! REUSE (do NOT redefine): [`MessageStore`] + [`NotificationClaimParams`] (step 7);
19
+ //! [`Transport`] / [`Target`] / [`PaneId`] (step 9); [`Provider`] / [`MessageStatus`]
20
+ //! shadow (step 2 enums); [`worker_sender_bypasses_owner_gate`] (step 5 owner-gate);
21
+ //! [`RouteResult`] / [`route_task`] (step 2 routing); [`ProviderError`] / classifier
22
+ //! types (step 8); [`EventLog`] (step 4).
23
+ //!
24
+ //! 铁律 (card §118-132, Rust 绝不重蹈):
25
+ //! - **unknown ≠ idle** (bug-071/077/085): [`ActivityStatus`] 穷尽 match,`Uncertain`
26
+ //! 独立分支显式 block,**无 `_ => Idle` 兜底**。
27
+ //! - **dedup key 不含 leader_session_uuid** (Stage 12): 唯一去重原语 =
28
+ //! [`MessageStore::claim_leader_notification_delivery`] 的 `INSERT OR IGNORE`;
29
+ //! `peek` 只读快路径,非去重本身。`notified_message_id` requeue 必须存活 (Gap 32)。
30
+ //! - **trust 自动应答 fail-safe** (bug-064/082): pane 宽度查询失败时**绝不**返回默认
31
+ //! 宽度 ([`PaneWidthQuery::Failed`]),matcher 退回精确相等;trust 应答只对自己工作
32
+ //! 目录 realpath 全等。重试有界 ([`TRUST_RETRY_MAX_ATTEMPTS`]),终态显式。
33
+ //! - **scheduled kind 穷尽**: [`ScheduledKind`] 穷尽 match,漏 kind 编不过,无运行时 fallback。
34
+ //! - **busy → 延后不丢**: [`DeliveryStatus::Queued`] 不 mark failed,留队列等下次 tick。
35
+ //! - **selftest 零 provider SDK** (§84/MUST-NOT-13): [`run_comms_selftest`] 走 trait
36
+ //! mock,断言 `{anthropic,openai,httpx} == 0`,见 [`ProviderSdkCalls`]。
37
+ //!
38
+ //! §10:scheduler / result_delivery / leader_receiver / delivery 子路径被 coordinator
39
+ //! daemon (step 12) tick 直接调用 (bug-084 崩溃面) → 所有投递/调度/IO 返
40
+ //! `Result<_, MessagingError>` (thiserror)。`#![deny(unwrap/expect/panic)]` 由 leader
41
+ //! 集成时统一加,本 ROUND-0 skeleton 不加 (与 provider.rs/transport.rs 一致)。
42
+
43
+ // ROUND-0 skeleton:fn body 全 unimplemented!() → import/field/method 暂未被用;P2 porter 落实现时移除。
44
+ #![allow(dead_code, unused_imports)]
45
+ // §10:scheduler/result_delivery/leader_receiver/delivery 子路径被 coordinator daemon tick 直接调用
46
+ // (bug-084 崩溃面)→ 实现层禁 unwrap/expect/panic(unimplemented!() stub 不被拦);tests 各自 allow。
47
+ #![deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
48
+
49
+ use thiserror::Error;
50
+
51
+ // ── REUSE: step 2 model ────────────────────────────────────────────────────
52
+ // 这些模块根级 `use` 保持子模块测试 `use super::*` 的名字解析不变 (TeamKey / PaneId / …)。
53
+ use crate::model::enums::Provider;
54
+ use crate::model::ids::{LeaderSessionUuid, OwnerEpoch, TaskId, TeamKey};
55
+ // route_task 已在 step 2 落地 (model::routing),send 子例程 REUSE,不重定义。
56
+ pub use crate::model::routing::{route_task, RouteResult};
57
+
58
+ // ── REUSE: step 4 event_log / step 7 message_store / step 9 transport ───────
59
+ use crate::event_log::EventLog;
60
+ use crate::message_store::MessageStore;
61
+ use crate::transport::{PaneId, Target, Transport};
62
+
63
+ pub mod activity;
64
+ pub mod delivery;
65
+ pub mod helpers;
66
+ pub mod leader_receiver;
67
+ pub mod peers;
68
+ pub mod results;
69
+ pub mod scheduler;
70
+ pub mod selftest;
71
+ pub mod send;
72
+ pub mod trust;
73
+ pub mod types;
74
+ pub mod watchers;
75
+
76
+ // ── re-export: 保持 `crate::messaging::X` 与 test `super::X` 解析不变 ──────────
77
+ pub use activity::{classify_agent_activity, detect_cross_worker_deadlocks, detect_idle_fallbacks};
78
+ pub use delivery::{
79
+ deliver_pending_message, deliver_pending_messages, deliver_stored_message, execute_trust_retry,
80
+ handle_trust_retry_needed, record_turn_open_if_leader_to_worker,
81
+ retry_injection_after_trust_auto_answer, stamp_first_send_at_if_leader_to_worker,
82
+ tmux_pane_width,
83
+ };
84
+ pub use helpers::fail_leader_delivery;
85
+ pub use leader_receiver::{
86
+ claim_leader_receiver, mirror_peer_message_to_leader, send_to_leader_receiver,
87
+ };
88
+ pub use peers::allow_peer_talk;
89
+ pub use results::{collect, collect_results_and_notify_watchers, report_result};
90
+ pub use scheduler::{detect_stuck_agents, fire_due_scheduled_events, stuck_cancel, stuck_list};
91
+ pub use selftest::{evaluate_idle_behavior, run_comms_selftest, CommsSelftestDriver};
92
+ pub use send::{apply_worker_sender_bypass, send_message, session_drift_refusal, MessageTarget, SendOptions};
93
+ pub use trust::{attempt_trust_auto_answer, TrustAnswerOutcome};
94
+ pub use types::{
95
+ ActivityStatus, AgentActivity, AlertSnapshot, AlertSuppression, AlertType, CheckEvidence,
96
+ CheckKind, CheckStatus, DeliveryOutcome, DeliveryRefusal, DeliveryStage, DeliveryStatus,
97
+ IdleEvaluation, LeaderNotificationKey, LeaderReceiver, PaneWidthQuery, ProviderSdkCalls,
98
+ ReceiverMode, ScheduledKind, SelftestCheck, SelftestReport, SendEventPayload, TrustRetryPayload,
99
+ WatcherNotice, RESULT_DELIVERY_MAX_ATTEMPTS, SEND_RETRY_MAX_ATTEMPTS, TRUST_RETRY_BACKOFF_SECONDS,
100
+ TRUST_RETRY_MAX_ATTEMPTS,
101
+ };
102
+ pub use watchers::{
103
+ delivered_result_message, format_result_watcher_notification, notify_result_watchers,
104
+ requeue_after_claim_leader, requeue_delivery_exhausted_watchers, result_id_from_text,
105
+ retry_result_deliveries,
106
+ };
107
+
108
+ // `MessageStatusShadow` 是 [`DeliveryOutcome::message_status`] 的公有字段类型,原在模块根
109
+ // 可见 (`crate::messaging::MessageStatusShadow`),故 `pub` 再导出保持解析不变。
110
+ pub use helpers::MessageStatusShadow;
111
+
112
+ // ===========================================================================
113
+ // ERROR (thiserror, lib 边界;daemon tick 经此 Result 退避,§10/bug-084)
114
+ // ===========================================================================
115
+
116
+ /// messaging 子系统错误。投递/调度/IO 失败一律经此 `Result` 上抛,由 step 12 主循环
117
+ /// catch + 退避;**绝不**在调度/投递循环 panic (bug-084)。
118
+ #[derive(Debug, Error)]
119
+ pub enum MessagingError {
120
+ #[error("db: {0}")]
121
+ Db(#[from] crate::db::DbError),
122
+ #[error("sqlite: {0}")]
123
+ Sqlite(#[from] rusqlite::Error),
124
+ #[error("message store: {0}")]
125
+ Store(#[from] crate::message_store::MessageStoreError),
126
+ #[error("transport: {0}")]
127
+ Transport(#[from] crate::transport::TransportError),
128
+ #[error("state: {0}")]
129
+ State(#[from] crate::state::StateError),
130
+ #[error("event log: {0}")]
131
+ EventLog(#[from] crate::event_log::EventLogError),
132
+ #[error("provider: {0}")]
133
+ Provider(String),
134
+ /// envelope / 输入校验 (validate_result_envelope 等)。
135
+ #[error("validation: {0}")]
136
+ Validation(String),
137
+ /// team 解析 / 路由 / owner-gate 拒绝 (非 IO,语义性失败)。
138
+ #[error("routing: {0}")]
139
+ Routing(String),
140
+ #[error("io: {0}")]
141
+ Io(#[from] std::io::Error),
142
+ #[error("json: {0}")]
143
+ Json(#[from] serde_json::Error),
144
+ }
145
+
146
+ #[cfg(test)]
147
+ mod tests;