@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
@@ -0,0 +1,252 @@
1
+ //! 测试默认零真-spawn、零真-tmux:lifecycle/CLI spawn 路径必经 *_with_transport 注入离线
2
+ //! mock;确需真 tmux 者必须 provider:fake + RAII kill_server 守卫 + #[ignore=real-machine].
3
+
4
+ use std::collections::BTreeMap;
5
+ use std::path::Path;
6
+ use std::sync::{Arc, Mutex};
7
+
8
+ use super::{
9
+ AttachOutcome, BackendKind, CaptureRange, CapturedText, InjectPayload, InjectReport,
10
+ InjectStage, InjectVerification, Key, PaneField, PaneId, PaneInfo, PaneLiveness, SessionName,
11
+ SetEnvOutcome, SpawnResult, SubmitVerification, Target, Transport, TransportError,
12
+ TurnVerification, WindowName,
13
+ };
14
+
15
+ #[derive(Debug, Clone, PartialEq, Eq)]
16
+ pub struct SpawnRecord {
17
+ pub kind: String,
18
+ pub argv: Vec<String>,
19
+ }
20
+
21
+ #[derive(Debug, Clone, Default)]
22
+ struct OfflineState {
23
+ session_present: bool,
24
+ targets: Vec<PaneInfo>,
25
+ windows: Vec<WindowName>,
26
+ calls: Vec<&'static str>,
27
+ spawns: Vec<SpawnRecord>,
28
+ inject_targets: Vec<Target>,
29
+ inject_payloads: Vec<String>,
30
+ }
31
+
32
+ #[derive(Debug, Clone, Default)]
33
+ pub struct OfflineTransport {
34
+ inner: Arc<Mutex<OfflineState>>,
35
+ }
36
+
37
+ impl OfflineTransport {
38
+ pub fn new() -> Self {
39
+ Self::default()
40
+ }
41
+
42
+ pub fn with_session_present(self, present: bool) -> Self {
43
+ self.with_state(|state| state.session_present = present);
44
+ self
45
+ }
46
+
47
+ pub fn with_targets(self, targets: Vec<PaneInfo>) -> Self {
48
+ self.with_state(|state| state.targets = targets);
49
+ self
50
+ }
51
+
52
+ pub fn with_windows(self, windows: Vec<WindowName>) -> Self {
53
+ self.with_state(|state| state.windows = windows);
54
+ self
55
+ }
56
+
57
+ pub fn calls(&self) -> Vec<&'static str> {
58
+ self.with_state(|state| state.calls.clone())
59
+ }
60
+
61
+ pub fn spawn_records(&self) -> Vec<(String, Vec<String>)> {
62
+ self.with_state(|state| {
63
+ state
64
+ .spawns
65
+ .iter()
66
+ .map(|record| (record.kind.clone(), record.argv.clone()))
67
+ .collect()
68
+ })
69
+ }
70
+
71
+ pub fn inject_targets(&self) -> Vec<Target> {
72
+ self.with_state(|state| state.inject_targets.clone())
73
+ }
74
+
75
+ pub fn inject_payloads(&self) -> Vec<String> {
76
+ self.with_state(|state| state.inject_payloads.clone())
77
+ }
78
+
79
+ fn record(&self, call: &'static str) {
80
+ self.with_state(|state| state.calls.push(call));
81
+ }
82
+
83
+ fn with_state<T>(&self, f: impl FnOnce(&mut OfflineState) -> T) -> T {
84
+ match self.inner.lock() {
85
+ Ok(mut guard) => f(&mut guard),
86
+ Err(poisoned) => {
87
+ let mut guard = poisoned.into_inner();
88
+ f(&mut guard)
89
+ }
90
+ }
91
+ }
92
+
93
+ fn spawn_result(
94
+ &self,
95
+ kind: &'static str,
96
+ session: &SessionName,
97
+ window: &WindowName,
98
+ argv: &[String],
99
+ ) -> SpawnResult {
100
+ let pane_index = self.with_state(|state| {
101
+ state.calls.push(kind);
102
+ state.spawns.push(SpawnRecord { kind: kind.to_string(), argv: argv.to_vec() });
103
+ state.spawns.len().saturating_sub(1)
104
+ });
105
+ SpawnResult {
106
+ pane_id: PaneId::new(format!("%{pane_index}")),
107
+ session: session.clone(),
108
+ window: window.clone(),
109
+ child_pid: None,
110
+ }
111
+ }
112
+
113
+ fn inject_report() -> InjectReport {
114
+ InjectReport {
115
+ stage_reached: InjectStage::Submit,
116
+ inject_verification: InjectVerification::CaptureContainsToken,
117
+ submit_verification: SubmitVerification::EnterSentWithoutPlaceholderCheck,
118
+ turn_verification: TurnVerification::NotYetObserved,
119
+ attempts: 1,
120
+ }
121
+ }
122
+ }
123
+
124
+ impl Transport for OfflineTransport {
125
+ fn kind(&self) -> BackendKind {
126
+ BackendKind::Tmux
127
+ }
128
+
129
+ fn spawn_first(
130
+ &self,
131
+ session: &SessionName,
132
+ window: &WindowName,
133
+ argv: &[String],
134
+ _cwd: &Path,
135
+ _env: &BTreeMap<String, String>,
136
+ ) -> Result<SpawnResult, TransportError> {
137
+ Ok(self.spawn_result("spawn_first", session, window, argv))
138
+ }
139
+
140
+ fn spawn_into(
141
+ &self,
142
+ session: &SessionName,
143
+ window: &WindowName,
144
+ argv: &[String],
145
+ _cwd: &Path,
146
+ _env: &BTreeMap<String, String>,
147
+ ) -> Result<SpawnResult, TransportError> {
148
+ Ok(self.spawn_result("spawn_into", session, window, argv))
149
+ }
150
+
151
+ fn inject(
152
+ &self,
153
+ target: &Target,
154
+ payload: &InjectPayload,
155
+ _submit: Key,
156
+ _bracketed: bool,
157
+ ) -> Result<InjectReport, TransportError> {
158
+ self.with_state(|state| {
159
+ state.calls.push("inject");
160
+ state.inject_targets.push(target.clone());
161
+ state.inject_payloads.push(match payload {
162
+ InjectPayload::Empty => String::new(),
163
+ InjectPayload::Text(text) => text.clone(),
164
+ });
165
+ });
166
+ Ok(Self::inject_report())
167
+ }
168
+
169
+ fn send_keys(&self, _target: &Target, _keys: &[Key]) -> Result<(), TransportError> {
170
+ self.record("send_keys");
171
+ Ok(())
172
+ }
173
+
174
+ fn capture(
175
+ &self,
176
+ _target: &Target,
177
+ range: CaptureRange,
178
+ ) -> Result<CapturedText, TransportError> {
179
+ self.record("capture");
180
+ Ok(CapturedText { text: String::new(), range })
181
+ }
182
+
183
+ fn query(
184
+ &self,
185
+ _target: &Target,
186
+ _field: PaneField,
187
+ ) -> Result<Option<String>, TransportError> {
188
+ self.record("query");
189
+ Ok(None)
190
+ }
191
+
192
+ fn liveness(&self, _pane: &PaneId) -> Result<PaneLiveness, TransportError> {
193
+ self.record("liveness");
194
+ Ok(PaneLiveness::Unknown)
195
+ }
196
+
197
+ fn list_targets(&self) -> Result<Vec<PaneInfo>, TransportError> {
198
+ Ok(self.with_state(|state| {
199
+ state.calls.push("list_targets");
200
+ state.targets.clone()
201
+ }))
202
+ }
203
+
204
+ fn has_session(&self, _session: &SessionName) -> Result<bool, TransportError> {
205
+ Ok(self.with_state(|state| {
206
+ state.calls.push("has_session");
207
+ state.session_present
208
+ }))
209
+ }
210
+
211
+ fn list_windows(
212
+ &self,
213
+ _session: &SessionName,
214
+ ) -> Result<Vec<WindowName>, TransportError> {
215
+ Ok(self.with_state(|state| {
216
+ state.calls.push("list_windows");
217
+ if state.windows.is_empty() {
218
+ state.targets.iter().filter_map(|pane| pane.window_name.clone()).collect()
219
+ } else {
220
+ state.windows.clone()
221
+ }
222
+ }))
223
+ }
224
+
225
+ fn set_session_env(
226
+ &self,
227
+ _session: &SessionName,
228
+ _key: &str,
229
+ _value: &str,
230
+ ) -> Result<SetEnvOutcome, TransportError> {
231
+ self.record("set_session_env");
232
+ Ok(SetEnvOutcome::Applied)
233
+ }
234
+
235
+ fn kill_session(&self, _session: &SessionName) -> Result<(), TransportError> {
236
+ self.record("kill_session");
237
+ Ok(())
238
+ }
239
+
240
+ fn kill_window(&self, _target: &Target) -> Result<(), TransportError> {
241
+ self.record("kill_window");
242
+ Ok(())
243
+ }
244
+
245
+ fn attach_session(
246
+ &self,
247
+ _session: &SessionName,
248
+ ) -> Result<AttachOutcome, TransportError> {
249
+ self.record("attach_session");
250
+ Ok(AttachOutcome::Attached)
251
+ }
252
+ }
@@ -0,0 +1,327 @@
1
+ // ════════════════════════════════════════════════════════════════════════
2
+ // GROUP G — PaneField 单字段查询映射(display-message -p -F → list json 字段)
3
+ // 非 tmux 后端无对应概念的字段返回 Ok(None)(typed「不适用」)。
4
+ // ════════════════════════════════════════════════════════════════════════
5
+ use super::*;
6
+
7
+ #[test]
8
+ fn pane_field_variants_distinct() {
9
+ // 6 个查询字段互不相等(避免散字符串打地鼠)。
10
+ let all = [
11
+ PaneField::PaneId,
12
+ PaneField::PaneMode,
13
+ PaneField::PaneWidth,
14
+ PaneField::PaneCurrentCommand,
15
+ PaneField::PaneCurrentPath,
16
+ PaneField::SessionName,
17
+ ];
18
+ for (i, a) in all.iter().enumerate() {
19
+ for (j, b) in all.iter().enumerate() {
20
+ assert_eq!(i == j, a == b, "{a:?} vs {b:?}");
21
+ }
22
+ }
23
+ }
24
+
25
+ #[test]
26
+ fn query_pane_width_returns_numeric_string() {
27
+ // contracts-rust-native test_query_pane_width_parity:
28
+ // tmux display-message '#{pane_width}' / wezterm list json size.cols。RED via stub。
29
+ let t = tmux();
30
+ let w = t
31
+ .query(&Target::Pane(PaneId::new("%7")), PaneField::PaneWidth)
32
+ .expect("query ok");
33
+ assert!(w.is_some(), "tmux must report a pane width");
34
+ assert!(
35
+ w.unwrap().chars().all(|c| c.is_ascii_digit()),
36
+ "width is numeric"
37
+ );
38
+
39
+ // 命令构造 golden(STEP-9):PaneWidth → display-message -p -t %7 -F '#{pane_width}'
40
+ // (delivery.py:34)。格式 check 不锁字段映射 —— 钉死 argv。
41
+ assert_eq!(
42
+ tmux_query_argv(&PaneId::new("%7"), PaneField::PaneWidth),
43
+ vec![
44
+ "tmux",
45
+ "display-message",
46
+ "-p",
47
+ "-t",
48
+ "%7",
49
+ "-F",
50
+ "#{pane_width}"
51
+ ]
52
+ );
53
+ // PaneMode 用裸格式参数(无 -F),pane_mode 直传(tmux_io.py:403)。
54
+ assert_eq!(
55
+ tmux_query_argv(&PaneId::new("%7"), PaneField::PaneMode),
56
+ vec!["tmux", "display-message", "-p", "-t", "%7", "#{pane_mode}"]
57
+ );
58
+ }
59
+
60
+ #[test]
61
+ fn query_pane_mode_is_not_applicable_on_wezterm() {
62
+ // 设计 §3.1 query(pane_mode):非 tmux 后端无 pane-mode 概念 → recognizer 恒当 Input。
63
+ // typed「不适用」收口为 Ok(None)(porter 不能返回 Ok(Some("anything")) 假绿)。RED via stub。
64
+ let w = wezterm();
65
+ let mode = w
66
+ .query(&Target::Pane(PaneId::new("1")), PaneField::PaneMode)
67
+ .expect("query pane_mode ok on wezterm (no error)");
68
+ assert_eq!(
69
+ mode, None,
70
+ "wezterm has no pane-mode concept -> typed not-applicable is Ok(None)"
71
+ );
72
+ }
73
+
74
+ // ════════════════════════════════════════════════════════════════════════
75
+ // GROUP H — liveness 三态(unknown ≠ dead ≠ live)
76
+ // contracts-rust-native test_pane_liveness_three_valued_parity。
77
+ // ════════════════════════════════════════════════════════════════════════
78
+
79
+ #[test]
80
+ fn freshly_spawned_pane_is_live_not_unknown_via_transport() {
81
+ // 已知存活 fixture:刚 spawn 的 pane 必须 == Live(不是 all-variant 析取的同义反复)。
82
+ // 镜像 conpty_cross_process...==Unknown 的强度,但锁的是 Live 这一极。
83
+ // RED via stub:spawn_first/liveness 都 unimplemented!()。
84
+ let t = tmux();
85
+ let env = BTreeMap::new();
86
+ let spawned = t
87
+ .spawn_first(
88
+ &SessionName::new("team-sess"),
89
+ &WindowName::new("win-1"),
90
+ &["sh".into(), "-lc".into(), "sleep 60".into()],
91
+ Path::new("/tmp/ws"),
92
+ &env,
93
+ )
94
+ .expect("spawn_first ok");
95
+ assert_eq!(
96
+ t.liveness(&spawned.pane_id).expect("liveness ok"),
97
+ PaneLiveness::Live,
98
+ "a freshly-spawned (sleeping) pane must read Live, never Unknown/Dead"
99
+ );
100
+ }
101
+
102
+ #[test]
103
+ fn conpty_cross_process_pane_is_unknown_never_optimistic_live() {
104
+ // contracts-rust-native:ConPTY 对非自有/跨进程 pane 判 Unknown,绝不乐观当 Live
105
+ // (bug-085 穷尽三态;Unknown 显式 block ping,不 fallthrough idle)。RED via stub。
106
+ let c = conpty();
107
+ let res = c.liveness(&PaneId::new("foreign-leader")).expect("liveness ok");
108
+ assert_eq!(
109
+ res,
110
+ PaneLiveness::Unknown,
111
+ "ConPTY cross-process pane must be Unknown, never optimistic Live"
112
+ );
113
+ }
114
+
115
+ // ════════════════════════════════════════════════════════════════════════
116
+ // GROUP I — 能力性拒绝是 typed outcome,不是 Err(§10)
117
+ // SetEnvOutcome / AttachOutcome 的 per-backend 语义(transport-backend-design §4c/§1.3)。
118
+ // ════════════════════════════════════════════════════════════════════════
119
+
120
+ #[test]
121
+ fn set_env_outcome_variants_carry_per_backend_semantics() {
122
+ // 三态语义不可混:tmux=Applied;wezterm/conpty worker=InternalizedAtSpawn;
123
+ // 外部 leader pane=UnsupportedForExternalPane{reason}(typed,审计,非 Err)。
124
+ assert_ne!(SetEnvOutcome::Applied, SetEnvOutcome::InternalizedAtSpawn);
125
+ let unsupported = SetEnvOutcome::UnsupportedForExternalPane {
126
+ reason: "external leader pane cannot be re-seeded".into(),
127
+ };
128
+ assert_ne!(unsupported, SetEnvOutcome::Applied);
129
+ if let SetEnvOutcome::UnsupportedForExternalPane { reason } = unsupported {
130
+ assert!(!reason.is_empty(), "refusal must carry an auditable reason");
131
+ } else {
132
+ panic!("expected UnsupportedForExternalPane");
133
+ }
134
+ }
135
+
136
+ #[test]
137
+ fn set_session_env_on_tmux_worker_is_applied_typed_not_err() {
138
+ // tmux set-environment → Applied(§4c)。能力性结果走 Ok(outcome),不走 Err。RED via stub。
139
+ let t = tmux();
140
+ let outcome = t
141
+ .set_session_env(&SessionName::new("team-sess"), "TEAM_AGENT_ID", "w1")
142
+ .expect("set_session_env must be Ok(outcome), capability refusal is NOT Err");
143
+ assert_eq!(outcome, SetEnvOutcome::Applied);
144
+ }
145
+
146
+ #[test]
147
+ fn set_session_env_on_wezterm_worker_is_internalized_at_spawn() {
148
+ // wezterm/conpty worker env 已在 spawn 注入 → InternalizedAtSpawn,非 Err、非假绿。RED via stub。
149
+ let w = wezterm();
150
+ let outcome = w
151
+ .set_session_env(&SessionName::new("team-sess"), "K", "V")
152
+ .expect("must be Ok(outcome)");
153
+ assert_eq!(outcome, SetEnvOutcome::InternalizedAtSpawn);
154
+ }
155
+
156
+ #[test]
157
+ fn attach_outcome_variants_are_distinct_per_backend() {
158
+ // tmux=Attached;wezterm=GuiAttachIsImplicit;conpty=Unsupported{reason}。
159
+ assert_ne!(AttachOutcome::Attached, AttachOutcome::GuiAttachIsImplicit);
160
+ let unsup = AttachOutcome::Unsupported {
161
+ reason: "conpty has no attach concept".into(),
162
+ };
163
+ assert_ne!(unsup, AttachOutcome::Attached);
164
+ if let AttachOutcome::Unsupported { reason } = unsup {
165
+ assert!(!reason.is_empty());
166
+ } else {
167
+ panic!("expected Unsupported");
168
+ }
169
+ }
170
+
171
+ #[test]
172
+ fn attach_session_conpty_is_unsupported_typed_not_err() {
173
+ // contracts/design §3.1:conpty attach_session → Unsupported(typed,不假绿,非 Err)。RED via stub。
174
+ let c = conpty();
175
+ let outcome = c
176
+ .attach_session(&SessionName::new("team-sess"))
177
+ .expect("capability refusal must be Ok(Unsupported), not Err");
178
+ // §10 不变量:能力性拒绝必须携带可审计 reason(不仅锁 variant,还锁 payload 非空)。
179
+ match outcome {
180
+ AttachOutcome::Unsupported { reason } => {
181
+ assert!(
182
+ !reason.is_empty(),
183
+ "ConPTY attach refusal must carry an auditable reason"
184
+ );
185
+ }
186
+ other => panic!("expected AttachOutcome::Unsupported, got {other:?}"),
187
+ }
188
+ }
189
+
190
+ #[test]
191
+ fn attach_session_wezterm_gui_attach_is_implicit() {
192
+ // wezterm GUI 启动即 attach,无独立动作 → GuiAttachIsImplicit。RED via stub。
193
+ let w = wezterm();
194
+ let outcome = w
195
+ .attach_session(&SessionName::new("team-sess"))
196
+ .expect("must be Ok(outcome)");
197
+ assert_eq!(outcome, AttachOutcome::GuiAttachIsImplicit);
198
+ }
199
+
200
+ // ════════════════════════════════════════════════════════════════════════
201
+ // GROUP J — TransportError Display(thiserror;lib 边界)+ I/O 才走 Err
202
+ // bug-084:os I/O/timeout 必须 Result 化,绝不裸穿 coordinator tick。
203
+ // ════════════════════════════════════════════════════════════════════════
204
+
205
+ #[test]
206
+ fn transport_error_display_messages_byte_locked() {
207
+ // 错误信息含定位字段(backend/stage/argv/target),便于 §5 系统集成调试。
208
+ let e = TransportError::Inject {
209
+ stage: InjectStage::PasteBuffer,
210
+ source: std::io::Error::other("boom"),
211
+ };
212
+ assert_eq!(
213
+ e.to_string(),
214
+ "inject failed at stage PasteBuffer: boom"
215
+ );
216
+
217
+ let sub = TransportError::Subprocess {
218
+ argv: vec!["tmux".into(), "paste-buffer".into()],
219
+ code: Some(1),
220
+ stderr: "no such pane".into(),
221
+ };
222
+ assert_eq!(
223
+ sub.to_string(),
224
+ "subprocess [\"tmux\", \"paste-buffer\"] exited with Some(1): no such pane"
225
+ );
226
+
227
+ let mux = TransportError::MuxUnavailable {
228
+ backend: BackendKind::WezTerm,
229
+ detail: "mux server not reachable".into(),
230
+ };
231
+ assert_eq!(
232
+ mux.to_string(),
233
+ "mux unavailable on WezTerm: mux server not reachable"
234
+ );
235
+
236
+ let nf = TransportError::TargetNotFound {
237
+ target: "%99".into(),
238
+ };
239
+ assert_eq!(nf.to_string(), "target not found: %99");
240
+ }
241
+
242
+ #[test]
243
+ fn io_error_converts_into_transport_error_via_from() {
244
+ // bug-084:I/O 错误 typed 化(#[from] std::io::Error)。
245
+ let io = std::io::Error::new(std::io::ErrorKind::PermissionDenied, "denied");
246
+ let te: TransportError = io.into();
247
+ assert!(matches!(te, TransportError::Io(_)));
248
+ }
249
+
250
+ // ════════════════════════════════════════════════════════════════════════
251
+ // GROUP K — backend kind 诊断标识 + buffer 不进 trait(内化)
252
+ // ════════════════════════════════════════════════════════════════════════
253
+
254
+ #[test]
255
+ fn backend_kind_is_reported_per_backend() {
256
+ assert_eq!(tmux().kind(), BackendKind::Tmux);
257
+ assert_eq!(wezterm().kind(), BackendKind::WezTerm);
258
+ assert_eq!(conpty().kind(), BackendKind::ConPty);
259
+ }
260
+
261
+ #[test]
262
+ fn buffer_lifecycle_internalized_trait_exposes_only_inject() {
263
+ // contracts-rust-native test_buffer_lifecycle_internalized_parity:
264
+ // set/load/delete-buffer 是 tmux inject 的内部细节,trait 只暴露 inject;
265
+ // 外部行为(注入成功、无陈旧 buffer 误注入)跨后端等价。
266
+ // 这里以契约形式锁住:Transport trait 上**没有**任何 buffer 方法面 —— 唯一物理出口是 inject。
267
+ // (编译期:若 porter 误把 buffer 提进 trait,本 stub 不再实现 trait → 编译失败。)
268
+ let t = tmux();
269
+ let report = t
270
+ .inject(
271
+ &Target::Pane(PaneId::new("%7")),
272
+ &InjectPayload::Text("payload".into()),
273
+ Key::Enter,
274
+ true,
275
+ )
276
+ .expect("inject ok");
277
+ // 非空文本注入抵达 submit,token 缺失时 verification=no_token(tmux_io.py:140)。
278
+ assert_eq!(report.stage_reached, InjectStage::Submit);
279
+ assert_eq!(report.inject_verification, InjectVerification::NoToken);
280
+ // 「无陈旧 buffer 误注入」是行为锁:内部 argv 序列必须**以 delete-buffer 收尾**
281
+ // (set-buffer → paste-buffer -p → delete-buffer;tmux_io.py:119-120)。
282
+ let argv = tmux_inject_text_argv(&PaneId::new("%7"), "team-agent-buf", "payload", true);
283
+ assert_eq!(
284
+ argv.last(),
285
+ Some(&vec![
286
+ "tmux".to_string(),
287
+ "delete-buffer".to_string(),
288
+ "-b".to_string(),
289
+ "team-agent-buf".to_string(),
290
+ ]),
291
+ "inject must delete its buffer (no stale buffer mis-injection)"
292
+ );
293
+ }
294
+
295
+ // ════════════════════════════════════════════════════════════════════════
296
+ // GROUP L — [真机] gated:WezTerm cli list / 正向身份登记(无法在此完整断言)
297
+ // ════════════════════════════════════════════════════════════════════════
298
+
299
+ #[test]
300
+ #[ignore = "real-machine-gated (fixture_kind=real): needs wezterm cli list --format json; \
301
+ asserts pane_pid/current_command/leader_env forward-registration (GAP-2/GAP-3)"]
302
+ fn wezterm_list_targets_forward_registration_real_machine() {
303
+ // contracts-rust-native test_list_targets_enumerate_parity /
304
+ // test_worker_identity_forward_registration_parity:
305
+ // WezTerm 身份靠正向登记表投影(不反向读进程 env)。仅锁形状,实现期真机校验。
306
+ let w = wezterm();
307
+ let listed = w.list_targets().expect("list_targets ok");
308
+ for info in &listed {
309
+ // 正向登记:leader_env 是登记表投影,不是反向读 /proc。
310
+ // [真机] list json 未必给 pane_pid(GAP-2)/ current_command(GAP-3) → Option。
311
+ let _ = (&info.leader_env, &info.pane_pid, &info.current_command);
312
+ }
313
+ }
314
+
315
+ #[test]
316
+ #[ignore = "real-machine-gated (B1): kill_window/kill_session liveness transition needs a live mux"]
317
+ fn kill_window_then_target_dead_real_machine() {
318
+ // contracts-rust-native test_kill_window_stops_agent_parity:
319
+ // kill_window 后 liveness=Dead 且 list_targets 不再列出。实现期真机校验。
320
+ let t = tmux();
321
+ t.kill_window(&Target::Pane(PaneId::new("%7")))
322
+ .expect("kill_window ok");
323
+ assert_eq!(
324
+ t.liveness(&PaneId::new("%7")).expect("liveness ok"),
325
+ PaneLiveness::Dead
326
+ );
327
+ }