swarph-cli 0.71.0__tar.gz → 0.72.1__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 (469) hide show
  1. {swarph_cli-0.71.0/src/swarph_cli.egg-info → swarph_cli-0.72.1}/PKG-INFO +1 -1
  2. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/pyproject.toml +1 -1
  3. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/mesh.py +240 -8
  5. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/monitor.py +1 -1
  6. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/spawn.py +178 -3
  7. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/stall_alert.py +11 -3
  8. {swarph_cli-0.71.0 → swarph_cli-0.72.1/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  9. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
  10. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_1006_board_publisher.py +37 -0
  11. swarph_cli-0.72.1/tests/test_1013_spawn_channel.py +246 -0
  12. swarph_cli-0.72.1/tests/test_1017_muse_tui.py +129 -0
  13. swarph_cli-0.72.1/tests/test_484_codex_queue_sink.py +149 -0
  14. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_960_channel.py +14 -2
  15. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_state_split.py +6 -2
  16. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_stall_alert.py +13 -2
  17. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/LICENSE +0 -0
  18. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/README.md +0 -0
  19. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/setup.cfg +0 -0
  20. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/__main__.py +0 -0
  21. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/__init__.py +0 -0
  22. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/backends.py +0 -0
  23. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/claude_cli.py +0 -0
  24. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/pack.py +0 -0
  25. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/prices.py +0 -0
  26. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/providers.py +0 -0
  27. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/quality.py +0 -0
  28. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/refresh_prices.py +0 -0
  29. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/rules.py +0 -0
  30. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/runner.py +0 -0
  31. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/signal_score.py +0 -0
  32. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/bench/validate.py +0 -0
  33. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/caller.py +0 -0
  34. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/__init__.py +0 -0
  35. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/harden.py +0 -0
  36. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/lineage.py +0 -0
  37. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/liveness.py +0 -0
  38. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/manifest.py +0 -0
  39. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/paths.py +0 -0
  40. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/capture/verify.py +0 -0
  41. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/cell.py +0 -0
  42. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/chain_token.py +0 -0
  43. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/channel.py +0 -0
  44. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/__init__.py +0 -0
  45. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/_content.py +0 -0
  46. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/_display.py +0 -0
  47. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/_gateway_client.py +0 -0
  48. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/add.py +0 -0
  49. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/bench.py +0 -0
  50. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/board.py +0 -0
  51. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/board_merge_check.py +0 -0
  52. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/brain.py +0 -0
  53. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/brain_ask.py +0 -0
  54. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/cell.py +0 -0
  55. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/cell_probe.py +0 -0
  56. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  57. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/channel.py +0 -0
  58. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/chat.py +0 -0
  59. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/codegraph.py +0 -0
  60. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  61. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  62. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/codex_waker.py +0 -0
  63. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/compress.py +0 -0
  64. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/daemon.py +0 -0
  65. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/dreaming.py +0 -0
  66. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/event.py +0 -0
  67. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/gateway.py +0 -0
  68. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/gh_route.py +0 -0
  69. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/group.py +0 -0
  70. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/guide.py +0 -0
  71. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/highlight.py +0 -0
  72. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  73. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/hook_output.py +0 -0
  74. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/hooks.py +0 -0
  75. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/import_session.py +0 -0
  76. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/init.py +0 -0
  77. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  78. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_hook.py +0 -0
  79. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  80. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  81. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  82. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  83. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/lane.py +0 -0
  84. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/mcp_server.py +0 -0
  85. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/memory.py +0 -0
  86. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  87. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/memory_sync.py +0 -0
  88. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/okf_links.py +0 -0
  89. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/onboard.py +0 -0
  90. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  91. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  92. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/protocol_handler.py +0 -0
  93. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/ratify.py +0 -0
  94. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/schedule.py +0 -0
  95. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/security.py +0 -0
  96. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/service.py +0 -0
  97. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/timeline.py +0 -0
  98. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/version.py +0 -0
  99. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  100. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/commands/watchdog.py +0 -0
  101. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/compress/__init__.py +0 -0
  102. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/compress/levers.py +0 -0
  103. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/compress/marker.py +0 -0
  104. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/compress/verify.py +0 -0
  105. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/console_safe.py +0 -0
  106. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/delivery_queue.py +0 -0
  107. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dm_frame.py +0 -0
  108. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/__init__.py +0 -0
  109. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/candidates.py +0 -0
  110. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/clone.py +0 -0
  111. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/enrich.py +0 -0
  112. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/index_check.py +0 -0
  113. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/notify.py +0 -0
  114. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/organize.py +0 -0
  115. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/probes.py +0 -0
  116. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/report.py +0 -0
  117. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/run.py +0 -0
  118. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/slm.py +0 -0
  119. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/transcripts.py +0 -0
  120. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/dreaming/verify.py +0 -0
  121. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/__init__.py +0 -0
  122. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/feature_registry.py +0 -0
  123. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/lanes_control.py +0 -0
  124. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/schema.sql +0 -0
  125. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/server.py +0 -0
  126. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway/services_control.py +0 -0
  127. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gateway_default.py +0 -0
  128. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/gh_identity.py +0 -0
  129. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/guide/GUIDE.md +0 -0
  130. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/identity.py +0 -0
  131. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/main.py +0 -0
  132. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/multiplexer.py +0 -0
  133. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/__init__.py +0 -0
  134. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/__main__.py +0 -0
  135. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/api.py +0 -0
  136. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/cli.py +0 -0
  137. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/config.py +0 -0
  138. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/operator/fmt.py +0 -0
  139. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/orphan_daemons.py +0 -0
  140. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/pane_probe.py +0 -0
  141. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/parsers/__init__.py +0 -0
  142. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/parsers/claude.py +0 -0
  143. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  144. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  145. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  146. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  147. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  148. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  149. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  150. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  151. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  152. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  153. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  154. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  155. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_executor.py +0 -0
  156. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_executor_windows.py +0 -0
  157. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_reconciliation.py +0 -0
  158. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_reply_outbox.py +0 -0
  159. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_service_host.py +0 -0
  160. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_service_payload.py +0 -0
  161. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_service_reply.py +0 -0
  162. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/peer_staging.py +0 -0
  163. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/query_filters.py +0 -0
  164. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/__init__.py +0 -0
  165. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  166. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  167. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  168. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  169. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  170. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/service/__init__.py +0 -0
  171. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/service/app.py +0 -0
  172. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/service/providers.py +0 -0
  173. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/session_bridge.py +0 -0
  174. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  175. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  176. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/source_text.py +0 -0
  177. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/codex-waker.default +0 -0
  178. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  179. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  180. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  181. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  182. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  183. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  184. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  185. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  186. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  187. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  188. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  189. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  190. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  191. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/timeline_paths.py +0 -0
  192. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli/tokens.py +0 -0
  193. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  194. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  195. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/requires.txt +0 -0
  196. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/src/swarph_cli.egg-info/top_level.txt +0 -0
  197. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_1006_session_board.py +0 -0
  198. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_124_register_merge.py +0 -0
  199. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_126_pending_from_complete.py +0 -0
  200. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  201. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_138_memory_navigate.py +0 -0
  202. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_139_tool_probe.py +0 -0
  203. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_183_card_anchor.py +0 -0
  204. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_184_notify_honesty.py +0 -0
  205. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_201_win32_bash_refusal.py +0 -0
  206. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_243_onboard_per_peer_token.py +0 -0
  207. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_307_task4_ask_reply.py +0 -0
  208. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  209. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_319_refusal_exit_codes.py +0 -0
  210. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_323_mistral_backend.py +0 -0
  211. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_332_report_organize_findings.py +0 -0
  212. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_356_query_filters.py +0 -0
  213. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_397_gh_identity_router.py +0 -0
  214. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_397_router_preflight.py +0 -0
  215. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_402_one_identity_resolver.py +0 -0
  216. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_467b_onboard_operator_credential.py +0 -0
  217. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_472_register_caller_binding.py +0 -0
  218. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_523_guide.py +0 -0
  219. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_524_conftest_hermeticity.py +0 -0
  220. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_532_accept_flag.py +0 -0
  221. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_535_version_capability.py +0 -0
  222. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_538_signal_bench.py +0 -0
  223. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_546_default_gateway.py +0 -0
  224. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_547_verbs_from_registry.py +0 -0
  225. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_548_gateway_remainder.py +0 -0
  226. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_552_codegraph_gateway.py +0 -0
  227. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_555_relay_row_shape.py +0 -0
  228. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_556_relay_freshness.py +0 -0
  229. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_559_edges_truncated.py +0 -0
  230. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_564_bootstrap_first_peer.py +0 -0
  231. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  232. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_579_register_bootstrap_message.py +0 -0
  233. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_591_cli_step_graph.py +0 -0
  234. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_644_monitor_supervision.py +0 -0
  235. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_648_install_unit.py +0 -0
  236. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_654_reexec.py +0 -0
  237. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_656_owned_slm.py +0 -0
  238. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_684_dreaming_verb.py +0 -0
  239. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_689_pkg_version_order.py +0 -0
  240. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_715_grok_wake.py +0 -0
  241. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_716_timeline_one_path.py +0 -0
  242. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_723_grok_0251.py +0 -0
  243. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  244. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_725_stdio_reconfigure.py +0 -0
  245. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_726_grok_limit_screen.py +0 -0
  246. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_729_arm_check_delivery_gap.py +0 -0
  247. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_729_dm_once.py +0 -0
  248. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_729_wake_watchdog.py +0 -0
  249. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  250. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_735_dm_mcp_tools.py +0 -0
  251. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_739_wake_writer.py +0 -0
  252. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_740_edit_project.py +0 -0
  253. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_762_cards_edit_priority.py +0 -0
  254. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_764_enrich_collapse.py +0 -0
  255. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_801_confirm_flow_cli.py +0 -0
  256. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_802_stage_history.py +0 -0
  257. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_825_codegraph_hook_noise.py +0 -0
  258. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_825_install_posttooluse_only.py +0 -0
  259. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  260. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_829_counterfactual_column.py +0 -0
  261. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_830_hook_binding_version.py +0 -0
  262. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_84_cli_labels.py +0 -0
  263. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_866_doctrine_guide.py +0 -0
  264. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_872_identity_refuses_not_warns.py +0 -0
  265. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_874_source_text.py +0 -0
  266. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  267. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_937_dreaming_notify.py +0 -0
  268. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_938_organize_line_limit.py +0 -0
  269. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_941_bench_backends.py +0 -0
  270. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_941_harness_342.py +0 -0
  271. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_945_same_line_pairing.py +0 -0
  272. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_955_prompt_audit.py +0 -0
  273. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_957_auth_http.py +0 -0
  274. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_959_venv_interpreter.py +0 -0
  275. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
  276. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_961_cursor_grok_footer.py +0 -0
  277. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_963_plugin_manifest.py +0 -0
  278. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_964_channel_cell.py +0 -0
  279. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_965_mesh_wait.py +0 -0
  280. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_975_channel_hop.py +0 -0
  281. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_add_security_gate.py +0 -0
  282. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_add.py +0 -0
  283. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_hash.py +0 -0
  284. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_lib.py +0 -0
  285. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_mcp.py +0 -0
  286. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_skill.py +0 -0
  287. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_tool.py +0 -0
  288. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_artifact_uri.py +0 -0
  289. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_backends.py +0 -0
  290. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_claude_cli.py +0 -0
  291. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_command.py +0 -0
  292. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_pack.py +0 -0
  293. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_prices.py +0 -0
  294. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_quality.py +0 -0
  295. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_runner.py +0 -0
  296. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_bench_validate.py +0 -0
  297. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_board_card_thread.py +0 -0
  298. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_board_command.py +0 -0
  299. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_board_merge_check.py +0 -0
  300. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_board_obligation_close.py +0 -0
  301. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_brain_ask_command.py +0 -0
  302. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_brain_ask_env_diagnosis.py +0 -0
  303. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_brain_ask_identity_explicitness.py +0 -0
  304. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_brain_command.py +0 -0
  305. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_caller_meta_guard.py +0 -0
  306. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_lineage.py +0 -0
  307. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_liveness.py +0 -0
  308. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_manifest.py +0 -0
  309. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_paths.py +0 -0
  310. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_capture_security.py +0 -0
  311. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_command_dispatch.py +0 -0
  312. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_harden.py +0 -0
  313. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_loader.py +0 -0
  314. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_selfcheck.py +0 -0
  315. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cell_verify.py +0 -0
  316. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_chain_token.py +0 -0
  317. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_channel_command.py +0 -0
  318. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  319. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_channel_post.py +0 -0
  320. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_channel_wake_policy_filter.py +0 -0
  321. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_chat_command.py +0 -0
  322. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_claude_parser.py +0 -0
  323. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_claude_tmux_template.py +0 -0
  324. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codegraph_caller_scope.py +0 -0
  325. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codegraph_command.py +0 -0
  326. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codegraph_hook.py +0 -0
  327. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codegraph_mcp.py +0 -0
  328. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_codex_waker.py +0 -0
  329. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_caller_convention.py +0 -0
  330. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_command.py +0 -0
  331. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_levers.py +0 -0
  332. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_marker.py +0 -0
  333. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_compress_verify.py +0 -0
  334. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_console_encoding_e2e.py +0 -0
  335. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_content_composition.py +0 -0
  336. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_cursor_membrane.py +0 -0
  337. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_daemon_command.py +0 -0
  338. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_daemon_console_encoding.py +0 -0
  339. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_daemon_delivery.py +0 -0
  340. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_delivery_queue.py +0 -0
  341. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_display_sanitize.py +0 -0
  342. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  343. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_event_emit.py +0 -0
  344. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_feature_to_uri.py +0 -0
  345. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gateway_brain.py +0 -0
  346. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gateway_client.py +0 -0
  347. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gateway_command.py +0 -0
  348. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gateway_message_idempotency.py +0 -0
  349. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  350. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_group_command.py +0 -0
  351. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_guide_commands_parse.py +0 -0
  352. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_guide_selfcheck.py +0 -0
  353. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_heartbeat_check.py +0 -0
  354. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_highlight_command.py +0 -0
  355. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hook_output.py +0 -0
  356. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_activity_marker.py +0 -0
  357. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_add.py +0 -0
  358. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_bundle.py +0 -0
  359. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_init.py +0 -0
  360. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_legacy_generation_removal.py +0 -0
  361. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_lifecycle.py +0 -0
  362. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_merge.py +0 -0
  363. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_verify.py +0 -0
  364. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  365. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_hooks_win32_command_path.py +0 -0
  366. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_import_command.py +0 -0
  367. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_inbox_identity_provenance.py +0 -0
  368. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_init_command.py +0 -0
  369. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_init_provider_menu.py +0 -0
  370. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_codex_hooks.py +0 -0
  371. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_hook.py +0 -0
  372. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_multiplexer_command.py +0 -0
  373. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_postcompact_hook.py +0 -0
  374. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_install_wake_hook.py +0 -0
  375. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_lane_command.py +0 -0
  376. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_main.py +0 -0
  377. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_main_unknown_verb_guard.py +0 -0
  378. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mcp_missing_hint.py +0 -0
  379. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mcp_server.py +0 -0
  380. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_command.py +0 -0
  381. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_emit_snippet.py +0 -0
  382. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_gateway.py +0 -0
  383. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_sync.py +0 -0
  384. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_memory_sync_membrane.py +0 -0
  385. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_command.py +0 -0
  386. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_inbox_mark_read.py +0 -0
  387. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_inbox_sanitize.py +0 -0
  388. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_originate_thread_623.py +0 -0
  389. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_send_unknown_peer.py +0 -0
  390. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_sidecar.py +0 -0
  391. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  392. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_sidecar_muse_sink.py +0 -0
  393. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  394. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_meta_edge_identity.py +0 -0
  395. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_channel_poll.py +0 -0
  396. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_cli.py +0 -0
  397. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_daemon_collision.py +0 -0
  398. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_monitor_self_name_verification.py +0 -0
  399. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_multiplexer.py +0 -0
  400. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_muse_membrane.py +0 -0
  401. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_muse_own_cli.py +0 -0
  402. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_obligation_close_is_surfaced.py +0 -0
  403. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_okf_links.py +0 -0
  404. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_onboard_command.py +0 -0
  405. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_opencode_membrane.py +0 -0
  406. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_opencode_plugin.py +0 -0
  407. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_operator_me.py +0 -0
  408. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_orphan_daemons.py +0 -0
  409. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_packaged_artifacts_exist.py +0 -0
  410. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_packaged_skills.py +0 -0
  411. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_executor.py +0 -0
  412. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_executor_windows.py +0 -0
  413. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_reply_drain_command.py +0 -0
  414. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_reply_drain_supervisor.py +0 -0
  415. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_reply_outbox.py +0 -0
  416. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_reply_transport.py +0 -0
  417. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_service_e2e.py +0 -0
  418. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_service_host.py +0 -0
  419. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_service_payload.py +0 -0
  420. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_peer_service_reply.py +0 -0
  421. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_postcompact_recall.py +0 -0
  422. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_process_liveness.py +0 -0
  423. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_protocol_handler.py +0 -0
  424. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_providers_isolation.py +0 -0
  425. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_ratify_command.py +0 -0
  426. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_readme_claims_no_gateway_default.py +0 -0
  427. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_repl_caller_convention.py +0 -0
  428. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_schedule_command.py +0 -0
  429. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_schedule_create_contract.py +0 -0
  430. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_security.py +0 -0
  431. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_service_app.py +0 -0
  432. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_service_command.py +0 -0
  433. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_service_providers.py +0 -0
  434. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_session_bridge_inject.py +0 -0
  435. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_session_bridge_probe.py +0 -0
  436. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_session_bridge_resolve.py +0 -0
  437. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_session_path_role_gate.py +0 -0
  438. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_shipped_units_match_the_cli.py +0 -0
  439. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_smoke_chat.py +0 -0
  440. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_smoke_one_shot.py +0 -0
  441. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_smoke_phase_5_5.py +0 -0
  442. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_base_hoist.py +0 -0
  443. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_command.py +0 -0
  444. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_identity_stamp.py +0 -0
  445. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_live_pin.py +0 -0
  446. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_mitosis_lineage.py +0 -0
  447. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_tmux_session.py +0 -0
  448. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_spawn_windows_relaunch.py +0 -0
  449. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_subprocess_decode_encoding.py +0 -0
  450. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_timeline_command.py +0 -0
  451. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_tmux_wake_runstate_defer.py +0 -0
  452. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_tmux_wake_session_reanchor.py +0 -0
  453. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_tmux_wake_stale.py +0 -0
  454. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_tmux_wake_submit_verify.py +0 -0
  455. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_token_file_one_parser.py +0 -0
  456. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_token_precedence_explicit_first.py +0 -0
  457. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_unified_token_resolver.py +0 -0
  458. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_version_command.py +0 -0
  459. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_version_consistency.py +0 -0
  460. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_vibe_membrane.py +0 -0
  461. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog.py +0 -0
  462. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_dm_wake.py +0 -0
  463. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  464. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  465. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_emit_health.py +0 -0
  466. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_install_flags.py +0 -0
  467. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_model_rung.py +0 -0
  468. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/tests/test_watchdog_stale_peers.py +0 -0
  469. {swarph_cli-0.71.0 → swarph_cli-0.72.1}/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.71.0
3
+ Version: 0.72.1
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.71.0"
7
+ version = "0.72.1"
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.71.0"
19
+ __version__ = "0.72.1"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -1482,6 +1482,7 @@ class TmuxSink(Sink):
1482
1482
 
1483
1483
  def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> Optional[bool]:
1484
1484
  led = state.ledger(self.name)
1485
+ self.failure_reason = None
1485
1486
  # #723: a rate-limit or usage-limit screen is not deliverable. An
1486
1487
  # empty composer beside "Retry failed: rate limit" used to inject
1487
1488
  # again, and the 1.0.44 upgrade modal (no Grok footer) used to
@@ -1619,6 +1620,7 @@ class TmuxSink(Sink):
1619
1620
  # Human text shares the composer (possibly merged into our
1620
1621
  # wake) — an Enter here submits THEIR line (#403's shape).
1621
1622
  return None
1623
+ self.failure_reason = _sink_failure_detail(self.target)
1622
1624
  return False # pane unreadable: keep the failure loud
1623
1625
  # Fresh path, gated on the OBSERVED composer (gpt-ops REVISE, #312):
1624
1626
  # "wake" — wake text already sits ALONE in the composer (a monitor
@@ -1647,6 +1649,7 @@ class TmuxSink(Sink):
1647
1649
  if composer == "busy":
1648
1650
  return None
1649
1651
  if composer != "clear":
1652
+ self.failure_reason = _sink_failure_detail(self.target)
1650
1653
  return False
1651
1654
  # #619: never inject mid-turn. A wake into a RUNNING TUI lands in
1652
1655
  # cursor's follow-up queue, which is input-gated (measured live
@@ -1656,6 +1659,13 @@ class TmuxSink(Sink):
1656
1659
  # after the turn ends injects into an IDLE composer, where Enter
1657
1660
  # submits immediately. Cost: <= one poll interval of delay.
1658
1661
  if _agent_running(self.target):
1662
+ seen = _capture_pane_lines(self.target)
1663
+ if seen and _muse_running(seen):
1664
+ print(
1665
+ f"[monitor] {self.name}: muse turn in progress — "
1666
+ "deferring, zero keys",
1667
+ flush=True,
1668
+ )
1659
1669
  return None
1660
1670
  # Module-global lookup on purpose: the sidecar regression suites patch
1661
1671
  # `mesh._tmux_wake`, and a `from`-import here would silently bypass them.
@@ -1922,6 +1932,117 @@ class MuseSink(Sink):
1922
1932
  return f"{count} DM{plural} not yet delivered to {self.name}"
1923
1933
 
1924
1934
 
1935
+ _CODEX_SEND_TIMEOUT_S = 120.0
1936
+
1937
+
1938
+ def _codex_send(thread: str, text: str, *,
1939
+ _run=subprocess.run,
1940
+ _which=shutil.which,
1941
+ _timeout: float = _CODEX_SEND_TIMEOUT_S,
1942
+ ) -> tuple:
1943
+ """One `codex queue --thread <thread> --message <text>`.
1944
+
1945
+ Returns (True, "") on a verified queue, else (False, NAMED reason).
1946
+ Every refusal is named — non-zero exit, missing binary, launch/timeout
1947
+ failure — so a dead wake path reads as a reason, never as silence.
1948
+ `_run`/`_which` are injectable so the result mapping is testable without
1949
+ a codex binary on PATH. Unlike `muse session-message send`, codex queue
1950
+ is a same-box queue file write: no ingress gate, no peer approval.
1951
+ """
1952
+ prog = _which("codex")
1953
+ if not prog:
1954
+ return (False, "codex binary not found on PATH")
1955
+ argv = [prog, "queue", "--thread", thread, "--message", text]
1956
+ try:
1957
+ proc = _run(argv, capture_output=True, text=True,
1958
+ timeout=_timeout)
1959
+ except FileNotFoundError:
1960
+ return (False, "codex binary not found on PATH")
1961
+ except subprocess.TimeoutExpired:
1962
+ return (False,
1963
+ f"codex queue timed out after {_timeout:.0f}s")
1964
+ except OSError as exc:
1965
+ return (False, f"codex queue failed to launch: {exc}")
1966
+ if getattr(proc, "returncode", 1) != 0:
1967
+ out = ((getattr(proc, "stdout", None) or "")
1968
+ + "\n" + (getattr(proc, "stderr", None) or ""))
1969
+ snippet = out.strip().splitlines()
1970
+ detail = snippet[-1][:200] if snippet else "no output"
1971
+ return (False,
1972
+ f"codex queue exited "
1973
+ f"{getattr(proc, 'returncode', '?')}: {detail}")
1974
+ return (True, "")
1975
+
1976
+
1977
+ class CodexSink(Sink):
1978
+ """Wake the live gpt-ops session with `codex queue` instead of typing.
1979
+
1980
+ Edge-triggered on the drain like MuseSink: while `wake_outstanding`
1981
+ stands (gateway unread > 0), further deliveries report delivered with NO
1982
+ new send; the next delivery after the drain sends again. There is no
1983
+ composer to read — codex queues into the session, it does not type —
1984
+ so there is no politeness gate, only the outstanding flag plus the
1985
+ gateway drain check. A refused or failed send returns False with a NAMED
1986
+ reason printed loud; it never advances the cursor in silence.
1987
+ """
1988
+
1989
+ is_push = True
1990
+
1991
+ def __init__(self, thread: str):
1992
+ super().__init__(f"codex:{thread}")
1993
+ self.thread = thread
1994
+
1995
+ def _prompt(self, dms: list) -> str:
1996
+ # Full bodies cross: this prompt IS the delivery, and the cell must
1997
+ # answer the message it actually received, not a 160-char preview.
1998
+ lines = [
1999
+ f"You have {len(dms)} new mesh DM(s). "
2000
+ "Read each and act per your standing instructions.",
2001
+ "",
2002
+ ]
2003
+ for dm in dms:
2004
+ lines.append(
2005
+ f"--- id={dm.get('id')} from={dm.get('from_node')} "
2006
+ f"kind={dm.get('kind')} ---"
2007
+ )
2008
+ lines.append(dm.get("content") or "")
2009
+ lines.append("")
2010
+ return "\n".join(lines)
2011
+
2012
+ def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> Optional[bool]:
2013
+ led = state.ledger(self.name)
2014
+ if led.get("wake_outstanding"):
2015
+ # Lazy: watchdog imports mesh module-level, so the reverse must
2016
+ # not. None (gateway error) reads as NOT-drained — re-arming on
2017
+ # an unreadable drain signal would re-open the stack.
2018
+ from swarph_cli.commands.watchdog import _gateway_unread_count
2019
+ if _gateway_unread_count(state.gateway, state.self_name,
2020
+ state.token) != 0:
2021
+ print(f"[monitor] {self.name}: delivery reported on a STANDING wake "
2022
+ f"(no send this poll)", flush=True)
2023
+ return True
2024
+ led["wake_outstanding"] = False # drained since — re-arm
2025
+ if not dms:
2026
+ # Nothing recoverable to hand over (log rotated) — do NOT claim
2027
+ # a delivery; the ledger stays and the gap stays visible.
2028
+ print(f"[monitor] {self.name}: {up_to_id} observed but no body "
2029
+ f"recoverable from inbox.log for the owed range", flush=True)
2030
+ return False
2031
+ # Module-global lookup on purpose: the sidecar regression suites patch
2032
+ # `mesh._codex_send`, and a `from`-import here would silently bypass them.
2033
+ ok, reason = _codex_send(self.thread, self._prompt(dms))
2034
+ if ok:
2035
+ led["wake_outstanding"] = True
2036
+ led["last_wake_injected_at"] = time.time()
2037
+ return True
2038
+ print(f"[monitor] {self.name}: codex send FAILED: {reason}", flush=True)
2039
+ return False
2040
+
2041
+ def pending_label(self, count: int) -> str:
2042
+ plural = "s" if count != 1 else ""
2043
+ return f"{count} DM{plural} not yet delivered to {self.name}"
2044
+
2045
+
1925
2046
  def parse_sink(spec: str) -> Sink:
1926
2047
  """`--deliver SINK` -> Sink. Unknown or held specs RAISE; nothing no-ops."""
1927
2048
  if spec == "pull":
@@ -1956,6 +2077,13 @@ def parse_sink(spec: str) -> Sink:
1956
2077
  "sink 'muse:' needs a session, e.g. muse:<session-uuid>"
1957
2078
  )
1958
2079
  return MuseSink(session)
2080
+ if spec.startswith("codex:"):
2081
+ thread = spec[len("codex:"):]
2082
+ if not thread:
2083
+ raise MonitorSinkError(
2084
+ "sink 'codex:' needs a thread, e.g. codex:<session-uuid>"
2085
+ )
2086
+ return CodexSink(thread)
1959
2087
  if spec.startswith("webhook:"):
1960
2088
  # HELD by the commander: outward-facing egress, and a build greenlight
1961
2089
  # does not clear an egress gate. It must EXIT, not silently no-op — a
@@ -1968,7 +2096,8 @@ def parse_sink(spec: str) -> Sink:
1968
2096
  )
1969
2097
  raise MonitorSinkError(
1970
2098
  f"unknown sink {spec!r}; expected pull, none, stdout, tmux:<target>, "
1971
- "tmux-notify:<target>, cursor-print:<cell>, or muse:<session>"
2099
+ "tmux-notify:<target>, cursor-print:<cell>, muse:<session>, "
2100
+ "or codex:<thread>"
1972
2101
  )
1973
2102
 
1974
2103
 
@@ -2173,6 +2302,9 @@ _OPENCODE_WAKE_PROMPT = "check mesh: swarph_dm_unread"
2173
2302
  # Grok's TUI has the same shape (#715, grok 1.0.41): no push channel, so
2174
2303
  # the wake is one line naming the pull tool and no DM body.
2175
2304
  _GROK_WAKE_PROMPT = "check mesh: swarph_dm_unread"
2305
+ # Muse 1.4.1 has no push channel either. The wake names the pull tool,
2306
+ # same text the grok and opencode sinks inject.
2307
+ _MUSE_WAKE_PROMPT = "check mesh: swarph_dm_unread"
2176
2308
  # A finished turn keeps its ▣ header and adds a duration.
2177
2309
  # Short turns read `· 3.3s` (drop-on-meta-edge, 1.18.33, card #961 post
2178
2310
  # 54557). A longer idle turn reads `· 3m 4s` (opencode pane, 2026-09-29
@@ -2310,7 +2442,7 @@ def _only_wake_text(content: str) -> bool:
2310
2442
  human's line.
2311
2443
  """
2312
2444
  rest = content
2313
- for prompt in (_GROK_WAKE_PROMPT, _OPENCODE_WAKE_PROMPT, _WAKE_PROMPT):
2445
+ for prompt in (_MUSE_WAKE_PROMPT, _GROK_WAKE_PROMPT, _OPENCODE_WAKE_PROMPT, _WAKE_PROMPT):
2314
2446
  rest = rest.replace(prompt, "")
2315
2447
  return rest.strip() == ""
2316
2448
 
@@ -2475,12 +2607,43 @@ def _is_cursor_composer_row(line: str) -> bool:
2475
2607
  return row.startswith("→") and not row.startswith("▎")
2476
2608
 
2477
2609
 
2610
+ def _is_muse_rule(line: str) -> bool:
2611
+ """A muse composer rule is a run of ``─``, measured on 1.4.1."""
2612
+ s = line.strip()
2613
+ return len(s) >= 8 and "─" in s and set(s) <= {"─"}
2614
+
2615
+
2616
+ def _is_muse_status(line: str) -> bool:
2617
+ """A status row under the lower rule.
2618
+
2619
+ Measured muse-spark is ``model · effort · path · YOLO``. An echo
2620
+ provider is ``echo · ~/x · YOLO``. The anchor is two or more `` · ``
2621
+ separators, not the model name. A model switch must not blind the sink.
2622
+ """
2623
+ return line.count(" · ") >= 2
2624
+
2625
+
2626
+ def _is_muse_composer_row(lines: list[str], index: int) -> bool:
2627
+ """The live muse composer is a ``❯`` row between two rules, with the
2628
+ model status under the lower rule. A transcript ``❯`` above that box
2629
+ has no rule on both sides, so it is history."""
2630
+ row = lines[index].strip()
2631
+ if not row.startswith("❯"):
2632
+ return False
2633
+ if index == 0 or index + 1 >= len(lines):
2634
+ return False
2635
+ if not (_is_muse_rule(lines[index - 1]) and _is_muse_rule(lines[index + 1])):
2636
+ return False
2637
+ return any(_is_muse_status(ln) for ln in lines[index + 2:index + 6])
2638
+
2639
+
2478
2640
  def _bottom_tui(lines: list[str]) -> "str | None":
2479
2641
  """Who owns the bottom-most composer row in the non-empty tail.
2480
2642
 
2481
- ``"cursor"``, ``"grok"``, or ``"opencode"``. A match above that tail
2482
- is scrollback. The lower composer wins, so a quoted grok box or a
2483
- quoted ``┃ Build ·`` row above the cursor composer does not take the pane.
2643
+ ``"cursor"``, ``"grok"``, ``"opencode"``, or ``"muse"``. A match above
2644
+ that tail is scrollback. The lower composer wins, so a quoted grok box
2645
+ or a quoted ``┃ Build ·`` row above the cursor composer does not take
2646
+ the pane.
2484
2647
  """
2485
2648
  tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
2486
2649
  for i in range(len(tail) - 1, -1, -1):
@@ -2490,12 +2653,15 @@ def _bottom_tui(lines: list[str]) -> "str | None":
2490
2653
  return "grok"
2491
2654
  if _is_opencode_composer_row(tail[i]):
2492
2655
  return "opencode"
2656
+ if _is_muse_composer_row(tail, i):
2657
+ return "muse"
2493
2658
  # A permission dialog is taller than the composer tail. Look further
2494
2659
  # for that one row. A mode row or a cursor/grok composer in the same
2495
2660
  # stretch is a quote, not a reason to take the pane.
2496
2661
  wider = _nonempty_tail(lines, _OPENCODE_PERMISSION_TAIL)
2497
2662
  for i in range(len(wider) - 1, -1, -1):
2498
- if _is_cursor_composer_row(wider[i]) or _is_grok_composer_row(wider, i):
2663
+ if (_is_cursor_composer_row(wider[i]) or _is_grok_composer_row(wider, i)
2664
+ or _is_muse_composer_row(wider, i)):
2499
2665
  return None
2500
2666
  if not _is_opencode_composer_row(wider[i]):
2501
2667
  continue
@@ -2645,14 +2811,74 @@ def _grok_composer_state(lines: list[str]) -> Optional[str]:
2645
2811
  return "busy"
2646
2812
 
2647
2813
 
2814
+ def _is_muse_pane(lines: list[str]) -> bool:
2815
+ """Muse 1.4.1: the ``❯`` row between two rules is the bottom composer."""
2816
+ return bool(lines) and _bottom_tui(lines) == "muse"
2817
+
2818
+
2819
+ def _muse_input(lines: list[str]) -> Optional[str]:
2820
+ """Text after the live ``❯``, '' when that row is empty, None if the
2821
+ bottom composer is not muse. A transcript ``❯`` above the rules is not
2822
+ this row."""
2823
+ tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
2824
+ for i in range(len(tail) - 1, -1, -1):
2825
+ if (_is_cursor_composer_row(tail[i]) or _is_grok_composer_row(tail, i)
2826
+ or _is_opencode_composer_row(tail[i])):
2827
+ return None
2828
+ if _is_muse_composer_row(tail, i):
2829
+ return tail[i].strip()[1:].strip()
2830
+ return None
2831
+
2832
+
2833
+ def _muse_composer_state(lines: list[str]) -> Optional[str]:
2834
+ text = _muse_input(lines)
2835
+ if text is None:
2836
+ return None
2837
+ if not text:
2838
+ return "clear"
2839
+ if _only_wake_text(text):
2840
+ return "wake"
2841
+ return "busy"
2842
+
2843
+
2844
+ def _muse_running(lines: list[str]) -> bool:
2845
+ """A muse turn in progress. 1.4.1 draws ``esc to interrupt`` on the
2846
+ status row above the composer (``Double checking (Ns · esc to interrupt)``).
2847
+ The card also names a ``Thinking`` row; either one means a keystroke
2848
+ would land in the turn, not the idle composer. The phrase leaves the
2849
+ visible screen when the turn ends."""
2850
+ if not _is_muse_pane(lines):
2851
+ return False
2852
+ for ln in lines:
2853
+ s = ln.strip()
2854
+ if "esc to interrupt" in s or s == "Thinking" or s.startswith("Thinking "):
2855
+ return True
2856
+ return False
2857
+
2858
+
2648
2859
  def _wake_prompt_for(lines: Optional[list[str]]) -> str:
2649
2860
  if lines and _is_grok_pane(lines):
2650
2861
  return _GROK_WAKE_PROMPT
2651
2862
  if lines and _is_opencode_pane(lines):
2652
2863
  return _OPENCODE_WAKE_PROMPT
2864
+ if lines and _is_muse_pane(lines):
2865
+ return _MUSE_WAKE_PROMPT
2653
2866
  return _WAKE_PROMPT
2654
2867
 
2655
2868
 
2869
+ def _sink_failure_detail(target: str) -> str:
2870
+ """Why a delivery returned False.
2871
+
2872
+ A captured pane with no known composer is an unrecognised TUI. A missing
2873
+ pane stays the old 'probably gone' line. Callers print this phrase as
2874
+ the failure line.
2875
+ """
2876
+ lines = _capture_pane_lines(target)
2877
+ if lines and _bottom_tui(lines) is None and _composer_line(lines) is None:
2878
+ return "unrecognised TUI"
2879
+ return "the sink is probably gone (session restart / renamed target)"
2880
+
2881
+
2656
2882
  def _agent_running(target: str) -> Optional[bool]:
2657
2883
  """True while a keystroke would miss the idle composer.
2658
2884
 
@@ -2673,6 +2899,8 @@ def _agent_running(target: str) -> Optional[bool]:
2673
2899
  return _opencode_running(lines)
2674
2900
  if _is_grok_pane(lines):
2675
2901
  return _grok_running(lines)
2902
+ if _is_muse_pane(lines):
2903
+ return _muse_running(lines)
2676
2904
  tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
2677
2905
  composer = _composer_line(tail)
2678
2906
  if composer is None or not composer.startswith("→"):
@@ -2721,6 +2949,8 @@ def _composer_state(target: str) -> Optional[str]:
2721
2949
  return _opencode_composer_state(lines)
2722
2950
  if _is_grok_pane(lines):
2723
2951
  return _grok_composer_state(lines)
2952
+ if _is_muse_pane(lines):
2953
+ return _muse_composer_state(lines)
2724
2954
  composer = _composer_line(lines)
2725
2955
  if composer is not None and composer.startswith("→"):
2726
2956
  # A cursor marker above the bottom tail is a quoted row. The
@@ -3125,10 +3355,12 @@ def _monitor_deliver(state: MonitorState) -> None:
3125
3355
  else:
3126
3356
  # A dead sink is VISIBLE instead of silently freezing anything.
3127
3357
  led["consecutive_failures"] = int(led["consecutive_failures"]) + 1
3358
+ detail = getattr(sink, "failure_reason", None) or (
3359
+ "the sink is probably gone (session restart / renamed target)"
3360
+ )
3128
3361
  print(f"{state.log_prefix} DELIVERY FAILED to {sink.name} "
3129
3362
  f"({led['consecutive_failures']} consecutive) -- DMs up to "
3130
- f"id {observed} were still observed and archived; the sink "
3131
- f"is probably gone (session restart / renamed target)",
3363
+ f"id {observed} were still observed and archived; {detail}",
3132
3364
  file=sys.stderr, flush=True)
3133
3365
  changed = True
3134
3366
 
@@ -64,7 +64,7 @@ def _build_parser() -> argparse.ArgumentParser:
64
64
  default=[],
65
65
  metavar="SINK",
66
66
  help="repeatable; pull (default) | tmux:<target> | tmux-notify:<target> "
67
- "| stdout | none | cursor-print:<cell> | muse:<session>. "
67
+ "| stdout | none | cursor-print:<cell> | muse:<session> | codex:<thread>. "
68
68
  "Each sink gets its OWN delivery ledger.",
69
69
  )
70
70
  start.add_argument("--poll-s", type=int, default=mesh._DEFAULT_POLL_S)
@@ -64,7 +64,7 @@ _BANNER = """\
64
64
  _USAGE = """\
65
65
  Usage:
66
66
  swarph spawn [<role-or-path>] [--onboarding PATH-OR-URL]
67
- [--dry-run] [--no-starter] [--print-id]
67
+ [--dry-run] [--no-starter] [--print-id] [--print-resolved]
68
68
  [-- provider-extra-args...]
69
69
 
70
70
  Resolution (first match wins):
@@ -147,6 +147,15 @@ def _build_parser() -> argparse.ArgumentParser:
147
147
  action="store_true",
148
148
  help="Print resolved session-id to stdout before exec.",
149
149
  )
150
+ p.add_argument(
151
+ "--print-resolved",
152
+ action="store_true",
153
+ help="Print the resolved provider argv + swarph-added env as JSON "
154
+ "and exit without exec (channel validation runs first; an invalid "
155
+ "channel exits non-zero naming the field). Named --print-resolved, "
156
+ "not --print, because argparse prefix-matching would route a bare "
157
+ "--print to --print-id and proceed to a REAL launch.",
158
+ )
150
159
  p.add_argument(
151
160
  "--new-instance",
152
161
  action="store_true",
@@ -377,6 +386,126 @@ def _set_live_pin_safe(role: str) -> None:
377
386
  file=sys.stderr)
378
387
 
379
388
 
389
+ #: cell.yaml `channel:` values valid per provider. "off"/absent means no
390
+ #: channel for every provider. Anything else on an unlisted provider fails
391
+ #: closed (a channel the provider cannot consume would start a deaf cell).
392
+ _CLAUDE_CHANNELS = frozenset({"allowlisted", "dev"})
393
+ _MUSE_CHANNELS = frozenset({"ingress"})
394
+
395
+
396
+ # Providers whose spawn consumes $SWARPH_CHANNEL. Anything else ignores an
397
+ # inherited value (a Claude cell's shell carries allowlisted; spawning grok
398
+ # from it must not refuse). Measured 2026-10-01: 0.72.0 blamed cell.yaml
399
+ # for that env and exited on grok-researcher.yaml, which has no channel field.
400
+ _ENV_CHANNEL_PROVIDERS = frozenset({"claude", "muse"})
401
+
402
+
403
+ def _channel_where(source: str) -> str:
404
+ """Name the place the mode came from. An env value must not read as cell.yaml."""
405
+ if source == "env":
406
+ return "SWARPH_CHANNEL"
407
+ return "cell.yaml: 'channel'"
408
+
409
+
410
+ def _field_channel(cell: Cell) -> str:
411
+ extra = getattr(cell, "extra", None) or {}
412
+ value = extra.get("channel", "") if isinstance(extra, dict) else ""
413
+ if value is None:
414
+ return ""
415
+ if not isinstance(value, str):
416
+ raise CellError(
417
+ f"cell.yaml: 'channel' must be a string; got "
418
+ f"{type(value).__name__}."
419
+ )
420
+ return value
421
+
422
+
423
+ def _resolve_channel(cell: Cell) -> tuple[str, str]:
424
+ """Effective channel mode and where it came from.
425
+
426
+ ``$SWARPH_CHANNEL`` wins (incl. "off") only for providers that support
427
+ it: claude (allowlisted/dev/off) and muse (ingress/off). Other providers
428
+ ignore the inherited env, as before 0.72.0, and the cell.yaml field is
429
+ still read. Returns ``(mode, source)`` with source ``env``, ``cell.yaml``,
430
+ or ``""``. Never launches anything; callers validate.
431
+ """
432
+ env_mode = os.environ.get("SWARPH_CHANNEL") or ""
433
+ provider = getattr(cell, "provider", None)
434
+ # No provider attribute: the stamp probes construct a name-only cell.
435
+ # Those are claude-shaped; the env still applies.
436
+ if env_mode and (provider is None or provider in _ENV_CHANNEL_PROVIDERS):
437
+ return env_mode, "env"
438
+ field = _field_channel(cell)
439
+ return field, ("cell.yaml" if field else "")
440
+
441
+
442
+ def _channel_marketplace_dir(source: str = "cell.yaml") -> Path:
443
+ """Install location of the `swarph` Claude marketplace (the allowlist).
444
+
445
+ Resolved through ~/.claude/plugins/known_marketplaces.json, the same
446
+ registry claude reads — never a second hardcoded copy that drifts.
447
+ ``source`` names env vs cell.yaml so a missing registry is not blamed
448
+ on the file when the mode came from ``$SWARPH_CHANNEL``.
449
+ """
450
+ where = _channel_where(source)
451
+ registry = Path.home() / ".claude" / "plugins" / "known_marketplaces.json"
452
+ try:
453
+ data = json.loads(registry.read_text(encoding="utf-8"))
454
+ except (OSError, ValueError) as exc:
455
+ raise CellError(
456
+ f"{where} 'allowlisted' needs the swarph marketplace, "
457
+ f"but its registry is unreadable: {registry} ({exc})."
458
+ ) from exc
459
+ try:
460
+ location = data["swarph"]["installLocation"]
461
+ except (KeyError, TypeError) as exc:
462
+ raise CellError(
463
+ f"{where} 'allowlisted' needs the swarph marketplace, "
464
+ f"but it is not registered in {registry}."
465
+ ) from exc
466
+ path = Path(location)
467
+ if not path.is_dir():
468
+ raise CellError(
469
+ f"{where} 'allowlisted' needs the swarph marketplace "
470
+ f"at {path}, which is not a directory."
471
+ )
472
+ return path
473
+
474
+
475
+ def _validate_channel(cell: Cell, mode: str, source: str = "cell.yaml") -> str:
476
+ """Fail closed on any channel mode the provider cannot consume.
477
+
478
+ Returns the mode unchanged. "" and "off" are valid everywhere (no-op).
479
+ Raises CellError naming the source (``SWARPH_CHANNEL`` or the cell.yaml
480
+ field) and the file, for a missing marketplace, so the operator fixes
481
+ config, never debugs a deaf cell.
482
+ """
483
+ if mode in ("", "off"):
484
+ return mode
485
+ where = _channel_where(source)
486
+ provider = cell.provider
487
+ if provider == "claude":
488
+ if mode not in _CLAUDE_CHANNELS:
489
+ raise CellError(
490
+ f"{where} {mode!r} is not valid for provider "
491
+ f"'claude'. Valid values: {sorted(_CLAUDE_CHANNELS | {'off'})}."
492
+ )
493
+ if mode == "allowlisted":
494
+ _channel_marketplace_dir(source)
495
+ return mode
496
+ if provider == "muse":
497
+ if mode not in _MUSE_CHANNELS:
498
+ raise CellError(
499
+ f"{where} {mode!r} is not valid for provider "
500
+ f"'muse'. Valid values: {sorted(_MUSE_CHANNELS | {'off'})}."
501
+ )
502
+ return mode
503
+ raise CellError(
504
+ f"{where} {mode!r} is only valid for providers "
505
+ f"'claude' and 'muse'; provider is {provider!r}."
506
+ )
507
+
508
+
380
509
  def _build_claude_argv(
381
510
  cell: Cell,
382
511
  session_id: str,
@@ -400,7 +529,8 @@ def _build_claude_argv(
400
529
  argv.extend(["--append-system-prompt", starter])
401
530
 
402
531
  argv.extend(passthrough)
403
- mode = os.environ.get("SWARPH_CHANNEL") or ""
532
+ mode, source = _resolve_channel(cell)
533
+ mode = _validate_channel(cell, mode, source)
404
534
  if mode == "allowlisted":
405
535
  argv.extend(["--channels", "plugin:swarph@swarph"])
406
536
  elif mode == "dev":
@@ -447,7 +577,10 @@ def _spawn_env_base(cell: Cell) -> dict[str, str]:
447
577
  env = scrub_env_for_subprocess()
448
578
  env["SWARPH_SPAWN"] = "1"
449
579
  env["SWARPH_SELF"] = cell.name
450
- mode = os.environ.get("SWARPH_CHANNEL") or ""
580
+ mode, _source = _resolve_channel(cell)
581
+ # NOTE: the marketplace/validity gate lives in _validate_channel, which
582
+ # _build_claude_argv (and the muse env builder) run. This base stays a
583
+ # pure reflection of the resolved mode so every provider's env agrees.
451
584
  if mode in {"allowlisted", "dev"}:
452
585
  env["SWARPH_CHANNEL_CELL"] = cell.name
453
586
  else:
@@ -475,6 +608,23 @@ def _claude_env(cell: Cell) -> dict[str, str]:
475
608
  return env
476
609
 
477
610
 
611
+ def _muse_env(cell: Cell) -> dict[str, str]:
612
+ """Launch env for an interactive ``muse`` session.
613
+
614
+ Claude billing scrub + identity stamp (shared), plus the external-agent
615
+ ingress gate when the cell takes the `ingress` channel. The gate must be
616
+ in the environment AT SESSION LAUNCH — it cannot be enabled mid-session
617
+ (measured on card #961: list/send without it reads "external agent
618
+ ingress is unavailable"). `_validate_channel` already ran at the
619
+ run_spawn choke point; the mode read here agrees with it by construction.
620
+ """
621
+ env = _claude_env(cell)
622
+ mode, _source = _resolve_channel(cell)
623
+ if mode == "ingress":
624
+ env["MUSE_EXPERIMENTAL_EXTERNAL_AGENT_INGRESS"] = "on"
625
+ return env
626
+
627
+
478
628
  def _agy_env(cell: Cell) -> dict[str, str]:
479
629
  """Subscription-billing env for an ``agy`` (antigravity/Gemini) session.
480
630
 
@@ -2706,6 +2856,8 @@ class MuseMembrane(ClaudeMembrane):
2706
2856
 
2707
2857
  name = "muse"
2708
2858
 
2859
+ env_builder = staticmethod(_muse_env)
2860
+
2709
2861
  def apply_task_injection(self, cell: Cell, argv: list[str], text: str) -> None:
2710
2862
  # muse has no --append-system-prompt; the prompt is POSITIONAL. Appended
2711
2863
  # only when this is a FRESH session — `muse resume` takes no prompt, so
@@ -3184,6 +3336,17 @@ def run_spawn(argv: Optional[list[str]] = None) -> int:
3184
3336
  was_generated = False
3185
3337
  effective_role: Optional[str] = None
3186
3338
 
3339
+ # Channel gate BEFORE session minting: an invalid channel mode must fail
3340
+ # before load_or_create_session_id persists anything (a refused spawn
3341
+ # must not leave a minted sidecar behind). One choke point for every
3342
+ # provider; builders below see only validated modes.
3343
+ try:
3344
+ mode, source = _resolve_channel(cell)
3345
+ _validate_channel(cell, mode, source)
3346
+ except CellError as exc:
3347
+ print(f"swarph spawn: {exc}", file=sys.stderr)
3348
+ return 1
3349
+
3187
3350
  if membrane.uses_pinned_session():
3188
3351
  # When user typed a slot-role (e.g. `swarph spawn drop-on-meta-edge-2`)
3189
3352
  # the cell.yaml resolved to the BASE file (drop-on-meta-edge.yaml) so
@@ -3265,6 +3428,18 @@ def run_spawn(argv: Optional[list[str]] = None) -> int:
3265
3428
  )
3266
3429
  return 0
3267
3430
 
3431
+ if args.print_resolved:
3432
+ # Resolved-launch capture: argv + the env keys swarph adds, as JSON
3433
+ # on stdout, then exit BEFORE resolve_binary — no binary needed, no
3434
+ # session started. Banner stays on stderr so stdout parses clean.
3435
+ # env_added is a DIFF against the parent env (not the full env) so
3436
+ # no secret the operator exported leaks into the capture.
3437
+ built_env = membrane.spawn_env(cell)
3438
+ added = {k: v for k, v in built_env.items()
3439
+ if os.environ.get(k) != v}
3440
+ print(json.dumps({"argv": spawn_argv, "env_added": added}))
3441
+ return 0
3442
+
3268
3443
  provider_bin = membrane.resolve_binary()
3269
3444
  if provider_bin is None:
3270
3445
  print(membrane.binary_not_found_message(), file=sys.stderr)