swarph-cli 0.72.1__tar.gz → 0.72.3__tar.gz

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 (472) hide show
  1. {swarph_cli-0.72.1/src/swarph_cli.egg-info → swarph_cli-0.72.3}/PKG-INFO +3 -1
  2. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/README.md +2 -0
  3. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/pyproject.toml +1 -1
  4. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/__init__.py +1 -1
  5. swarph_cli-0.72.3/src/swarph_cli/bench/codex_cli.py +127 -0
  6. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/bench.py +7 -0
  7. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/spawn.py +7 -0
  8. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/wake_hook_output.py +39 -5
  9. {swarph_cli-0.72.1 → swarph_cli-0.72.3/src/swarph_cli.egg-info}/PKG-INFO +3 -1
  10. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
  11. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_1013_spawn_channel.py +25 -0
  12. swarph_cli-0.72.3/tests/test_961_loud_stop.py +106 -0
  13. swarph_cli-0.72.3/tests/test_bench_codex_cli.py +138 -0
  14. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/LICENSE +0 -0
  15. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/setup.cfg +0 -0
  16. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/__main__.py +0 -0
  17. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/__init__.py +0 -0
  18. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/backends.py +0 -0
  19. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/claude_cli.py +0 -0
  20. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/pack.py +0 -0
  21. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/prices.py +0 -0
  22. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/providers.py +0 -0
  23. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/quality.py +0 -0
  24. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/refresh_prices.py +0 -0
  25. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/rules.py +0 -0
  26. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/runner.py +0 -0
  27. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/signal_score.py +0 -0
  28. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/bench/validate.py +0 -0
  29. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/caller.py +0 -0
  30. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/__init__.py +0 -0
  31. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/harden.py +0 -0
  32. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/lineage.py +0 -0
  33. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/liveness.py +0 -0
  34. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/manifest.py +0 -0
  35. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/paths.py +0 -0
  36. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/capture/verify.py +0 -0
  37. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/cell.py +0 -0
  38. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/chain_token.py +0 -0
  39. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/channel.py +0 -0
  40. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/__init__.py +0 -0
  41. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/_content.py +0 -0
  42. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/_display.py +0 -0
  43. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/_gateway_client.py +0 -0
  44. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/add.py +0 -0
  45. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/board.py +0 -0
  46. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/board_merge_check.py +0 -0
  47. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/brain.py +0 -0
  48. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/brain_ask.py +0 -0
  49. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/cell.py +0 -0
  50. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/cell_probe.py +0 -0
  51. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  52. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/channel.py +0 -0
  53. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/chat.py +0 -0
  54. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/codegraph.py +0 -0
  55. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  56. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  57. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/codex_waker.py +0 -0
  58. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/compress.py +0 -0
  59. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/daemon.py +0 -0
  60. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/dreaming.py +0 -0
  61. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/event.py +0 -0
  62. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/gateway.py +0 -0
  63. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/gh_route.py +0 -0
  64. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/group.py +0 -0
  65. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/guide.py +0 -0
  66. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/highlight.py +0 -0
  67. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  68. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/hook_output.py +0 -0
  69. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/hooks.py +0 -0
  70. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/import_session.py +0 -0
  71. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/init.py +0 -0
  72. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  73. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_hook.py +0 -0
  74. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  75. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  76. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  77. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  78. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/lane.py +0 -0
  79. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/mcp_server.py +0 -0
  80. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/memory.py +0 -0
  81. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  82. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/memory_sync.py +0 -0
  83. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/mesh.py +0 -0
  84. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/monitor.py +0 -0
  85. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/okf_links.py +0 -0
  86. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/onboard.py +0 -0
  87. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  88. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  89. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/protocol_handler.py +0 -0
  90. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/ratify.py +0 -0
  91. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/schedule.py +0 -0
  92. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/security.py +0 -0
  93. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/service.py +0 -0
  94. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/timeline.py +0 -0
  95. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/version.py +0 -0
  96. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/commands/watchdog.py +0 -0
  97. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/compress/__init__.py +0 -0
  98. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/compress/levers.py +0 -0
  99. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/compress/marker.py +0 -0
  100. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/compress/verify.py +0 -0
  101. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/console_safe.py +0 -0
  102. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/delivery_queue.py +0 -0
  103. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dm_frame.py +0 -0
  104. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/__init__.py +0 -0
  105. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/candidates.py +0 -0
  106. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/clone.py +0 -0
  107. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/enrich.py +0 -0
  108. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/index_check.py +0 -0
  109. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/notify.py +0 -0
  110. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/organize.py +0 -0
  111. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/probes.py +0 -0
  112. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/report.py +0 -0
  113. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/run.py +0 -0
  114. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/slm.py +0 -0
  115. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/transcripts.py +0 -0
  116. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/dreaming/verify.py +0 -0
  117. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/__init__.py +0 -0
  118. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/feature_registry.py +0 -0
  119. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/lanes_control.py +0 -0
  120. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/schema.sql +0 -0
  121. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/server.py +0 -0
  122. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway/services_control.py +0 -0
  123. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gateway_default.py +0 -0
  124. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/gh_identity.py +0 -0
  125. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/guide/GUIDE.md +0 -0
  126. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/identity.py +0 -0
  127. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/main.py +0 -0
  128. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/multiplexer.py +0 -0
  129. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/__init__.py +0 -0
  130. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/__main__.py +0 -0
  131. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/api.py +0 -0
  132. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/cli.py +0 -0
  133. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/config.py +0 -0
  134. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/operator/fmt.py +0 -0
  135. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/orphan_daemons.py +0 -0
  136. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/pane_probe.py +0 -0
  137. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/parsers/__init__.py +0 -0
  138. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/parsers/claude.py +0 -0
  139. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  140. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  141. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  142. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  143. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  144. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  145. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  146. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  147. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  148. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  149. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  150. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  151. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_executor.py +0 -0
  152. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_executor_windows.py +0 -0
  153. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_reconciliation.py +0 -0
  154. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_reply_outbox.py +0 -0
  155. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_service_host.py +0 -0
  156. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_service_payload.py +0 -0
  157. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_service_reply.py +0 -0
  158. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/peer_staging.py +0 -0
  159. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/query_filters.py +0 -0
  160. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/__init__.py +0 -0
  161. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  162. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  163. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  164. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  165. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  166. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/service/__init__.py +0 -0
  167. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/service/app.py +0 -0
  168. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/service/providers.py +0 -0
  169. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/session_bridge.py +0 -0
  170. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  171. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  172. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/source_text.py +0 -0
  173. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/stall_alert.py +0 -0
  174. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/codex-waker.default +0 -0
  175. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  176. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  177. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  178. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  179. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  180. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  181. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  182. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  183. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  184. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  185. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  186. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  187. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  188. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/timeline_paths.py +0 -0
  189. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli/tokens.py +0 -0
  190. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  191. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  192. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/requires.txt +0 -0
  193. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/src/swarph_cli.egg-info/top_level.txt +0 -0
  194. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_1006_board_publisher.py +0 -0
  195. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_1006_session_board.py +0 -0
  196. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_1017_muse_tui.py +0 -0
  197. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_124_register_merge.py +0 -0
  198. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_126_pending_from_complete.py +0 -0
  199. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  200. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_138_memory_navigate.py +0 -0
  201. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_139_tool_probe.py +0 -0
  202. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_183_card_anchor.py +0 -0
  203. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_184_notify_honesty.py +0 -0
  204. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_201_win32_bash_refusal.py +0 -0
  205. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_243_onboard_per_peer_token.py +0 -0
  206. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_307_task4_ask_reply.py +0 -0
  207. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  208. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_319_refusal_exit_codes.py +0 -0
  209. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_323_mistral_backend.py +0 -0
  210. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_332_report_organize_findings.py +0 -0
  211. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_356_query_filters.py +0 -0
  212. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_397_gh_identity_router.py +0 -0
  213. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_397_router_preflight.py +0 -0
  214. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_402_one_identity_resolver.py +0 -0
  215. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_467b_onboard_operator_credential.py +0 -0
  216. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_472_register_caller_binding.py +0 -0
  217. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_484_codex_queue_sink.py +0 -0
  218. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_523_guide.py +0 -0
  219. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_524_conftest_hermeticity.py +0 -0
  220. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_532_accept_flag.py +0 -0
  221. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_535_version_capability.py +0 -0
  222. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_538_signal_bench.py +0 -0
  223. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_546_default_gateway.py +0 -0
  224. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_547_verbs_from_registry.py +0 -0
  225. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_548_gateway_remainder.py +0 -0
  226. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_552_codegraph_gateway.py +0 -0
  227. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_555_relay_row_shape.py +0 -0
  228. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_556_relay_freshness.py +0 -0
  229. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_559_edges_truncated.py +0 -0
  230. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_564_bootstrap_first_peer.py +0 -0
  231. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  232. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_579_register_bootstrap_message.py +0 -0
  233. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_591_cli_step_graph.py +0 -0
  234. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_644_monitor_supervision.py +0 -0
  235. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_648_install_unit.py +0 -0
  236. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_654_reexec.py +0 -0
  237. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_656_owned_slm.py +0 -0
  238. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_684_dreaming_verb.py +0 -0
  239. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_689_pkg_version_order.py +0 -0
  240. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_715_grok_wake.py +0 -0
  241. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_716_timeline_one_path.py +0 -0
  242. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_723_grok_0251.py +0 -0
  243. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  244. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_725_stdio_reconfigure.py +0 -0
  245. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_726_grok_limit_screen.py +0 -0
  246. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_729_arm_check_delivery_gap.py +0 -0
  247. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_729_dm_once.py +0 -0
  248. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_729_wake_watchdog.py +0 -0
  249. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  250. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_735_dm_mcp_tools.py +0 -0
  251. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_739_wake_writer.py +0 -0
  252. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_740_edit_project.py +0 -0
  253. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_762_cards_edit_priority.py +0 -0
  254. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_764_enrich_collapse.py +0 -0
  255. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_801_confirm_flow_cli.py +0 -0
  256. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_802_stage_history.py +0 -0
  257. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_825_codegraph_hook_noise.py +0 -0
  258. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_825_install_posttooluse_only.py +0 -0
  259. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  260. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_829_counterfactual_column.py +0 -0
  261. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_830_hook_binding_version.py +0 -0
  262. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_84_cli_labels.py +0 -0
  263. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_866_doctrine_guide.py +0 -0
  264. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_872_identity_refuses_not_warns.py +0 -0
  265. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_874_source_text.py +0 -0
  266. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  267. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_937_dreaming_notify.py +0 -0
  268. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_938_organize_line_limit.py +0 -0
  269. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_941_bench_backends.py +0 -0
  270. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_941_harness_342.py +0 -0
  271. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_945_same_line_pairing.py +0 -0
  272. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_955_prompt_audit.py +0 -0
  273. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_957_auth_http.py +0 -0
  274. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_959_venv_interpreter.py +0 -0
  275. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_960_channel.py +0 -0
  276. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
  277. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_961_cursor_grok_footer.py +0 -0
  278. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_963_plugin_manifest.py +0 -0
  279. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_964_channel_cell.py +0 -0
  280. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_965_mesh_wait.py +0 -0
  281. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_975_channel_hop.py +0 -0
  282. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_add_security_gate.py +0 -0
  283. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_add.py +0 -0
  284. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_hash.py +0 -0
  285. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_lib.py +0 -0
  286. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_mcp.py +0 -0
  287. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_skill.py +0 -0
  288. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_tool.py +0 -0
  289. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_artifact_uri.py +0 -0
  290. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_backends.py +0 -0
  291. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_claude_cli.py +0 -0
  292. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_command.py +0 -0
  293. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_pack.py +0 -0
  294. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_prices.py +0 -0
  295. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_quality.py +0 -0
  296. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_runner.py +0 -0
  297. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_bench_validate.py +0 -0
  298. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_board_card_thread.py +0 -0
  299. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_board_command.py +0 -0
  300. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_board_merge_check.py +0 -0
  301. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_board_obligation_close.py +0 -0
  302. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_brain_ask_command.py +0 -0
  303. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_brain_ask_env_diagnosis.py +0 -0
  304. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_brain_ask_identity_explicitness.py +0 -0
  305. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_brain_command.py +0 -0
  306. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_caller_meta_guard.py +0 -0
  307. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_lineage.py +0 -0
  308. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_liveness.py +0 -0
  309. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_manifest.py +0 -0
  310. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_paths.py +0 -0
  311. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_capture_security.py +0 -0
  312. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_command_dispatch.py +0 -0
  313. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_harden.py +0 -0
  314. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_loader.py +0 -0
  315. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_selfcheck.py +0 -0
  316. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cell_verify.py +0 -0
  317. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_chain_token.py +0 -0
  318. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_channel_command.py +0 -0
  319. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  320. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_channel_post.py +0 -0
  321. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_channel_wake_policy_filter.py +0 -0
  322. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_chat_command.py +0 -0
  323. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_claude_parser.py +0 -0
  324. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_claude_tmux_template.py +0 -0
  325. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codegraph_caller_scope.py +0 -0
  326. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codegraph_command.py +0 -0
  327. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codegraph_hook.py +0 -0
  328. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codegraph_mcp.py +0 -0
  329. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_codex_waker.py +0 -0
  330. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_caller_convention.py +0 -0
  331. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_command.py +0 -0
  332. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_levers.py +0 -0
  333. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_marker.py +0 -0
  334. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_compress_verify.py +0 -0
  335. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_console_encoding_e2e.py +0 -0
  336. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_content_composition.py +0 -0
  337. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_cursor_membrane.py +0 -0
  338. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_daemon_command.py +0 -0
  339. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_daemon_console_encoding.py +0 -0
  340. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_daemon_delivery.py +0 -0
  341. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_delivery_queue.py +0 -0
  342. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_display_sanitize.py +0 -0
  343. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  344. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_event_emit.py +0 -0
  345. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_feature_to_uri.py +0 -0
  346. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gateway_brain.py +0 -0
  347. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gateway_client.py +0 -0
  348. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gateway_command.py +0 -0
  349. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gateway_message_idempotency.py +0 -0
  350. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  351. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_group_command.py +0 -0
  352. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_guide_commands_parse.py +0 -0
  353. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_guide_selfcheck.py +0 -0
  354. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_heartbeat_check.py +0 -0
  355. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_highlight_command.py +0 -0
  356. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hook_output.py +0 -0
  357. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_activity_marker.py +0 -0
  358. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_add.py +0 -0
  359. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_bundle.py +0 -0
  360. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_init.py +0 -0
  361. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_legacy_generation_removal.py +0 -0
  362. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_lifecycle.py +0 -0
  363. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_merge.py +0 -0
  364. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_verify.py +0 -0
  365. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  366. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_hooks_win32_command_path.py +0 -0
  367. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_import_command.py +0 -0
  368. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_inbox_identity_provenance.py +0 -0
  369. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_init_command.py +0 -0
  370. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_init_provider_menu.py +0 -0
  371. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_codex_hooks.py +0 -0
  372. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_hook.py +0 -0
  373. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_multiplexer_command.py +0 -0
  374. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_postcompact_hook.py +0 -0
  375. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_install_wake_hook.py +0 -0
  376. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_lane_command.py +0 -0
  377. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_main.py +0 -0
  378. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_main_unknown_verb_guard.py +0 -0
  379. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mcp_missing_hint.py +0 -0
  380. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mcp_server.py +0 -0
  381. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_command.py +0 -0
  382. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_emit_snippet.py +0 -0
  383. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_gateway.py +0 -0
  384. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_sync.py +0 -0
  385. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_memory_sync_membrane.py +0 -0
  386. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_command.py +0 -0
  387. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_inbox_mark_read.py +0 -0
  388. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_inbox_sanitize.py +0 -0
  389. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_originate_thread_623.py +0 -0
  390. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_send_unknown_peer.py +0 -0
  391. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_sidecar.py +0 -0
  392. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  393. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_sidecar_muse_sink.py +0 -0
  394. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  395. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_meta_edge_identity.py +0 -0
  396. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_channel_poll.py +0 -0
  397. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_cli.py +0 -0
  398. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_daemon_collision.py +0 -0
  399. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_self_name_verification.py +0 -0
  400. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_monitor_state_split.py +0 -0
  401. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_multiplexer.py +0 -0
  402. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_muse_membrane.py +0 -0
  403. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_muse_own_cli.py +0 -0
  404. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_obligation_close_is_surfaced.py +0 -0
  405. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_okf_links.py +0 -0
  406. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_onboard_command.py +0 -0
  407. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_opencode_membrane.py +0 -0
  408. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_opencode_plugin.py +0 -0
  409. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_operator_me.py +0 -0
  410. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_orphan_daemons.py +0 -0
  411. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_packaged_artifacts_exist.py +0 -0
  412. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_packaged_skills.py +0 -0
  413. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_executor.py +0 -0
  414. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_executor_windows.py +0 -0
  415. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_reply_drain_command.py +0 -0
  416. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_reply_drain_supervisor.py +0 -0
  417. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_reply_outbox.py +0 -0
  418. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_reply_transport.py +0 -0
  419. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_service_e2e.py +0 -0
  420. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_service_host.py +0 -0
  421. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_service_payload.py +0 -0
  422. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_peer_service_reply.py +0 -0
  423. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_postcompact_recall.py +0 -0
  424. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_process_liveness.py +0 -0
  425. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_protocol_handler.py +0 -0
  426. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_providers_isolation.py +0 -0
  427. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_ratify_command.py +0 -0
  428. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_readme_claims_no_gateway_default.py +0 -0
  429. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_repl_caller_convention.py +0 -0
  430. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_schedule_command.py +0 -0
  431. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_schedule_create_contract.py +0 -0
  432. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_security.py +0 -0
  433. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_service_app.py +0 -0
  434. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_service_command.py +0 -0
  435. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_service_providers.py +0 -0
  436. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_session_bridge_inject.py +0 -0
  437. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_session_bridge_probe.py +0 -0
  438. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_session_bridge_resolve.py +0 -0
  439. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_session_path_role_gate.py +0 -0
  440. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_shipped_units_match_the_cli.py +0 -0
  441. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_smoke_chat.py +0 -0
  442. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_smoke_one_shot.py +0 -0
  443. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_smoke_phase_5_5.py +0 -0
  444. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_base_hoist.py +0 -0
  445. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_command.py +0 -0
  446. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_identity_stamp.py +0 -0
  447. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_live_pin.py +0 -0
  448. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_mitosis_lineage.py +0 -0
  449. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_tmux_session.py +0 -0
  450. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_spawn_windows_relaunch.py +0 -0
  451. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_stall_alert.py +0 -0
  452. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_subprocess_decode_encoding.py +0 -0
  453. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_timeline_command.py +0 -0
  454. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_tmux_wake_runstate_defer.py +0 -0
  455. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_tmux_wake_session_reanchor.py +0 -0
  456. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_tmux_wake_stale.py +0 -0
  457. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_tmux_wake_submit_verify.py +0 -0
  458. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_token_file_one_parser.py +0 -0
  459. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_token_precedence_explicit_first.py +0 -0
  460. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_unified_token_resolver.py +0 -0
  461. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_version_command.py +0 -0
  462. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_version_consistency.py +0 -0
  463. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_vibe_membrane.py +0 -0
  464. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog.py +0 -0
  465. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_dm_wake.py +0 -0
  466. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  467. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  468. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_emit_health.py +0 -0
  469. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_install_flags.py +0 -0
  470. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_model_rung.py +0 -0
  471. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_watchdog_stale_peers.py +0 -0
  472. {swarph_cli-0.72.1 → swarph_cli-0.72.3}/tests/test_windows_waker_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.72.1
3
+ Version: 0.72.3
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -248,6 +248,8 @@ Task types: `numeric` (rel-err), `categorical` (exact), `ranking` (Kendall-tau),
248
248
 
249
249
  `claude-sonnet-5-5:subscription` runs `claude -p` on the Claude subscription when `claude` is on PATH. The argv is fixed: `--max-turns 1`, `--output-format stream-json`, `--strict-mcp-config`, an empty MCP config, `--tools ""`, `--setting-sources ""`. The prompt goes on stdin. The child environment drops `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and every `SWARPH_CHANNEL*` name. A result whose `apiKeySource` is not `none` is `billing:<source>`, is not scored, and aborts the rest of the run on every arm. A usage limit is retried after 60s and then 300s; the arm then stops with `--ledger`, and the next run skips tasks that already completed. It is not scored as an answer.
250
250
 
251
+ `gpt-5.6-sol:codex` runs `codex exec` on the ChatGPT subscription when `codex` is on PATH. The argv is fixed: `--json`, `--ephemeral`, `--skip-git-repo-check`, `--ignore-user-config`, `--ignore-rules`, `--sandbox read-only`, in an empty temporary directory. `codex exec` has no system-prompt flag, so the pack's `system` goes on stdin ahead of the prompt. The child environment drops `OPENAI_API_KEY`, `CODEX_API_KEY`, and every `SWARPH_CHANNEL*` name. The billing guard runs **before** the call: the codex stream reports no key source, so the arm reads `auth_mode` from `$CODEX_HOME/auth.json` and refuses with `billing:<mode>` unless it is `chatgpt` (`billing:unknown` when the file cannot be read). Usage limits follow the claude arm: retried, then resumable with `--ledger`, never scored. The isolation flags drop the operator's MCP servers and plugins only. Measured on codex-cli 0.157.0 with a one-line prompt: 15,472 input tokens by default, 14,410 with the flags. The rest is the CLI's own agent instructions, so this arm's input tokens are not comparable with the claude arm's.
252
+
251
253
  ### `swarph brain-ask` (v0.14.0)
252
254
 
253
255
  Search the **swarph-brain** (gbrain) — the swarm's sovereign $0 semantic-retrieval memory — over MCP. The "does the swarm already know X?" reflex, as a one-shot.
@@ -199,6 +199,8 @@ Task types: `numeric` (rel-err), `categorical` (exact), `ranking` (Kendall-tau),
199
199
 
200
200
  `claude-sonnet-5-5:subscription` runs `claude -p` on the Claude subscription when `claude` is on PATH. The argv is fixed: `--max-turns 1`, `--output-format stream-json`, `--strict-mcp-config`, an empty MCP config, `--tools ""`, `--setting-sources ""`. The prompt goes on stdin. The child environment drops `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and every `SWARPH_CHANNEL*` name. A result whose `apiKeySource` is not `none` is `billing:<source>`, is not scored, and aborts the rest of the run on every arm. A usage limit is retried after 60s and then 300s; the arm then stops with `--ledger`, and the next run skips tasks that already completed. It is not scored as an answer.
201
201
 
202
+ `gpt-5.6-sol:codex` runs `codex exec` on the ChatGPT subscription when `codex` is on PATH. The argv is fixed: `--json`, `--ephemeral`, `--skip-git-repo-check`, `--ignore-user-config`, `--ignore-rules`, `--sandbox read-only`, in an empty temporary directory. `codex exec` has no system-prompt flag, so the pack's `system` goes on stdin ahead of the prompt. The child environment drops `OPENAI_API_KEY`, `CODEX_API_KEY`, and every `SWARPH_CHANNEL*` name. The billing guard runs **before** the call: the codex stream reports no key source, so the arm reads `auth_mode` from `$CODEX_HOME/auth.json` and refuses with `billing:<mode>` unless it is `chatgpt` (`billing:unknown` when the file cannot be read). Usage limits follow the claude arm: retried, then resumable with `--ledger`, never scored. The isolation flags drop the operator's MCP servers and plugins only. Measured on codex-cli 0.157.0 with a one-line prompt: 15,472 input tokens by default, 14,410 with the flags. The rest is the CLI's own agent instructions, so this arm's input tokens are not comparable with the claude arm's.
203
+
202
204
  ### `swarph brain-ask` (v0.14.0)
203
205
 
204
206
  Search the **swarph-brain** (gbrain) — the swarm's sovereign $0 semantic-retrieval memory — over MCP. The "does the swarm already know X?" reflex, as a one-shot.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.72.1"
7
+ version = "0.72.3"
8
8
  description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.72.1"
19
+ __version__ = "0.72.3"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -0,0 +1,127 @@
1
+ """codex exec transport for the bench `codex` backend. Subscription-only, isolated,
2
+ billing-guarded. Sibling of :mod:`swarph_cli.bench.claude_cli`, and it differs from it
3
+ in three places that matter.
4
+
5
+ Measured 2026-10-02, codex-cli 0.157.0, macOS arm64, a one-line prompt:
6
+ default config 15,472 input tokens
7
+ --ignore-user-config --ignore-rules 14,410 input tokens
8
+ The isolation flags remove the operator's MCP servers and plugins, and that is all
9
+ they remove. The floor is the CLI's own agent instructions: there is no flag here that
10
+ turns `codex exec` into a bare completion, so this arm does not reach the ~421 tokens
11
+ `claude -p --tools ""` does. Compare arms on quality and on OUTPUT tokens; the input
12
+ count carries this fixed load.
13
+
14
+ THE BILLING GUARD IS CHECKED BEFORE THE CALL, NOT READ FROM THE RESULT. The claude
15
+ stream names its key source (`apiKeySource`). The codex stream names nothing about
16
+ billing, so the guard reads `auth_mode` from `$CODEX_HOME/auth.json` and refuses to
17
+ call unless it is `chatgpt`. A file that cannot be read is `billing:unknown`: not
18
+ knowing is a refusal, never a pass.
19
+
20
+ NO SYSTEM-PROMPT FLAG. `codex exec` has none, so the pack's `system` is sent on stdin
21
+ ahead of the prompt, separated by a blank line.
22
+
23
+ The child runs in an empty temporary directory under a read-only sandbox, so there is
24
+ no repository for the agent to read and nothing it can write.
25
+ """
26
+ from __future__ import annotations
27
+
28
+ import json
29
+ import os
30
+ import subprocess
31
+ import tempfile
32
+ from pathlib import Path
33
+ from typing import Optional
34
+
35
+ from swarph_cli.bench.claude_cli import CliResult
36
+
37
+ _STRIP_EXACT = ("OPENAI_API_KEY", "CODEX_API_KEY")
38
+ _RATE = ("usage limit", "rate limit", "429", "too many requests")
39
+ _SUBSCRIPTION_MODE = "chatgpt"
40
+
41
+
42
+ def build_argv(model_id: str) -> list:
43
+ """Fixed argv. The trailing `-` reads the prompt from stdin, never from argv."""
44
+ return ["codex", "exec", "--model", model_id, "--json", "--ephemeral",
45
+ "--skip-git-repo-check", "--ignore-user-config", "--ignore-rules",
46
+ "--sandbox", "read-only", "-"]
47
+
48
+
49
+ def clean_env(env: dict) -> dict:
50
+ """Drop billing keys and every SWARPH_CHANNEL* name, including ones not listed yet."""
51
+ return {
52
+ k: v for k, v in env.items()
53
+ if k not in _STRIP_EXACT and not k.startswith("SWARPH_CHANNEL")
54
+ }
55
+
56
+
57
+ def codex_home(env: dict) -> Path:
58
+ return Path(env.get("CODEX_HOME") or (Path.home() / ".codex"))
59
+
60
+
61
+ def auth_mode(home: Path) -> Optional[str]:
62
+ """`auth_mode` from auth.json, or None when it cannot be read. None is not a pass."""
63
+ try:
64
+ data = json.loads((home / "auth.json").read_text(encoding="utf-8"))
65
+ except (OSError, ValueError):
66
+ return None
67
+ mode = data.get("auth_mode") if isinstance(data, dict) else None
68
+ return mode if isinstance(mode, str) and mode else None
69
+
70
+
71
+ def stdin_text(prompt: str, system: str) -> str:
72
+ return f"{system}\n\n{prompt}" if system else prompt
73
+
74
+
75
+ def _is_rate(text: str) -> bool:
76
+ low = (text or "").lower()
77
+ return any(t in low for t in _RATE)
78
+
79
+
80
+ def parse_stream(lines, stderr: str = "", key: Optional[str] = _SUBSCRIPTION_MODE) -> CliResult:
81
+ """JSONL from `codex exec --json` -> CliResult.
82
+
83
+ The answer is the LAST agent_message. Usage is the LAST turn.completed: that event
84
+ carries the thread's running total, so adding two of them would double-count.
85
+ A usage limit is `rate_limit` whether it arrives as an event or on stderr — the
86
+ runner retries that string and never scores it as an answer.
87
+ """
88
+ text, usage, failure = None, None, None
89
+ for ln in lines:
90
+ try:
91
+ e = json.loads(ln)
92
+ except Exception:
93
+ continue
94
+ if not isinstance(e, dict):
95
+ continue
96
+ kind = e.get("type")
97
+ if kind == "item.completed":
98
+ item = e.get("item") or {}
99
+ if item.get("type") == "agent_message" and isinstance(item.get("text"), str):
100
+ text = item["text"]
101
+ elif kind == "turn.completed":
102
+ usage = e.get("usage") or {}
103
+ elif kind in ("error", "turn.failed"):
104
+ detail = e.get("message") or e.get("error") or ""
105
+ failure = (kind, detail if isinstance(detail, str) else json.dumps(detail))
106
+ u = usage or {}
107
+ tin, tout = int(u.get("input_tokens") or 0), int(u.get("output_tokens") or 0)
108
+ if failure is not None:
109
+ err = "rate_limit" if _is_rate(failure[1]) or _is_rate(stderr) else f"cli_error:{failure[0]}"
110
+ return CliResult("", tin, tout, key, None, error=err)
111
+ if text is None or usage is None:
112
+ err = "rate_limit" if _is_rate(stderr) else "no_result"
113
+ return CliResult("", tin, tout, key, None, error=err)
114
+ return CliResult(text, tin, tout, key, None)
115
+
116
+
117
+ def call(model_id: str, prompt: str, system: str, *, runner=subprocess.run,
118
+ timeout: int = 300, env: Optional[dict] = None) -> CliResult:
119
+ source = dict(os.environ) if env is None else dict(env)
120
+ mode = auth_mode(codex_home(source))
121
+ if mode != _SUBSCRIPTION_MODE:
122
+ # Refused before any call: nothing was spent, and nothing is scored.
123
+ return CliResult("", 0, 0, mode, None, error=f"billing:{mode or 'unknown'}")
124
+ with tempfile.TemporaryDirectory(prefix="swarph-bench-codex-") as cwd:
125
+ p = runner(build_argv(model_id), input=stdin_text(prompt, system), capture_output=True,
126
+ text=True, env=clean_env(source), timeout=timeout, cwd=cwd)
127
+ return parse_stream((p.stdout or "").splitlines(), p.stderr or "", key=mode)
@@ -61,10 +61,17 @@ def _default_backends() -> dict[str, Backend]:
61
61
  if shutil.which("claude"):
62
62
  from swarph_cli.bench.claude_cli import call as claude_call
63
63
  subscription = SubscriptionBackend(call_fn=claude_call)
64
+ # codex on PATH wires its own lane to an isolated `codex exec`. Its own key,
65
+ # like mistral: `id:subscription` keeps meaning claude.
66
+ codex = SubscriptionBackend()
67
+ if shutil.which("codex"):
68
+ from swarph_cli.bench.codex_cli import call as codex_call
69
+ codex = SubscriptionBackend(call_fn=codex_call)
64
70
  return {
65
71
  "metered": MeteredGeminiBackend(),
66
72
  "mistral": MeteredMistralBackend(),
67
73
  "subscription": subscription,
74
+ "codex": codex,
68
75
  "rule": RuleBackend(),
69
76
  "signal": SignalBackend(),
70
77
  "typed-http": TypedHttpBackend(),
@@ -581,9 +581,16 @@ def _spawn_env_base(cell: Cell) -> dict[str, str]:
581
581
  # NOTE: the marketplace/validity gate lives in _validate_channel, which
582
582
  # _build_claude_argv (and the muse env builder) run. This base stays a
583
583
  # pure reflection of the resolved mode so every provider's env agrees.
584
+ # #1022: SWARPH_CHANNEL must be exported too, whatever the mode's source.
585
+ # channel_opted_in() (channel.py) gates on SWARPH_CHANNEL alone, so a
586
+ # mode that came from cell.yaml — not from the parent env — left the
587
+ # session with SWARPH_CHANNEL_CELL set and the channel deaf: the server
588
+ # never declared claude/channel (droplet 4 days, gridiron on lab).
584
589
  if mode in {"allowlisted", "dev"}:
590
+ env["SWARPH_CHANNEL"] = mode
585
591
  env["SWARPH_CHANNEL_CELL"] = cell.name
586
592
  else:
593
+ env.pop("SWARPH_CHANNEL", None)
587
594
  env.pop("SWARPH_CHANNEL_CELL", None)
588
595
  return env
589
596
 
@@ -10,6 +10,11 @@ distinction is not WHICH harness, it is WHERE THE WAKE LIVES):
10
10
  * ``claude`` / ``codex`` → ARM-INSTRUCTION. The wake lives in the harness:
11
11
  emit the watch pipeline (tail -F inbox.log | dm_notify_filter) as
12
12
  additionalContext so the agent arms it as a background watch.
13
+ * ``grok`` → the same arm instruction, with the pipeline wrapped in
14
+ ``timeout -k 5s 590m`` and one ``[MESH WAKE EXPIRED]`` line. Grok caps a
15
+ background task at 10h (measured 2026-10-01); the wrapper exits 10
16
+ minutes early so the stop is a wake, not a SIGKILL the session never
17
+ sees. The duration is one unit: coreutils timeout rejects ``9h50m``.
13
18
  * ``cursor`` → ARM-INSTRUCTION, same pipeline as claude/codex, emitted in
14
19
  Cursor's ``additional_context`` envelope (the ``~/.cursor/hooks.json``
15
20
  sessionStart shape). The wake is the session-armed tail. A missing or
@@ -43,7 +48,7 @@ from swarph_cli.cell import (
43
48
  load_cell,
44
49
  )
45
50
 
46
- _ARM_HARNESSES = ("claude", "codex", "muse", "opencode")
51
+ _ARM_HARNESSES = ("claude", "codex", "muse", "opencode", "grok")
47
52
  _INJECT_HARNESSES = ("antigravity",)
48
53
  _VERIFY_HARNESSES = ("cursor",)
49
54
  _KNOWN_HARNESSES = _ARM_HARNESSES + _INJECT_HARNESSES + _VERIFY_HARNESSES
@@ -52,6 +57,12 @@ _KNOWN_HARNESSES = _ARM_HARNESSES + _INJECT_HARNESSES + _VERIFY_HARNESSES
52
57
  _FILTER_MODULE = "swarph_cli.scripts.dm_notify_filter"
53
58
  # Same default as `swarph monitor arm-check --max-age-s` (15 minutes).
54
59
  _INBOX_MAX_AGE_S = 15 * 60
60
+ _EXPIRED_LINE = "[MESH WAKE EXPIRED] re-arm the inbox tail now"
61
+ # Harnesses whose background tasks die at a cap. The wrapper is that cap
62
+ # minus 10 minutes, as one unit coreutils timeout accepts. Grok's cap is
63
+ # 10h (grok-researcher, 2026-10-01); "9h50m" is rejected as an invalid
64
+ # time interval on this box.
65
+ _HARNESS_TIMEOUT = {"grok": "590m"}
55
66
 
56
67
 
57
68
  def _read_stdin_payload() -> dict[str, Any]:
@@ -233,7 +244,27 @@ def _provenance_note(source: str) -> str:
233
244
  return f" (identity from {source})"
234
245
 
235
246
 
236
- def _arm_instruction(cell_name: Optional[str], source: str = "unresolved") -> str:
247
+ def _watch_pipeline(inbox: Path, interpreter: str) -> str:
248
+ return f'tail -n 0 -F "{inbox}" | "{interpreter}" -u -m {_FILTER_MODULE}'
249
+
250
+
251
+ def _loud_stop(pipeline: str, duration: str) -> str:
252
+ """Self-expire before the harness kills the task, then say so.
253
+
254
+ ``-k 5s`` sends KILL if the tail is still up after TERM. A single-quoted
255
+ ``sh -c`` matches the pipeline, which quotes paths with double quotes.
256
+ """
257
+ return (
258
+ f"timeout -k 5s {duration} sh -c '{pipeline}'; "
259
+ f"echo '{_EXPIRED_LINE}'"
260
+ )
261
+
262
+
263
+ def _arm_instruction(
264
+ cell_name: Optional[str],
265
+ source: str = "unresolved",
266
+ harness: str = "",
267
+ ) -> str:
237
268
  interpreter = sys.executable
238
269
  if not cell_name:
239
270
  # CANNOT-RESOLVE is a LOUD REFUSAL, never a placeholder (card #527
@@ -259,8 +290,10 @@ def _arm_instruction(cell_name: Optional[str], source: str = "unresolved") -> st
259
290
  f'"{interpreter}" -m swarph_cli monitor arm-check '
260
291
  f'--as {cell_name} --path "{inbox}"'
261
292
  )
262
- tail = f'tail -n 0 -F "{inbox}"'
263
- pipeline = f'{tail} | "{interpreter}" -u -m {_FILTER_MODULE}'
293
+ pipeline = _watch_pipeline(inbox, interpreter)
294
+ cap = _HARNESS_TIMEOUT.get(harness)
295
+ if cap:
296
+ pipeline = _loud_stop(pipeline, cap)
264
297
  return (
265
298
  "[swarph silent-wake] This cell's DM wake lives in THIS harness. "
266
299
  "BEFORE arming, refuse a missing/stale inbox.log (#729):\n"
@@ -490,7 +523,8 @@ def run_wake_hook_output(argv: Optional[list[str]] = None) -> int:
490
523
  )
491
524
  if harness in _ARM_HARNESSES:
492
525
  return _emit(
493
- {"context": _arm_instruction(cell_name, cell_source)}, harness=harness
526
+ {"context": _arm_instruction(cell_name, cell_source, harness)},
527
+ harness=harness,
494
528
  )
495
529
  return _emit(
496
530
  {"context": _verify_report(cell_name, cell_source)}, harness=harness
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.72.1
3
+ Version: 0.72.3
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -248,6 +248,8 @@ Task types: `numeric` (rel-err), `categorical` (exact), `ranking` (Kendall-tau),
248
248
 
249
249
  `claude-sonnet-5-5:subscription` runs `claude -p` on the Claude subscription when `claude` is on PATH. The argv is fixed: `--max-turns 1`, `--output-format stream-json`, `--strict-mcp-config`, an empty MCP config, `--tools ""`, `--setting-sources ""`. The prompt goes on stdin. The child environment drops `ANTHROPIC_API_KEY`, `ANTHROPIC_AUTH_TOKEN`, and every `SWARPH_CHANNEL*` name. A result whose `apiKeySource` is not `none` is `billing:<source>`, is not scored, and aborts the rest of the run on every arm. A usage limit is retried after 60s and then 300s; the arm then stops with `--ledger`, and the next run skips tasks that already completed. It is not scored as an answer.
250
250
 
251
+ `gpt-5.6-sol:codex` runs `codex exec` on the ChatGPT subscription when `codex` is on PATH. The argv is fixed: `--json`, `--ephemeral`, `--skip-git-repo-check`, `--ignore-user-config`, `--ignore-rules`, `--sandbox read-only`, in an empty temporary directory. `codex exec` has no system-prompt flag, so the pack's `system` goes on stdin ahead of the prompt. The child environment drops `OPENAI_API_KEY`, `CODEX_API_KEY`, and every `SWARPH_CHANNEL*` name. The billing guard runs **before** the call: the codex stream reports no key source, so the arm reads `auth_mode` from `$CODEX_HOME/auth.json` and refuses with `billing:<mode>` unless it is `chatgpt` (`billing:unknown` when the file cannot be read). Usage limits follow the claude arm: retried, then resumable with `--ledger`, never scored. The isolation flags drop the operator's MCP servers and plugins only. Measured on codex-cli 0.157.0 with a one-line prompt: 15,472 input tokens by default, 14,410 with the flags. The rest is the CLI's own agent instructions, so this arm's input tokens are not comparable with the claude arm's.
252
+
251
253
  ### `swarph brain-ask` (v0.14.0)
252
254
 
253
255
  Search the **swarph-brain** (gbrain) — the swarm's sovereign $0 semantic-retrieval memory — over MCP. The "does the swarm already know X?" reflex, as a one-shot.
@@ -40,6 +40,7 @@ src/swarph_cli.egg-info/top_level.txt
40
40
  src/swarph_cli/bench/__init__.py
41
41
  src/swarph_cli/bench/backends.py
42
42
  src/swarph_cli/bench/claude_cli.py
43
+ src/swarph_cli/bench/codex_cli.py
43
44
  src/swarph_cli/bench/pack.py
44
45
  src/swarph_cli/bench/prices.py
45
46
  src/swarph_cli/bench/providers.py
@@ -270,6 +271,7 @@ tests/test_959_venv_interpreter.py
270
271
  tests/test_960_channel.py
271
272
  tests/test_960_ensure_monitor_heartbeat.py
272
273
  tests/test_961_cursor_grok_footer.py
274
+ tests/test_961_loud_stop.py
273
275
  tests/test_963_plugin_manifest.py
274
276
  tests/test_964_channel_cell.py
275
277
  tests/test_965_mesh_wait.py
@@ -284,6 +286,7 @@ tests/test_artifact_tool.py
284
286
  tests/test_artifact_uri.py
285
287
  tests/test_bench_backends.py
286
288
  tests/test_bench_claude_cli.py
289
+ tests/test_bench_codex_cli.py
287
290
  tests/test_bench_command.py
288
291
  tests/test_bench_pack.py
289
292
  tests/test_bench_prices.py
@@ -244,3 +244,28 @@ def test_parent_channel_cell_does_not_reach_a_different_cell(tmp_path, monkeypat
244
244
  assert rc == 0
245
245
  assert env_added.get("SWARPH_CHANNEL_CELL") == "science-claude"
246
246
 
247
+
248
+ def test_field_channel_exports_swarph_channel_so_serve_is_not_deaf(tmp_path, monkeypatch, capsys):
249
+ """Card #1022. A mode that came from cell.yaml must reach channel-serve:
250
+ the spawned env carries SWARPH_CHANNEL=mode (not just SWARPH_CHANNEL_CELL),
251
+ and channel_opted_in() is True under that env. On 7afd16b the field path
252
+ set only SWARPH_CHANNEL_CELL, so the server never declared claude/channel
253
+ and the cell ran deaf (droplet ~4 days, gridiron on lab).
254
+ """
255
+ from swarph_cli.channel import channel_opted_in
256
+
257
+ _registry(tmp_path, monkeypatch, with_swarph=True)
258
+ monkeypatch.delenv("SWARPH_CHANNEL", raising=False)
259
+ p = _cell_yaml(tmp_path, "claude", channel="allowlisted", name="gridiron")
260
+ rc, argv, env_added = _print(tmp_path, p, capsys)
261
+ assert rc == 0
262
+ assert "--channels" in argv and "plugin:swarph@swarph" in argv
263
+ # The env handed to claude names the mode, whatever its source was.
264
+ assert env_added.get("SWARPH_CHANNEL") == "allowlisted"
265
+ assert env_added.get("SWARPH_CHANNEL_CELL") == "gridiron"
266
+ # And the server's own gate agrees under exactly that env.
267
+ with monkeypatch.context() as ctx:
268
+ ctx.setenv("SWARPH_CHANNEL", env_added["SWARPH_CHANNEL"])
269
+ ctx.setenv("SWARPH_CHANNEL_CELL", env_added["SWARPH_CHANNEL_CELL"])
270
+ assert channel_opted_in() is True
271
+
@@ -0,0 +1,106 @@
1
+ """Card #961. A capped harness self-expires its inbox tail and says so.
2
+
3
+ Grok kills a background task at 10h. The hook wraps that tail in
4
+ ``timeout -k 5s 590m`` (one unit; ``9h50m`` is rejected) and echoes one
5
+ EXPIRED line. Claude, cursor, and codex have no known cap and stay the
6
+ bare pipeline. On main, ``--harness grok`` is an unsupported-harness
7
+ refusal and this file fails.
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import shutil
13
+ import subprocess
14
+ import time
15
+
16
+ import pytest
17
+
18
+ from swarph_cli.commands import wake_hook_output as who
19
+
20
+ _EXPIRED = "[MESH WAKE EXPIRED] re-arm the inbox tail now"
21
+
22
+
23
+ def _context(monkeypatch, capsys, harness: str, tmp_path, cell: str = "probe-cell"):
24
+ def _dir(name):
25
+ return tmp_path / "swarph_state" / name / "mesh-sidecar"
26
+
27
+ monkeypatch.setattr(who, "_sidecar_dir", _dir)
28
+ monkeypatch.setattr(who, "_resolve_cell", lambda explicit=None: (cell, "test"))
29
+ side = _dir(cell)
30
+ side.mkdir(parents=True, exist_ok=True)
31
+ inbox = side / "inbox.log"
32
+ inbox.write_text("", encoding="utf-8")
33
+ rc = who.run_wake_hook_output(["--harness", harness])
34
+ assert rc == 0
35
+ raw = json.loads(capsys.readouterr().out)
36
+ if harness == "cursor":
37
+ ctx = raw["additional_context"]
38
+ else:
39
+ ctx = raw["hookSpecificOutput"]["additionalContext"]
40
+ return ctx, inbox
41
+
42
+
43
+ def _timeout_line(ctx: str) -> str:
44
+ lines = [line.strip() for line in ctx.splitlines() if "timeout -k 5s" in line]
45
+ assert len(lines) == 1
46
+ return lines[0]
47
+
48
+
49
+ def test_grok_emits_a_single_unit_timeout_and_the_expired_echo(monkeypatch, capsys, tmp_path):
50
+ ctx, _inbox = _context(monkeypatch, capsys, "grok", tmp_path)
51
+ line = _timeout_line(ctx)
52
+ assert line.startswith("timeout -k 5s 590m sh -c '")
53
+ assert line.endswith(f"'; echo '{_EXPIRED}'")
54
+ assert "9h50m" not in ctx
55
+ assert "9h" not in line
56
+
57
+
58
+ def test_claude_cursor_and_codex_stay_the_bare_pipeline(monkeypatch, capsys, tmp_path):
59
+ for harness in ("claude", "cursor", "codex"):
60
+ ctx, _inbox = _context(monkeypatch, capsys, harness, tmp_path)
61
+ assert "timeout" not in ctx
62
+ assert "MESH WAKE EXPIRED" not in ctx
63
+ assert "tail -n 0 -F" in ctx
64
+ assert "dm_notify_filter" in ctx
65
+
66
+
67
+ def _coreutils_timeout() -> bool:
68
+ """A coreutils timeout that honors ``-k`` and a duration.
69
+
70
+ GNU and uutils both do. A macOS stand-in returns at once, so the
71
+ elapsed check would fail for the wrong reason.
72
+ """
73
+ if shutil.which("timeout") is None:
74
+ return False
75
+ proc = subprocess.run(
76
+ ["timeout", "--version"],
77
+ capture_output=True,
78
+ text=True,
79
+ )
80
+ text = f"{proc.stdout or ''}{proc.stderr or ''}".lower()
81
+ return proc.returncode == 0 and "coreutils" in text
82
+
83
+
84
+ def test_a_two_second_cap_prints_exactly_one_expired_line(monkeypatch, capsys, tmp_path):
85
+ """Run the emitted command with the duration swapped to 2s.
86
+
87
+ The scratch inbox is empty, so the filter prints nothing. After the
88
+ timeout, the shell echoes the re-arm line once.
89
+ """
90
+ ctx, inbox = _context(monkeypatch, capsys, "grok", tmp_path)
91
+ script = _timeout_line(ctx).replace("590m", "2s", 1)
92
+ assert "590m" not in script
93
+ assert inbox.is_file()
94
+ if not _coreutils_timeout():
95
+ pytest.skip("the 2s run needs a coreutils timeout")
96
+ started = time.monotonic()
97
+ proc = subprocess.run(
98
+ ["sh", "-c", script],
99
+ capture_output=True,
100
+ text=True,
101
+ timeout=20,
102
+ )
103
+ elapsed = time.monotonic() - started
104
+ expired = [line for line in proc.stdout.splitlines() if "MESH WAKE EXPIRED" in line]
105
+ assert expired == [_EXPIRED]
106
+ assert elapsed >= 1.5
@@ -0,0 +1,138 @@
1
+ """Codex bench transport: isolated codex exec, billing guard before the call, no live call."""
2
+ import json
3
+ import types
4
+
5
+ from swarph_cli.bench import backends as B
6
+ from swarph_cli.bench import codex_cli as C
7
+
8
+
9
+ def lines(text='{"answer": 68}', usage=None, extra=()):
10
+ u = usage or {"input_tokens": 14410, "cached_input_tokens": 12160,
11
+ "cache_write_input_tokens": 0, "output_tokens": 10, "reasoning_output_tokens": 0}
12
+ out = [json.dumps({"type": "thread.started", "thread_id": "t"}),
13
+ json.dumps({"type": "turn.started"})]
14
+ out += [json.dumps(e) for e in extra]
15
+ if text is not None:
16
+ out.append(json.dumps({"type": "item.completed",
17
+ "item": {"id": "item_0", "type": "agent_message", "text": text}}))
18
+ out.append(json.dumps({"type": "turn.completed", "usage": u}))
19
+ return out
20
+
21
+
22
+ def home(tmp_path, payload='{"auth_mode": "chatgpt", "OPENAI_API_KEY": null}'):
23
+ if payload is not None:
24
+ (tmp_path / "auth.json").write_text(payload, encoding="utf-8")
25
+ return {"CODEX_HOME": str(tmp_path), "PATH": "/usr/bin"}
26
+
27
+
28
+ def recording_runner(stdout="", stderr=""):
29
+ seen = {"calls": 0}
30
+
31
+ def runner(argv, input, capture_output, text, env, timeout, cwd):
32
+ seen.update(argv=argv, input=input, env=env, cwd=cwd, calls=seen["calls"] + 1)
33
+ return types.SimpleNamespace(returncode=0, stdout=stdout, stderr=stderr)
34
+
35
+ return runner, seen
36
+
37
+
38
+ def test_argv_isolation_flags():
39
+ a = C.build_argv("gpt-5.6-sol")
40
+ for flag in ("--json", "--ephemeral", "--skip-git-repo-check", "--ignore-user-config", "--ignore-rules"):
41
+ assert flag in a
42
+ assert a[a.index("--sandbox") + 1] == "read-only"
43
+ assert a[a.index("--model") + 1] == "gpt-5.6-sol"
44
+ assert a[-1] == "-" # prompt is read from stdin, never argv
45
+
46
+
47
+ def test_clean_env_strips_keys():
48
+ e = C.clean_env({"OPENAI_API_KEY": "k", "CODEX_API_KEY": "c", "SWARPH_CHANNEL": "dev",
49
+ "SWARPH_CHANNEL_CELL": "droplet", "PATH": "/usr/bin", "CODEX_HOME": "/h"})
50
+ assert set(e) == {"PATH", "CODEX_HOME"}
51
+
52
+
53
+ def test_parse_ok():
54
+ r = C.parse_stream(lines())
55
+ assert r.text == '{"answer": 68}' and r.tokens_in == 14410 and r.tokens_out == 10
56
+ assert r.error is None and r.cost_equiv is None and r.api_key_source == "chatgpt"
57
+
58
+
59
+ def test_parse_takes_the_last_message_and_the_last_usage():
60
+ first = {"type": "item.completed", "item": {"type": "agent_message", "text": "draft"}}
61
+ early = {"type": "turn.completed", "usage": {"input_tokens": 5, "output_tokens": 1}}
62
+ r = C.parse_stream(lines(extra=(first, early)))
63
+ assert r.text == '{"answer": 68}' and r.tokens_in == 14410 # running total, never summed
64
+
65
+
66
+ def test_tool_items_are_not_the_answer():
67
+ tool = {"type": "item.completed", "item": {"type": "command_execution", "text": "ls"}}
68
+ r = C.parse_stream(lines(extra=(tool,)))
69
+ assert r.text == '{"answer": 68}'
70
+
71
+
72
+ def test_no_answer_is_no_result_not_an_empty_answer():
73
+ r = C.parse_stream(lines(text=None))
74
+ assert r.error == "no_result" and r.text == ""
75
+ assert C.parse_stream([]).error == "no_result"
76
+
77
+
78
+ def test_rate_limit_is_retryable_from_event_or_stderr():
79
+ ev = {"type": "error", "message": "You've hit your usage limit. Try again later."}
80
+ assert C.parse_stream([json.dumps(ev)]).error == "rate_limit"
81
+ assert C.parse_stream([], stderr="ERROR: You've hit your usage limit").error == "rate_limit"
82
+
83
+
84
+ def test_other_failure_is_a_cli_error_and_discards_the_text():
85
+ ev = {"type": "turn.failed", "error": {"message": "model not found"}}
86
+ r = C.parse_stream(lines(extra=(ev,)))
87
+ assert r.error == "cli_error:turn.failed" and r.text == ""
88
+
89
+
90
+ def test_call_passes_system_and_prompt_on_stdin_in_an_empty_cwd(tmp_path):
91
+ runner, seen = recording_runner(stdout="\n".join(lines()))
92
+ env = dict(home(tmp_path), OPENAI_API_KEY="k")
93
+ r = C.call("gpt-5.6-sol", "PROMPT TEXT", "SYS", runner=runner, env=env)
94
+ assert r.error is None and r.text == '{"answer": 68}'
95
+ assert seen["input"] == "SYS\n\nPROMPT TEXT" and "PROMPT TEXT" not in seen["argv"]
96
+ assert "OPENAI_API_KEY" not in seen["env"] and seen["env"]["CODEX_HOME"] == str(tmp_path)
97
+ assert seen["cwd"] != str(tmp_path) and "swarph-bench-codex-" in seen["cwd"]
98
+
99
+
100
+ def test_billing_guard_refuses_before_any_call(tmp_path):
101
+ runner, seen = recording_runner(stdout="\n".join(lines()))
102
+ r = C.call("gpt-5.6-sol", "P", "S", runner=runner,
103
+ env=home(tmp_path, '{"auth_mode": "apikey", "OPENAI_API_KEY": "k"}'))
104
+ assert r.error == "billing:apikey" and r.text == "" and seen["calls"] == 0
105
+
106
+
107
+ def test_unreadable_auth_is_unknown_never_a_pass(tmp_path):
108
+ runner, seen = recording_runner(stdout="\n".join(lines()))
109
+ for payload in (None, "not json", '{"OPENAI_API_KEY": null}', '["chatgpt"]'):
110
+ d = tmp_path / str(abs(hash(payload)))
111
+ d.mkdir()
112
+ r = C.call("gpt-5.6-sol", "P", "S", runner=runner, env=home(d, payload))
113
+ assert r.error == "billing:unknown", payload
114
+ assert seen["calls"] == 0
115
+
116
+
117
+ def test_backend_reports_real_usage_and_carries_the_error(tmp_path):
118
+ runner, _ = recording_runner(stdout="\n".join(lines()))
119
+ ok = B.SubscriptionBackend(
120
+ call_fn=lambda m, p, s: C.call(m, p, s, runner=runner, env=home(tmp_path)))
121
+ res = ok.generate("gpt-5.6-sol", "P", "S")
122
+ assert res.text == '{"answer": 68}' and res.tokens_in == 14410 and res.estimated is False
123
+
124
+ bad = tmp_path / "bad"
125
+ bad.mkdir()
126
+ refused = B.SubscriptionBackend(
127
+ call_fn=lambda m, p, s: C.call(m, p, s, runner=runner, env=home(bad, None)))
128
+ res = refused.generate("gpt-5.6-sol", "P", "S")
129
+ assert res.error == "billing:unknown" and res.text == ""
130
+
131
+
132
+ def test_codex_lane_is_selectable(monkeypatch):
133
+ from swarph_cli.commands import bench
134
+
135
+ monkeypatch.setattr(bench.shutil, "which", lambda name: None)
136
+ assert bench._default_backends()["codex"].missing_creds() # stub: preflight skips it
137
+ monkeypatch.setattr(bench.shutil, "which", lambda name: "/usr/bin/" + name)
138
+ assert bench._default_backends()["codex"].missing_creds() == []
File without changes
File without changes