@team-agent/installer 0.2.10 → 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 -83
  203. package/src/team_agent/coordinator/lifecycle.py +0 -363
  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 -200
  226. package/src/team_agent/idle_takeover.py +0 -59
  227. package/src/team_agent/idle_takeover_wiring.py +0 -111
  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 -254
  255. package/src/team_agent/messaging/delivery.py +0 -473
  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 -457
  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 -86
  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 -1239
  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 -143
  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 -602
  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,801 @@
1
+ //! ProviderAdapter trait(per-provider 命令构造 + 能力面)+ registry facade + 占位实现。
2
+
3
+ use std::path::{Path, PathBuf};
4
+ use std::process::Command;
5
+
6
+ use super::helpers::{find_session_id, parse_jsonl_records, patterns};
7
+ use super::types::{
8
+ AuthHintStatus, CaptureVia, CapturedSession, Confidence, McpConfig, ProviderCaps,
9
+ ProviderError, RolloutPath, SessionId, StatusPatterns,
10
+ };
11
+ use super::{AuthMode, Provider};
12
+
13
+ // ===========================================================================
14
+ // TRAIT: ProviderAdapter (method SIGNATURES only — 无 body)
15
+ // ===========================================================================
16
+
17
+ /// 单一真相 per-provider 命令构造器 + 能力面(`provider_cli/adapter.py` `ProviderAdapter`
18
+ /// 基类的 Rust 等价)。claude/codex/gemini/fake 各一 impl;copilot/opencode 占位 impl
19
+ /// 调用即返 `ProviderError::CapabilityUnsupported`。
20
+ ///
21
+ /// fallible 边界(§10):任何触碰 fs / 子进程 / provider 日志的方法返 `Result`;
22
+ /// 纯查询(caps)返值。**MUST-NOT-13**:trait 实现绝不调 provider client / network SDK。
23
+ pub trait ProviderAdapter {
24
+ /// 此 adapter 对应的 provider(claude_code 归一后单变体)。
25
+ fn provider(&self) -> Provider;
26
+
27
+ /// 静态能力声明(doc §59)。
28
+ fn caps(&self) -> ProviderCaps;
29
+
30
+ /// CLI 是否已安装(doctor;`adapter.py` is_installed)。
31
+ fn is_installed(&self) -> bool;
32
+
33
+ /// CLI 版本字符串(doctor;`adapter.py` version)。I/O → Result。
34
+ fn version(&self) -> Result<String, ProviderError>;
35
+
36
+ /// auth 状态提示(doctor;`adapter.py:38` auth_hint)。
37
+ fn auth_hint(&self, auth_mode: AuthMode) -> AuthHintStatus;
38
+
39
+ /// 构造 launch 命令(含 MCP 注入 / 权限模式 / system prompt / model)。
40
+ /// 返回可 exec 的 argv(`adapter.py` build_command;`providers.py:54`)。
41
+ fn build_command(
42
+ &self,
43
+ auth_mode: AuthMode,
44
+ mcp_config: Option<&McpConfig>,
45
+ system_prompt: Option<&str>,
46
+ model: Option<&str>,
47
+ ) -> Result<Vec<String>, ProviderError>;
48
+
49
+ /// Same as [`ProviderAdapter::build_command`], with the agent tool list supplied
50
+ /// so provider-specific sandbox flags can be computed without guessing.
51
+ fn build_command_with_tools(
52
+ &self,
53
+ auth_mode: AuthMode,
54
+ mcp_config: Option<&McpConfig>,
55
+ system_prompt: Option<&str>,
56
+ model: Option<&str>,
57
+ tools: &[&str],
58
+ ) -> Result<Vec<String>, ProviderError>;
59
+
60
+ /// 启动后从 provider session 日志捕获 session_id + rollout_path
61
+ /// (`claude.py:73`/`codex.py:62`)。fs watch / mtime fallback / repair。
62
+ fn capture_session_id(
63
+ &self,
64
+ agent_id: &str,
65
+ spawn_cwd: &std::path::Path,
66
+ timeout_s: u64,
67
+ ) -> Result<Option<CapturedSession>, ProviderError>;
68
+
69
+ /// restart/reset 路径:从已存 transcript/rollout 回收 session_id
70
+ /// (`claude.py:115`)。`None` 合法(找不到)。
71
+ fn recover_session_id(
72
+ &self,
73
+ agent_id: &str,
74
+ spawn_cwd: &std::path::Path,
75
+ ) -> Result<Option<SessionId>, ProviderError>;
76
+
77
+ /// 给定 session 是否可 resume(`claude.py:143`)。bug-085:compatible_api
78
+ /// `session_id=None` → 不可 resume(`false`,不崩)。
79
+ fn session_is_resumable(
80
+ &self,
81
+ session_id: Option<&SessionId>,
82
+ auth_mode: AuthMode,
83
+ ) -> Result<bool, ProviderError>;
84
+
85
+ /// 构造 resume 命令(`providers.py:74`)。不可 resume → `Err(ResumeUnavailable)`。
86
+ fn build_resume_command(
87
+ &self,
88
+ session_id: Option<&SessionId>,
89
+ auth_mode: AuthMode,
90
+ mcp_config: Option<&McpConfig>,
91
+ ) -> Result<Vec<String>, ProviderError>;
92
+
93
+ fn build_resume_command_with_context(
94
+ &self,
95
+ session_id: Option<&SessionId>,
96
+ auth_mode: AuthMode,
97
+ mcp_config: Option<&McpConfig>,
98
+ system_prompt: Option<&str>,
99
+ model: Option<&str>,
100
+ tools: &[&str],
101
+ ) -> Result<Vec<String>, ProviderError>;
102
+
103
+ /// 构造 fork 命令(`providers.py:99`)。fork 需 caps.fork ∧ auth_mode!=compatible_api;
104
+ /// 不支持 → `Err`。
105
+ fn fork(
106
+ &self,
107
+ session_id: Option<&SessionId>,
108
+ auth_mode: AuthMode,
109
+ mcp_config: Option<&McpConfig>,
110
+ ) -> Result<Vec<String>, ProviderError>;
111
+
112
+ fn fork_with_context(
113
+ &self,
114
+ session_id: Option<&SessionId>,
115
+ auth_mode: AuthMode,
116
+ mcp_config: Option<&McpConfig>,
117
+ system_prompt: Option<&str>,
118
+ model: Option<&str>,
119
+ tools: &[&str],
120
+ ) -> Result<Vec<String>, ProviderError>;
121
+
122
+ /// 计算本 provider 该用的 MCP server 配置(`adapter.py` mcp_config;claude
123
+ /// compatible_api 走 `ensure_compatible_claude_mcp_config`)。
124
+ fn mcp_config(&self, auth_mode: AuthMode) -> Result<McpConfig, ProviderError>;
125
+
126
+ /// 安装 MCP server。gemini 写全局 `~/.gemini/settings.json` 并备份/还原
127
+ /// (`gemini.py:40-78`)——有副作用 I/O,失败可还原 → Result。
128
+ fn install_mcp(&self, config: &McpConfig) -> Result<(), ProviderError>;
129
+
130
+ /// idle/processing/trust 识别正则集(`claude.py`/`codex.py` status_patterns)。
131
+ /// pane→status 检测用。返编译失败可能 → Result(`re.error` 容错)。
132
+ fn status_patterns(&self) -> Result<StatusPatterns, ProviderError>;
133
+
134
+ /// 校验 model 名对本 provider 合法(`codex debug models` 等;doctor)。
135
+ fn validate_model(&self, model: &str) -> Result<bool, ProviderError>;
136
+
137
+ /// Provider-specific startup prompt handling. Non-Codex adapters return an empty list; Codex
138
+ /// delegates to the provider-layer recognizer.
139
+ fn handle_startup_prompts(
140
+ &self,
141
+ transport: &dyn crate::transport::Transport,
142
+ target: &crate::transport::Target,
143
+ checks: usize,
144
+ sleep_s: f64,
145
+ ) -> Vec<crate::provider::HandledPrompt> {
146
+ std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| match self.provider() {
147
+ Provider::Codex => {
148
+ super::startup_prompt::codex_handle_startup_prompts(transport, target, checks, sleep_s)
149
+ }
150
+ _ => Vec::new(),
151
+ }))
152
+ .unwrap_or_default()
153
+ }
154
+ }
155
+
156
+ // ===========================================================================
157
+ // FACADE 自由函数 (doc §71 providers.get_adapter — body unimplemented)
158
+ // ===========================================================================
159
+
160
+ /// `providers.get_adapter(provider)`(`providers.py:47`)——拿某 provider 的命令构造器。
161
+ /// `claude`/`claude_code` 指向同一 adapter(`Provider::ClaudeCode` 单变体已归一);
162
+ /// copilot/opencode 返占位 adapter(调用即 `CapabilityUnsupported`)。
163
+ pub fn get_adapter(p: Provider) -> Box<dyn ProviderAdapter> {
164
+ Box::new(BasicProviderAdapter { provider: p })
165
+ }
166
+
167
+ #[derive(Debug, Clone, Copy)]
168
+ struct BasicProviderAdapter {
169
+ provider: Provider,
170
+ }
171
+
172
+ impl ProviderAdapter for BasicProviderAdapter {
173
+ fn provider(&self) -> Provider {
174
+ self.provider
175
+ }
176
+
177
+ fn caps(&self) -> ProviderCaps {
178
+ match self.provider {
179
+ Provider::Claude | Provider::ClaudeCode => ProviderCaps {
180
+ resume: true,
181
+ fork: true,
182
+ native_mcp_config: true,
183
+ writes_global_settings: false,
184
+ },
185
+ Provider::Codex => ProviderCaps {
186
+ resume: true,
187
+ fork: true,
188
+ native_mcp_config: false,
189
+ writes_global_settings: false,
190
+ },
191
+ Provider::GeminiCli => ProviderCaps {
192
+ resume: false,
193
+ fork: false,
194
+ native_mcp_config: false,
195
+ writes_global_settings: true,
196
+ },
197
+ Provider::Fake => ProviderCaps {
198
+ resume: false,
199
+ fork: false,
200
+ native_mcp_config: false,
201
+ writes_global_settings: false,
202
+ },
203
+ }
204
+ }
205
+
206
+ fn is_installed(&self) -> bool {
207
+ if matches!(self.provider, Provider::Fake) {
208
+ return true;
209
+ }
210
+ command_on_path(command_name(self.provider))
211
+ }
212
+
213
+ fn version(&self) -> Result<String, ProviderError> {
214
+ if matches!(self.provider, Provider::Fake) {
215
+ return Ok("fake".to_string());
216
+ }
217
+ let output = Command::new(command_name(self.provider))
218
+ .arg("--version")
219
+ .output()
220
+ .map_err(|e| ProviderError::Io(format!("{} --version: {e}", command_name(self.provider))))?;
221
+ if !output.status.success() {
222
+ return Ok("unknown".to_string());
223
+ }
224
+ let text = String::from_utf8_lossy(&output.stdout).trim().to_string();
225
+ if text.is_empty() {
226
+ Ok("unknown".to_string())
227
+ } else {
228
+ Ok(text)
229
+ }
230
+ }
231
+
232
+ fn auth_hint(&self, auth_mode: AuthMode) -> AuthHintStatus {
233
+ match auth_mode {
234
+ AuthMode::Subscription => AuthHintStatus::Present,
235
+ AuthMode::OfficialApi | AuthMode::CompatibleApi => AuthHintStatus::MissingOrUnknown,
236
+ }
237
+ }
238
+
239
+ fn build_command(
240
+ &self,
241
+ auth_mode: AuthMode,
242
+ mcp_config: Option<&McpConfig>,
243
+ system_prompt: Option<&str>,
244
+ model: Option<&str>,
245
+ ) -> Result<Vec<String>, ProviderError> {
246
+ self.build_command_with_tools(auth_mode, mcp_config, system_prompt, model, &[])
247
+ }
248
+
249
+ fn build_command_with_tools(
250
+ &self,
251
+ auth_mode: AuthMode,
252
+ mcp_config: Option<&McpConfig>,
253
+ system_prompt: Option<&str>,
254
+ model: Option<&str>,
255
+ tools: &[&str],
256
+ ) -> Result<Vec<String>, ProviderError> {
257
+ match self.provider {
258
+ Provider::Claude | Provider::ClaudeCode => {
259
+ Ok(claude_launch_command(self, auth_mode, mcp_config, system_prompt, model)?)
260
+ }
261
+ Provider::Codex => Ok(codex_base_command(None, auth_mode, mcp_config, system_prompt, model, tools)),
262
+ Provider::GeminiCli => {
263
+ let mut argv = vec!["gemini".to_string()];
264
+ if let Some(model) = model {
265
+ argv.push("--model".to_string());
266
+ argv.push(model.to_string());
267
+ }
268
+ Ok(argv)
269
+ }
270
+ Provider::Fake => Ok(fake_worker_command()),
271
+ }
272
+ }
273
+
274
+ fn capture_session_id(
275
+ &self,
276
+ agent_id: &str,
277
+ spawn_cwd: &Path,
278
+ _timeout_s: u64,
279
+ ) -> Result<Option<CapturedSession>, ProviderError> {
280
+ let candidates = candidate_session_files(spawn_cwd, agent_id)?;
281
+ for path in candidates {
282
+ let Ok(text) = std::fs::read_to_string(&path) else {
283
+ continue;
284
+ };
285
+ let records = parse_jsonl_records(&text);
286
+ if records.is_empty() {
287
+ continue;
288
+ }
289
+ let session_id = records.iter().find_map(find_session_id);
290
+ if matches!(self.provider, Provider::Claude | Provider::ClaudeCode)
291
+ && session_id.is_some()
292
+ && !records.iter().any(has_cwd_field)
293
+ {
294
+ continue;
295
+ }
296
+ let captured_via = if session_id.is_some() {
297
+ CaptureVia::FsWatch
298
+ } else {
299
+ CaptureVia::FsMtimeFallback
300
+ };
301
+ let attribution_confidence = if session_id.is_some() {
302
+ Confidence::High
303
+ } else {
304
+ Confidence::Low
305
+ };
306
+ return Ok(Some(CapturedSession {
307
+ session_id: session_id.map(SessionId::new),
308
+ rollout_path: Some(RolloutPath::new(path)),
309
+ captured_via,
310
+ attribution_confidence,
311
+ spawn_cwd: spawn_cwd.to_path_buf(),
312
+ }));
313
+ }
314
+ Ok(None)
315
+ }
316
+
317
+ fn recover_session_id(
318
+ &self,
319
+ agent_id: &str,
320
+ spawn_cwd: &Path,
321
+ ) -> Result<Option<SessionId>, ProviderError> {
322
+ Ok(self
323
+ .capture_session_id(agent_id, spawn_cwd, 0)?
324
+ .and_then(|captured| captured.session_id))
325
+ }
326
+
327
+ fn session_is_resumable(
328
+ &self,
329
+ session_id: Option<&SessionId>,
330
+ auth_mode: AuthMode,
331
+ ) -> Result<bool, ProviderError> {
332
+ if session_id.is_none() || auth_mode == AuthMode::CompatibleApi {
333
+ return Ok(false);
334
+ }
335
+ Ok(self.caps().resume)
336
+ }
337
+
338
+ fn build_resume_command(
339
+ &self,
340
+ session_id: Option<&SessionId>,
341
+ auth_mode: AuthMode,
342
+ mcp_config: Option<&McpConfig>,
343
+ ) -> Result<Vec<String>, ProviderError> {
344
+ self.build_resume_command_with_context(session_id, auth_mode, mcp_config, None, None, &[])
345
+ }
346
+
347
+ fn build_resume_command_with_context(
348
+ &self,
349
+ session_id: Option<&SessionId>,
350
+ auth_mode: AuthMode,
351
+ mcp_config: Option<&McpConfig>,
352
+ system_prompt: Option<&str>,
353
+ model: Option<&str>,
354
+ tools: &[&str],
355
+ ) -> Result<Vec<String>, ProviderError> {
356
+ if !self.session_is_resumable(session_id, auth_mode)? {
357
+ return Err(ProviderError::ResumeUnavailable(format!(
358
+ "{} resume requires session_id",
359
+ provider_wire(self.provider)
360
+ )));
361
+ }
362
+ let Some(session_id) = session_id else {
363
+ return Err(ProviderError::ResumeUnavailable("resume requires session_id".to_string()));
364
+ };
365
+ match self.provider {
366
+ Provider::Codex => {
367
+ let mut argv = codex_base_command(
368
+ Some("resume"),
369
+ auth_mode,
370
+ mcp_config,
371
+ system_prompt,
372
+ model,
373
+ tools,
374
+ );
375
+ argv.push(session_id.as_str().to_string());
376
+ Ok(argv)
377
+ }
378
+ Provider::Claude | Provider::ClaudeCode => {
379
+ let mut argv = claude_base_command(self, auth_mode, mcp_config, system_prompt, model)?;
380
+ argv.push("--resume".to_string());
381
+ argv.push(session_id.as_str().to_string());
382
+ Ok(argv)
383
+ }
384
+ Provider::GeminiCli | Provider::Fake => Err(ProviderError::ResumeUnavailable(format!(
385
+ "{} resume requires session_id",
386
+ provider_wire(self.provider)
387
+ ))),
388
+ }
389
+ }
390
+
391
+ fn fork(
392
+ &self,
393
+ session_id: Option<&SessionId>,
394
+ auth_mode: AuthMode,
395
+ mcp_config: Option<&McpConfig>,
396
+ ) -> Result<Vec<String>, ProviderError> {
397
+ self.fork_with_context(session_id, auth_mode, mcp_config, None, None, &[])
398
+ }
399
+
400
+ fn fork_with_context(
401
+ &self,
402
+ session_id: Option<&SessionId>,
403
+ auth_mode: AuthMode,
404
+ mcp_config: Option<&McpConfig>,
405
+ system_prompt: Option<&str>,
406
+ model: Option<&str>,
407
+ tools: &[&str],
408
+ ) -> Result<Vec<String>, ProviderError> {
409
+ if !self.caps().fork || auth_mode == AuthMode::CompatibleApi {
410
+ return Err(ProviderError::CapabilityUnsupported(format!(
411
+ "{} does not support native session fork",
412
+ provider_wire(self.provider)
413
+ )));
414
+ }
415
+ let Some(session_id) = session_id else {
416
+ return Err(ProviderError::ResumeUnavailable("fork requires session_id".to_string()));
417
+ };
418
+ match self.provider {
419
+ Provider::Codex => {
420
+ let mut argv = codex_base_command(
421
+ Some("fork"),
422
+ auth_mode,
423
+ mcp_config,
424
+ system_prompt,
425
+ model,
426
+ tools,
427
+ );
428
+ argv.push(session_id.as_str().to_string());
429
+ Ok(argv)
430
+ }
431
+ Provider::Claude | Provider::ClaudeCode => {
432
+ let mut argv = claude_base_command(self, auth_mode, mcp_config, system_prompt, model)?;
433
+ argv.push("--session-id".to_string());
434
+ argv.push(next_session_token());
435
+ argv.push("--resume".to_string());
436
+ argv.push(session_id.as_str().to_string());
437
+ argv.push("--fork-session".to_string());
438
+ Ok(argv)
439
+ }
440
+ Provider::GeminiCli | Provider::Fake => Err(ProviderError::CapabilityUnsupported(format!(
441
+ "{} does not support native session fork",
442
+ provider_wire(self.provider)
443
+ ))),
444
+ }
445
+ }
446
+
447
+ fn mcp_config(&self, auth_mode: AuthMode) -> Result<McpConfig, ProviderError> {
448
+ let server = mcp_server_config(auth_mode);
449
+ Ok(McpConfig {
450
+ raw: serde_json::json!({
451
+ "team_orchestrator": server
452
+ }),
453
+ })
454
+ }
455
+
456
+ fn install_mcp(&self, config: &McpConfig) -> Result<(), ProviderError> {
457
+ if !matches!(self.provider, Provider::GeminiCli) {
458
+ return Ok(());
459
+ }
460
+ let Some(home) = std::env::var_os("HOME").map(PathBuf::from) else {
461
+ return Err(ProviderError::Io("HOME is not set".to_string()));
462
+ };
463
+ let dir = home.join(".gemini");
464
+ std::fs::create_dir_all(&dir)
465
+ .map_err(|e| ProviderError::Io(format!("{}: {e}", dir.display())))?;
466
+ let path = dir.join("settings.json");
467
+ let text = serde_json::to_string_pretty(&config.raw)
468
+ .map_err(|e| ProviderError::Command(format!("serialize mcp config: {e}")))?;
469
+ std::fs::write(&path, text)
470
+ .map_err(|e| ProviderError::Io(format!("{}: {e}", path.display())))?;
471
+ Ok(())
472
+ }
473
+
474
+ fn status_patterns(&self) -> Result<StatusPatterns, ProviderError> {
475
+ match self.provider {
476
+ Provider::Claude | Provider::ClaudeCode => patterns(r"[>❯]\s", r"[✶✢✽✻✳·].*…", r"Error|Traceback"),
477
+ Provider::Codex => patterns(r"(›|❯|codex>)", r"•.*esc to interrupt", r"Error|Traceback|panic"),
478
+ Provider::GeminiCli | Provider::Fake => patterns(r">", r"working|processing", r"Error|Traceback"),
479
+ }
480
+ }
481
+
482
+ fn validate_model(&self, _model: &str) -> Result<bool, ProviderError> {
483
+ Ok(true)
484
+ }
485
+ }
486
+
487
+ fn command_name(provider: Provider) -> &'static str {
488
+ match provider {
489
+ Provider::Claude | Provider::ClaudeCode => "claude",
490
+ Provider::Codex => "codex",
491
+ Provider::GeminiCli => "gemini",
492
+ Provider::Fake => "team-agent",
493
+ }
494
+ }
495
+
496
+ fn provider_wire(provider: Provider) -> &'static str {
497
+ match provider {
498
+ Provider::Claude => "claude",
499
+ Provider::ClaudeCode => "claude_code",
500
+ Provider::Codex => "codex",
501
+ Provider::GeminiCli => "gemini_cli",
502
+ Provider::Fake => "fake",
503
+ }
504
+ }
505
+
506
+ fn auth_mode_wire(auth_mode: AuthMode) -> &'static str {
507
+ match auth_mode {
508
+ AuthMode::Subscription => "subscription",
509
+ AuthMode::OfficialApi => "official_api",
510
+ AuthMode::CompatibleApi => "compatible_api",
511
+ }
512
+ }
513
+
514
+ fn command_on_path(name: &str) -> bool {
515
+ let Some(path) = std::env::var_os("PATH") else {
516
+ return false;
517
+ };
518
+ std::env::split_paths(&path).any(|dir| dir.join(name).is_file())
519
+ }
520
+
521
+ fn candidate_session_files(spawn_cwd: &Path, agent_id: &str) -> Result<Vec<PathBuf>, ProviderError> {
522
+ let mut out = Vec::new();
523
+ collect_candidate_files(spawn_cwd, agent_id, 0, &mut out)?;
524
+ out.sort_by(|a, b| a.to_string_lossy().cmp(&b.to_string_lossy()));
525
+ Ok(out)
526
+ }
527
+
528
+ fn collect_candidate_files(
529
+ dir: &Path,
530
+ agent_id: &str,
531
+ depth: usize,
532
+ out: &mut Vec<PathBuf>,
533
+ ) -> Result<(), ProviderError> {
534
+ if depth > 4 {
535
+ return Ok(());
536
+ }
537
+ let entries = match std::fs::read_dir(dir) {
538
+ Ok(entries) => entries,
539
+ Err(e) if depth == 0 => return Err(ProviderError::Io(format!("{}: {e}", dir.display()))),
540
+ Err(_) => return Ok(()),
541
+ };
542
+ for entry in entries {
543
+ let Ok(entry) = entry else {
544
+ continue;
545
+ };
546
+ let path = entry.path();
547
+ if path.is_dir() {
548
+ collect_candidate_files(&path, agent_id, depth.saturating_add(1), out)?;
549
+ } else if looks_like_session_file(&path, agent_id) {
550
+ out.push(path);
551
+ }
552
+ }
553
+ Ok(())
554
+ }
555
+
556
+ fn looks_like_session_file(path: &Path, agent_id: &str) -> bool {
557
+ // F5/N11/Contract D: Team Agent's own runtime/log JSONL must never be picked up
558
+ // as a provider transcript. `.team/logs/events.jsonl` and the rest of the
559
+ // `.team/runtime/` tree are framework files, not Codex/Claude rollout. Reject
560
+ // anything under `.team/` BEFORE the filename-shape match, so honest "no valid
561
+ // rollout" yields `rollout_path=None` instead of fake idle from our own logs.
562
+ if path_is_under_team_runtime(path) {
563
+ return false;
564
+ }
565
+ let Some(name) = path.file_name().and_then(|s| s.to_str()) else {
566
+ return false;
567
+ };
568
+ name.ends_with(".jsonl")
569
+ || name.ends_with(".json")
570
+ || name.contains("session")
571
+ || name.contains("rollout")
572
+ || (!agent_id.is_empty() && name.contains(agent_id))
573
+ }
574
+
575
+ /// `true` iff any path component is `.team` (the Team Agent runtime/logs root) — used
576
+ /// to gate session-file detection so `<workspace>/.team/logs/events.jsonl`,
577
+ /// `.team/runtime/team.db`, etc. are NEVER mistaken for a provider transcript.
578
+ fn path_is_under_team_runtime(path: &Path) -> bool {
579
+ path.components()
580
+ .any(|c| c.as_os_str() == std::ffi::OsStr::new(".team"))
581
+ }
582
+
583
+ fn fake_worker_command() -> Vec<String> {
584
+ let exe = std::env::current_exe()
585
+ .ok()
586
+ .and_then(|p| p.into_os_string().into_string().ok())
587
+ .unwrap_or_else(|| "team-agent".to_string());
588
+ vec![
589
+ exe,
590
+ "fake-worker".to_string(),
591
+ "--workspace".to_string(),
592
+ "{workspace}".to_string(),
593
+ "--agent-id".to_string(),
594
+ "{agent_id}".to_string(),
595
+ ]
596
+ }
597
+
598
+ fn claude_launch_command(
599
+ adapter: &BasicProviderAdapter,
600
+ auth_mode: AuthMode,
601
+ mcp_config: Option<&McpConfig>,
602
+ system_prompt: Option<&str>,
603
+ model: Option<&str>,
604
+ ) -> Result<Vec<String>, ProviderError> {
605
+ let mut argv = claude_base_command(adapter, auth_mode, mcp_config, system_prompt, model)?;
606
+ argv.push("--session-id".to_string());
607
+ argv.push(next_session_token());
608
+ Ok(argv)
609
+ }
610
+
611
+ fn claude_base_command(
612
+ adapter: &BasicProviderAdapter,
613
+ auth_mode: AuthMode,
614
+ mcp_config: Option<&McpConfig>,
615
+ system_prompt: Option<&str>,
616
+ model: Option<&str>,
617
+ ) -> Result<Vec<String>, ProviderError> {
618
+ let mut argv = vec![
619
+ "claude".to_string(),
620
+ "--permission-mode".to_string(),
621
+ "default".to_string(),
622
+ ];
623
+ if let Some(model) = model {
624
+ argv.push("--model".to_string());
625
+ argv.push(model.to_string());
626
+ }
627
+ if let Some(prompt) = system_prompt {
628
+ argv.push("--append-system-prompt".to_string());
629
+ argv.push(prompt.to_string());
630
+ }
631
+ if mcp_config.is_some() || auth_mode == AuthMode::CompatibleApi || system_prompt.is_some_and(prompt_needs_native_mcp) {
632
+ let raw = if let Some(config) = mcp_config {
633
+ serde_json::json!({"mcpServers": config.raw.clone()})
634
+ } else {
635
+ serde_json::json!({"mcpServers": adapter.mcp_config(auth_mode)?.raw})
636
+ };
637
+ argv.push("--mcp-config".to_string());
638
+ argv.push(raw.to_string());
639
+ argv.push("--strict-mcp-config".to_string());
640
+ for tool in ["Bash", "Grep"] {
641
+ argv.push("--disallowedTools".to_string());
642
+ argv.push(tool.to_string());
643
+ }
644
+ }
645
+ Ok(argv)
646
+ }
647
+
648
+ fn prompt_needs_native_mcp(prompt: &str) -> bool {
649
+ prompt.contains('\n') || prompt.contains('"')
650
+ }
651
+
652
+ fn codex_base_command(
653
+ subcommand: Option<&str>,
654
+ _auth_mode: AuthMode,
655
+ mcp_config: Option<&McpConfig>,
656
+ system_prompt: Option<&str>,
657
+ model: Option<&str>,
658
+ tools: &[&str],
659
+ ) -> Vec<String> {
660
+ let mut argv = vec![
661
+ "codex".to_string(),
662
+ ];
663
+ if let Some(subcommand) = subcommand {
664
+ argv.push(subcommand.to_string());
665
+ }
666
+ argv.extend([
667
+ "--no-alt-screen".to_string(),
668
+ "--disable".to_string(),
669
+ "shell_snapshot".to_string(),
670
+ "--disable".to_string(),
671
+ "apps".to_string(),
672
+ ]);
673
+ if codex_dangerous_auto_approve(tools) {
674
+ argv.push("--dangerously-bypass-approvals-and-sandbox".to_string());
675
+ } else {
676
+ argv.push("--sandbox".to_string());
677
+ argv.push(codex_sandbox_mode(tools).to_string());
678
+ argv.push("--ask-for-approval".to_string());
679
+ argv.push("on-request".to_string());
680
+ }
681
+ if let Some(model) = model {
682
+ argv.push("--model".to_string());
683
+ argv.push(model.to_string());
684
+ }
685
+ if let Some(prompt) = system_prompt {
686
+ argv.push("-c".to_string());
687
+ argv.push(format!("developer_instructions=\"{}\"", prompt.replace('"', "\\\"")));
688
+ }
689
+ // Contract C / MUST-8: Codex CLI (2026-06) does NOT take Claude's `--mcp-config <json>` flag;
690
+ // instead it uses `-c mcp_servers.<name>.<field>=...` overrides, the same pattern used by
691
+ // the live Team Agent workers in this very session (attach-leader codex panes spawn with
692
+ // `-c mcp_servers.team_orchestrator.command="..."`, ` ...args=[...]`, `...env.TEAM_AGENT_ID=...`).
693
+ // Inject the resolved MCP config that way so the Codex worker has a real callable
694
+ // `team_orchestrator` server (not prompt-only metadata).
695
+ if let Some(config) = mcp_config {
696
+ append_codex_mcp_overrides(&mut argv, &config.raw);
697
+ }
698
+ argv
699
+ }
700
+
701
+ /// Render an `McpConfig::raw` ({ name: { type, command, args, env: {...} } }) into Codex
702
+ /// `-c mcp_servers.<name>.<field>=...` overrides. JSON values are stringified with serde
703
+ /// so arrays/objects survive (Codex parses the right-hand side as JSON; this is what the
704
+ /// Python golden + the live attached Codex panes do).
705
+ fn append_codex_mcp_overrides(argv: &mut Vec<String>, raw: &serde_json::Value) {
706
+ let Some(servers) = raw.as_object() else {
707
+ return;
708
+ };
709
+ for (name, server) in servers {
710
+ let Some(obj) = server.as_object() else {
711
+ continue;
712
+ };
713
+ for (key, value) in obj {
714
+ if key == "env" {
715
+ if let Some(env) = value.as_object() {
716
+ for (env_key, env_value) in env {
717
+ argv.push("-c".to_string());
718
+ argv.push(format!(
719
+ "mcp_servers.{name}.env.{env_key}={}",
720
+ json_inline(env_value)
721
+ ));
722
+ }
723
+ }
724
+ continue;
725
+ }
726
+ argv.push("-c".to_string());
727
+ argv.push(format!("mcp_servers.{name}.{key}={}", json_inline(value)));
728
+ }
729
+ }
730
+ }
731
+
732
+ fn json_inline(value: &serde_json::Value) -> String {
733
+ match value {
734
+ serde_json::Value::String(s) => format!("\"{}\"", s.replace('"', "\\\"")),
735
+ other => other.to_string(),
736
+ }
737
+ }
738
+
739
+ fn codex_dangerous_auto_approve(tools: &[&str]) -> bool {
740
+ tools.contains(&"dangerous_auto_approve")
741
+ }
742
+
743
+ fn codex_sandbox_mode(tools: &[&str]) -> &'static str {
744
+ if tools.iter().any(|tool| matches!(*tool, "fs_write" | "execute_bash")) {
745
+ "workspace-write"
746
+ } else {
747
+ "read-only"
748
+ }
749
+ }
750
+
751
+ /// Contract C / MUST-8: the per-worker Team Agent MCP server config used by Claude
752
+ /// (`--mcp-config`) and Codex (`-c mcp_servers.*` injection). Placeholders
753
+ /// `{workspace}` / `{agent_id}` / `{team_id}` are substituted at spawn time by
754
+ /// [`crate::lifecycle::launch::fill_spawn_placeholders`]; this template MUST NOT
755
+ /// contain hardcoded paths or agent/team ids — the probe5 RED probe burned that.
756
+ ///
757
+ /// step3/rt finding (binary c5d22208): bare `command="team-agent"` lets the worker
758
+ /// process's PATH resolve to a stale Python CLI install (e.g. `~/.local/bin/team-agent`)
759
+ /// that lacks the `mcp-server` subcommand → handshake fails (`MCP startup failed:
760
+ /// connection closed: initialize response`). Pin to the absolute path of the CURRENT
761
+ /// running binary via `std::env::current_exe()` so the spawned MCP server is the same
762
+ /// build that produced this config.
763
+ fn mcp_server_config(auth_mode: AuthMode) -> serde_json::Value {
764
+ serde_json::json!({
765
+ "type": "stdio",
766
+ "command": current_team_agent_command(),
767
+ "args": ["mcp-server", "--workspace", "{workspace}"],
768
+ "env": {
769
+ "TEAM_AGENT_WORKSPACE": "{workspace}",
770
+ "TEAM_AGENT_ID": "{agent_id}",
771
+ "TEAM_AGENT_OWNER_TEAM_ID": "{team_id}",
772
+ "TEAM_AGENT_AUTH_MODE": auth_mode_wire(auth_mode),
773
+ }
774
+ })
775
+ }
776
+
777
+ /// Absolute path of the running `team-agent` binary, suitable for an MCP `command`
778
+ /// field that must not be PATH-resolved. Uses `std::env::current_exe()`, canonicalizes
779
+ /// to drop `/proc/self/exe`-style indirection where supported. Falls back to a
780
+ /// well-known absolute install path only if both lookups fail (CI sandboxes); the
781
+ /// contract requires `Path::is_absolute(command) == true`, which the fallback honors.
782
+ fn current_team_agent_command() -> String {
783
+ if let Ok(exe) = std::env::current_exe() {
784
+ if let Ok(canon) = std::fs::canonicalize(&exe) {
785
+ return canon.to_string_lossy().to_string();
786
+ }
787
+ return exe.to_string_lossy().to_string();
788
+ }
789
+ "/usr/local/bin/team-agent".to_string()
790
+ }
791
+
792
+ fn has_cwd_field(record: &serde_json::Value) -> bool {
793
+ record.get("cwd").and_then(serde_json::Value::as_str).is_some()
794
+ }
795
+
796
+ fn next_session_token() -> String {
797
+ let nanos = std::time::SystemTime::now()
798
+ .duration_since(std::time::UNIX_EPOCH)
799
+ .map_or(0, |d| d.as_nanos());
800
+ format!("session-{nanos:x}")
801
+ }