swarph-cli 0.71.0__tar.gz → 0.72.0__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.0}/PKG-INFO +1 -1
  2. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/pyproject.toml +1 -1
  3. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/mesh.py +229 -7
  5. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/monitor.py +1 -1
  6. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/spawn.py +144 -3
  7. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/stall_alert.py +11 -3
  8. {swarph_cli-0.71.0 → swarph_cli-0.72.0/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  9. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
  10. swarph_cli-0.72.0/tests/test_1013_spawn_channel.py +177 -0
  11. swarph_cli-0.72.0/tests/test_1017_muse_tui.py +113 -0
  12. swarph_cli-0.72.0/tests/test_484_codex_queue_sink.py +149 -0
  13. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_960_channel.py +14 -2
  14. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_state_split.py +6 -2
  15. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_stall_alert.py +13 -2
  16. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/LICENSE +0 -0
  17. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/README.md +0 -0
  18. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/setup.cfg +0 -0
  19. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/__main__.py +0 -0
  20. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/__init__.py +0 -0
  21. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/backends.py +0 -0
  22. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/claude_cli.py +0 -0
  23. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/pack.py +0 -0
  24. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/prices.py +0 -0
  25. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/providers.py +0 -0
  26. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/quality.py +0 -0
  27. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/refresh_prices.py +0 -0
  28. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/rules.py +0 -0
  29. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/runner.py +0 -0
  30. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/signal_score.py +0 -0
  31. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/bench/validate.py +0 -0
  32. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/caller.py +0 -0
  33. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/__init__.py +0 -0
  34. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/harden.py +0 -0
  35. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/lineage.py +0 -0
  36. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/liveness.py +0 -0
  37. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/manifest.py +0 -0
  38. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/paths.py +0 -0
  39. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/capture/verify.py +0 -0
  40. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/cell.py +0 -0
  41. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/chain_token.py +0 -0
  42. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/channel.py +0 -0
  43. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/__init__.py +0 -0
  44. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/_content.py +0 -0
  45. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/_display.py +0 -0
  46. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/_gateway_client.py +0 -0
  47. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/add.py +0 -0
  48. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/bench.py +0 -0
  49. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/board.py +0 -0
  50. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/board_merge_check.py +0 -0
  51. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/brain.py +0 -0
  52. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/brain_ask.py +0 -0
  53. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/cell.py +0 -0
  54. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/cell_probe.py +0 -0
  55. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  56. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/channel.py +0 -0
  57. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/chat.py +0 -0
  58. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/codegraph.py +0 -0
  59. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  60. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  61. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/codex_waker.py +0 -0
  62. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/compress.py +0 -0
  63. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/daemon.py +0 -0
  64. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/dreaming.py +0 -0
  65. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/event.py +0 -0
  66. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/gateway.py +0 -0
  67. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/gh_route.py +0 -0
  68. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/group.py +0 -0
  69. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/guide.py +0 -0
  70. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/highlight.py +0 -0
  71. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  72. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/hook_output.py +0 -0
  73. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/hooks.py +0 -0
  74. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/import_session.py +0 -0
  75. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/init.py +0 -0
  76. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  77. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_hook.py +0 -0
  78. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  79. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  80. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  81. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  82. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/lane.py +0 -0
  83. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/mcp_server.py +0 -0
  84. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/memory.py +0 -0
  85. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  86. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/memory_sync.py +0 -0
  87. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/okf_links.py +0 -0
  88. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/onboard.py +0 -0
  89. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  90. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  91. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/protocol_handler.py +0 -0
  92. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/ratify.py +0 -0
  93. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/schedule.py +0 -0
  94. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/security.py +0 -0
  95. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/service.py +0 -0
  96. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/timeline.py +0 -0
  97. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/version.py +0 -0
  98. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  99. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/commands/watchdog.py +0 -0
  100. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/compress/__init__.py +0 -0
  101. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/compress/levers.py +0 -0
  102. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/compress/marker.py +0 -0
  103. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/compress/verify.py +0 -0
  104. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/console_safe.py +0 -0
  105. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/delivery_queue.py +0 -0
  106. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dm_frame.py +0 -0
  107. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/__init__.py +0 -0
  108. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/candidates.py +0 -0
  109. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/clone.py +0 -0
  110. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/enrich.py +0 -0
  111. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/index_check.py +0 -0
  112. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/notify.py +0 -0
  113. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/organize.py +0 -0
  114. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/probes.py +0 -0
  115. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/report.py +0 -0
  116. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/run.py +0 -0
  117. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/slm.py +0 -0
  118. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/transcripts.py +0 -0
  119. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/dreaming/verify.py +0 -0
  120. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/__init__.py +0 -0
  121. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/feature_registry.py +0 -0
  122. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/lanes_control.py +0 -0
  123. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/schema.sql +0 -0
  124. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/server.py +0 -0
  125. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway/services_control.py +0 -0
  126. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gateway_default.py +0 -0
  127. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/gh_identity.py +0 -0
  128. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/guide/GUIDE.md +0 -0
  129. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/identity.py +0 -0
  130. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/main.py +0 -0
  131. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/multiplexer.py +0 -0
  132. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/__init__.py +0 -0
  133. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/__main__.py +0 -0
  134. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/api.py +0 -0
  135. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/cli.py +0 -0
  136. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/config.py +0 -0
  137. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/operator/fmt.py +0 -0
  138. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/orphan_daemons.py +0 -0
  139. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/pane_probe.py +0 -0
  140. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/parsers/__init__.py +0 -0
  141. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/parsers/claude.py +0 -0
  142. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  143. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  144. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  145. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  146. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  147. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  148. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  149. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  150. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  151. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  152. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  153. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  154. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_executor.py +0 -0
  155. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_executor_windows.py +0 -0
  156. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_reconciliation.py +0 -0
  157. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_reply_outbox.py +0 -0
  158. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_service_host.py +0 -0
  159. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_service_payload.py +0 -0
  160. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_service_reply.py +0 -0
  161. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/peer_staging.py +0 -0
  162. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/query_filters.py +0 -0
  163. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/__init__.py +0 -0
  164. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  165. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  166. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  167. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  168. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  169. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/service/__init__.py +0 -0
  170. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/service/app.py +0 -0
  171. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/service/providers.py +0 -0
  172. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/session_bridge.py +0 -0
  173. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  174. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  175. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/source_text.py +0 -0
  176. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/codex-waker.default +0 -0
  177. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  178. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  179. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  180. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  181. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  182. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  183. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  184. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  185. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  186. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  187. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  188. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  189. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  190. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/timeline_paths.py +0 -0
  191. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli/tokens.py +0 -0
  192. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  193. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  194. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/requires.txt +0 -0
  195. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/src/swarph_cli.egg-info/top_level.txt +0 -0
  196. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_1006_board_publisher.py +0 -0
  197. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_1006_session_board.py +0 -0
  198. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_124_register_merge.py +0 -0
  199. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_126_pending_from_complete.py +0 -0
  200. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  201. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_138_memory_navigate.py +0 -0
  202. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_139_tool_probe.py +0 -0
  203. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_183_card_anchor.py +0 -0
  204. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_184_notify_honesty.py +0 -0
  205. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_201_win32_bash_refusal.py +0 -0
  206. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_243_onboard_per_peer_token.py +0 -0
  207. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_307_task4_ask_reply.py +0 -0
  208. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  209. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_319_refusal_exit_codes.py +0 -0
  210. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_323_mistral_backend.py +0 -0
  211. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_332_report_organize_findings.py +0 -0
  212. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_356_query_filters.py +0 -0
  213. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_397_gh_identity_router.py +0 -0
  214. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_397_router_preflight.py +0 -0
  215. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_402_one_identity_resolver.py +0 -0
  216. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_467b_onboard_operator_credential.py +0 -0
  217. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_472_register_caller_binding.py +0 -0
  218. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_523_guide.py +0 -0
  219. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_524_conftest_hermeticity.py +0 -0
  220. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_532_accept_flag.py +0 -0
  221. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_535_version_capability.py +0 -0
  222. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_538_signal_bench.py +0 -0
  223. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_546_default_gateway.py +0 -0
  224. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_547_verbs_from_registry.py +0 -0
  225. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_548_gateway_remainder.py +0 -0
  226. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_552_codegraph_gateway.py +0 -0
  227. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_555_relay_row_shape.py +0 -0
  228. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_556_relay_freshness.py +0 -0
  229. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_559_edges_truncated.py +0 -0
  230. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_564_bootstrap_first_peer.py +0 -0
  231. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  232. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_579_register_bootstrap_message.py +0 -0
  233. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_591_cli_step_graph.py +0 -0
  234. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_644_monitor_supervision.py +0 -0
  235. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_648_install_unit.py +0 -0
  236. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_654_reexec.py +0 -0
  237. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_656_owned_slm.py +0 -0
  238. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_684_dreaming_verb.py +0 -0
  239. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_689_pkg_version_order.py +0 -0
  240. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_715_grok_wake.py +0 -0
  241. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_716_timeline_one_path.py +0 -0
  242. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_723_grok_0251.py +0 -0
  243. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  244. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_725_stdio_reconfigure.py +0 -0
  245. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_726_grok_limit_screen.py +0 -0
  246. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_729_arm_check_delivery_gap.py +0 -0
  247. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_729_dm_once.py +0 -0
  248. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_729_wake_watchdog.py +0 -0
  249. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  250. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_735_dm_mcp_tools.py +0 -0
  251. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_739_wake_writer.py +0 -0
  252. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_740_edit_project.py +0 -0
  253. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_762_cards_edit_priority.py +0 -0
  254. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_764_enrich_collapse.py +0 -0
  255. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_801_confirm_flow_cli.py +0 -0
  256. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_802_stage_history.py +0 -0
  257. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_825_codegraph_hook_noise.py +0 -0
  258. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_825_install_posttooluse_only.py +0 -0
  259. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  260. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_829_counterfactual_column.py +0 -0
  261. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_830_hook_binding_version.py +0 -0
  262. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_84_cli_labels.py +0 -0
  263. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_866_doctrine_guide.py +0 -0
  264. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_872_identity_refuses_not_warns.py +0 -0
  265. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_874_source_text.py +0 -0
  266. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  267. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_937_dreaming_notify.py +0 -0
  268. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_938_organize_line_limit.py +0 -0
  269. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_941_bench_backends.py +0 -0
  270. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_941_harness_342.py +0 -0
  271. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_945_same_line_pairing.py +0 -0
  272. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_955_prompt_audit.py +0 -0
  273. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_957_auth_http.py +0 -0
  274. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_959_venv_interpreter.py +0 -0
  275. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
  276. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_961_cursor_grok_footer.py +0 -0
  277. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_963_plugin_manifest.py +0 -0
  278. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_964_channel_cell.py +0 -0
  279. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_965_mesh_wait.py +0 -0
  280. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_975_channel_hop.py +0 -0
  281. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_add_security_gate.py +0 -0
  282. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_add.py +0 -0
  283. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_hash.py +0 -0
  284. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_lib.py +0 -0
  285. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_mcp.py +0 -0
  286. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_skill.py +0 -0
  287. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_tool.py +0 -0
  288. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_artifact_uri.py +0 -0
  289. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_backends.py +0 -0
  290. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_claude_cli.py +0 -0
  291. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_command.py +0 -0
  292. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_pack.py +0 -0
  293. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_prices.py +0 -0
  294. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_quality.py +0 -0
  295. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_runner.py +0 -0
  296. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_bench_validate.py +0 -0
  297. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_board_card_thread.py +0 -0
  298. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_board_command.py +0 -0
  299. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_board_merge_check.py +0 -0
  300. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_board_obligation_close.py +0 -0
  301. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_brain_ask_command.py +0 -0
  302. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_brain_ask_env_diagnosis.py +0 -0
  303. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_brain_ask_identity_explicitness.py +0 -0
  304. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_brain_command.py +0 -0
  305. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_caller_meta_guard.py +0 -0
  306. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_lineage.py +0 -0
  307. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_liveness.py +0 -0
  308. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_manifest.py +0 -0
  309. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_paths.py +0 -0
  310. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_capture_security.py +0 -0
  311. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_command_dispatch.py +0 -0
  312. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_harden.py +0 -0
  313. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_loader.py +0 -0
  314. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_selfcheck.py +0 -0
  315. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cell_verify.py +0 -0
  316. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_chain_token.py +0 -0
  317. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_channel_command.py +0 -0
  318. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  319. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_channel_post.py +0 -0
  320. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_channel_wake_policy_filter.py +0 -0
  321. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_chat_command.py +0 -0
  322. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_claude_parser.py +0 -0
  323. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_claude_tmux_template.py +0 -0
  324. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codegraph_caller_scope.py +0 -0
  325. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codegraph_command.py +0 -0
  326. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codegraph_hook.py +0 -0
  327. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codegraph_mcp.py +0 -0
  328. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_codex_waker.py +0 -0
  329. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_caller_convention.py +0 -0
  330. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_command.py +0 -0
  331. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_levers.py +0 -0
  332. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_marker.py +0 -0
  333. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_compress_verify.py +0 -0
  334. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_console_encoding_e2e.py +0 -0
  335. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_content_composition.py +0 -0
  336. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_cursor_membrane.py +0 -0
  337. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_daemon_command.py +0 -0
  338. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_daemon_console_encoding.py +0 -0
  339. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_daemon_delivery.py +0 -0
  340. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_delivery_queue.py +0 -0
  341. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_display_sanitize.py +0 -0
  342. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  343. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_event_emit.py +0 -0
  344. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_feature_to_uri.py +0 -0
  345. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gateway_brain.py +0 -0
  346. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gateway_client.py +0 -0
  347. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gateway_command.py +0 -0
  348. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gateway_message_idempotency.py +0 -0
  349. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  350. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_group_command.py +0 -0
  351. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_guide_commands_parse.py +0 -0
  352. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_guide_selfcheck.py +0 -0
  353. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_heartbeat_check.py +0 -0
  354. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_highlight_command.py +0 -0
  355. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hook_output.py +0 -0
  356. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_activity_marker.py +0 -0
  357. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_add.py +0 -0
  358. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_bundle.py +0 -0
  359. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_init.py +0 -0
  360. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_legacy_generation_removal.py +0 -0
  361. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_lifecycle.py +0 -0
  362. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_merge.py +0 -0
  363. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_verify.py +0 -0
  364. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  365. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_hooks_win32_command_path.py +0 -0
  366. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_import_command.py +0 -0
  367. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_inbox_identity_provenance.py +0 -0
  368. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_init_command.py +0 -0
  369. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_init_provider_menu.py +0 -0
  370. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_codex_hooks.py +0 -0
  371. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_hook.py +0 -0
  372. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_multiplexer_command.py +0 -0
  373. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_postcompact_hook.py +0 -0
  374. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_install_wake_hook.py +0 -0
  375. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_lane_command.py +0 -0
  376. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_main.py +0 -0
  377. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_main_unknown_verb_guard.py +0 -0
  378. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mcp_missing_hint.py +0 -0
  379. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mcp_server.py +0 -0
  380. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_command.py +0 -0
  381. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_emit_snippet.py +0 -0
  382. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_gateway.py +0 -0
  383. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_sync.py +0 -0
  384. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_memory_sync_membrane.py +0 -0
  385. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_command.py +0 -0
  386. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_inbox_mark_read.py +0 -0
  387. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_inbox_sanitize.py +0 -0
  388. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_originate_thread_623.py +0 -0
  389. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_send_unknown_peer.py +0 -0
  390. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_sidecar.py +0 -0
  391. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  392. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_sidecar_muse_sink.py +0 -0
  393. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  394. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_meta_edge_identity.py +0 -0
  395. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_channel_poll.py +0 -0
  396. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_cli.py +0 -0
  397. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_daemon_collision.py +0 -0
  398. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_monitor_self_name_verification.py +0 -0
  399. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_multiplexer.py +0 -0
  400. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_muse_membrane.py +0 -0
  401. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_muse_own_cli.py +0 -0
  402. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_obligation_close_is_surfaced.py +0 -0
  403. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_okf_links.py +0 -0
  404. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_onboard_command.py +0 -0
  405. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_opencode_membrane.py +0 -0
  406. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_opencode_plugin.py +0 -0
  407. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_operator_me.py +0 -0
  408. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_orphan_daemons.py +0 -0
  409. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_packaged_artifacts_exist.py +0 -0
  410. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_packaged_skills.py +0 -0
  411. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_executor.py +0 -0
  412. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_executor_windows.py +0 -0
  413. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_reply_drain_command.py +0 -0
  414. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_reply_drain_supervisor.py +0 -0
  415. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_reply_outbox.py +0 -0
  416. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_reply_transport.py +0 -0
  417. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_service_e2e.py +0 -0
  418. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_service_host.py +0 -0
  419. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_service_payload.py +0 -0
  420. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_peer_service_reply.py +0 -0
  421. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_postcompact_recall.py +0 -0
  422. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_process_liveness.py +0 -0
  423. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_protocol_handler.py +0 -0
  424. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_providers_isolation.py +0 -0
  425. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_ratify_command.py +0 -0
  426. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_readme_claims_no_gateway_default.py +0 -0
  427. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_repl_caller_convention.py +0 -0
  428. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_schedule_command.py +0 -0
  429. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_schedule_create_contract.py +0 -0
  430. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_security.py +0 -0
  431. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_service_app.py +0 -0
  432. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_service_command.py +0 -0
  433. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_service_providers.py +0 -0
  434. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_session_bridge_inject.py +0 -0
  435. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_session_bridge_probe.py +0 -0
  436. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_session_bridge_resolve.py +0 -0
  437. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_session_path_role_gate.py +0 -0
  438. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_shipped_units_match_the_cli.py +0 -0
  439. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_smoke_chat.py +0 -0
  440. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_smoke_one_shot.py +0 -0
  441. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_smoke_phase_5_5.py +0 -0
  442. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_base_hoist.py +0 -0
  443. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_command.py +0 -0
  444. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_identity_stamp.py +0 -0
  445. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_live_pin.py +0 -0
  446. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_mitosis_lineage.py +0 -0
  447. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_tmux_session.py +0 -0
  448. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_spawn_windows_relaunch.py +0 -0
  449. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_subprocess_decode_encoding.py +0 -0
  450. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_timeline_command.py +0 -0
  451. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_tmux_wake_runstate_defer.py +0 -0
  452. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_tmux_wake_session_reanchor.py +0 -0
  453. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_tmux_wake_stale.py +0 -0
  454. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_tmux_wake_submit_verify.py +0 -0
  455. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_token_file_one_parser.py +0 -0
  456. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_token_precedence_explicit_first.py +0 -0
  457. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_unified_token_resolver.py +0 -0
  458. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_version_command.py +0 -0
  459. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_version_consistency.py +0 -0
  460. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_vibe_membrane.py +0 -0
  461. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog.py +0 -0
  462. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_dm_wake.py +0 -0
  463. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  464. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  465. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_emit_health.py +0 -0
  466. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_install_flags.py +0 -0
  467. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_model_rung.py +0 -0
  468. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/tests/test_watchdog_stale_peers.py +0 -0
  469. {swarph_cli-0.71.0 → swarph_cli-0.72.0}/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.0
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.0"
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.0"
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
 
@@ -2475,12 +2604,38 @@ def _is_cursor_composer_row(line: str) -> bool:
2475
2604
  return row.startswith("→") and not row.startswith("▎")
2476
2605
 
2477
2606
 
2607
+ def _is_muse_rule(line: str) -> bool:
2608
+ """A muse composer rule is a run of ``─``, measured on 1.4.1."""
2609
+ s = line.strip()
2610
+ return len(s) >= 8 and "─" in s and set(s) <= {"─"}
2611
+
2612
+
2613
+ def _is_muse_status(line: str) -> bool:
2614
+ """The status under the lower rule. Measured: ``muse-spark-… · … · YOLO``."""
2615
+ return "muse-spark" in line and "·" in line
2616
+
2617
+
2618
+ def _is_muse_composer_row(lines: list[str], index: int) -> bool:
2619
+ """The live muse composer is a ``❯`` row between two rules, with the
2620
+ model status under the lower rule. A transcript ``❯`` above that box
2621
+ has no rule on both sides, so it is history."""
2622
+ row = lines[index].strip()
2623
+ if not row.startswith("❯"):
2624
+ return False
2625
+ if index == 0 or index + 1 >= len(lines):
2626
+ return False
2627
+ if not (_is_muse_rule(lines[index - 1]) and _is_muse_rule(lines[index + 1])):
2628
+ return False
2629
+ return any(_is_muse_status(ln) for ln in lines[index + 2:index + 6])
2630
+
2631
+
2478
2632
  def _bottom_tui(lines: list[str]) -> "str | None":
2479
2633
  """Who owns the bottom-most composer row in the non-empty tail.
2480
2634
 
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.
2635
+ ``"cursor"``, ``"grok"``, ``"opencode"``, or ``"muse"``. A match above
2636
+ that tail is scrollback. The lower composer wins, so a quoted grok box
2637
+ or a quoted ``┃ Build ·`` row above the cursor composer does not take
2638
+ the pane.
2484
2639
  """
2485
2640
  tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
2486
2641
  for i in range(len(tail) - 1, -1, -1):
@@ -2490,12 +2645,15 @@ def _bottom_tui(lines: list[str]) -> "str | None":
2490
2645
  return "grok"
2491
2646
  if _is_opencode_composer_row(tail[i]):
2492
2647
  return "opencode"
2648
+ if _is_muse_composer_row(tail, i):
2649
+ return "muse"
2493
2650
  # A permission dialog is taller than the composer tail. Look further
2494
2651
  # for that one row. A mode row or a cursor/grok composer in the same
2495
2652
  # stretch is a quote, not a reason to take the pane.
2496
2653
  wider = _nonempty_tail(lines, _OPENCODE_PERMISSION_TAIL)
2497
2654
  for i in range(len(wider) - 1, -1, -1):
2498
- if _is_cursor_composer_row(wider[i]) or _is_grok_composer_row(wider, i):
2655
+ if (_is_cursor_composer_row(wider[i]) or _is_grok_composer_row(wider, i)
2656
+ or _is_muse_composer_row(wider, i)):
2499
2657
  return None
2500
2658
  if not _is_opencode_composer_row(wider[i]):
2501
2659
  continue
@@ -2645,6 +2803,51 @@ def _grok_composer_state(lines: list[str]) -> Optional[str]:
2645
2803
  return "busy"
2646
2804
 
2647
2805
 
2806
+ def _is_muse_pane(lines: list[str]) -> bool:
2807
+ """Muse 1.4.1: the ``❯`` row between two rules is the bottom composer."""
2808
+ return bool(lines) and _bottom_tui(lines) == "muse"
2809
+
2810
+
2811
+ def _muse_input(lines: list[str]) -> Optional[str]:
2812
+ """Text after the live ``❯``, '' when that row is empty, None if the
2813
+ bottom composer is not muse. A transcript ``❯`` above the rules is not
2814
+ this row."""
2815
+ tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
2816
+ for i in range(len(tail) - 1, -1, -1):
2817
+ if (_is_cursor_composer_row(tail[i]) or _is_grok_composer_row(tail, i)
2818
+ or _is_opencode_composer_row(tail[i])):
2819
+ return None
2820
+ if _is_muse_composer_row(tail, i):
2821
+ return tail[i].strip()[1:].strip()
2822
+ return None
2823
+
2824
+
2825
+ def _muse_composer_state(lines: list[str]) -> Optional[str]:
2826
+ text = _muse_input(lines)
2827
+ if text is None:
2828
+ return None
2829
+ if not text:
2830
+ return "clear"
2831
+ if _only_wake_text(text):
2832
+ return "wake"
2833
+ return "busy"
2834
+
2835
+
2836
+ def _muse_running(lines: list[str]) -> bool:
2837
+ """A muse turn in progress. 1.4.1 draws ``esc to interrupt`` on the
2838
+ status row above the composer (``Double checking (Ns · esc to interrupt)``).
2839
+ The card also names a ``Thinking`` row; either one means a keystroke
2840
+ would land in the turn, not the idle composer. The phrase leaves the
2841
+ visible screen when the turn ends."""
2842
+ if not _is_muse_pane(lines):
2843
+ return False
2844
+ for ln in lines:
2845
+ s = ln.strip()
2846
+ if "esc to interrupt" in s or s == "Thinking" or s.startswith("Thinking "):
2847
+ return True
2848
+ return False
2849
+
2850
+
2648
2851
  def _wake_prompt_for(lines: Optional[list[str]]) -> str:
2649
2852
  if lines and _is_grok_pane(lines):
2650
2853
  return _GROK_WAKE_PROMPT
@@ -2653,6 +2856,19 @@ def _wake_prompt_for(lines: Optional[list[str]]) -> str:
2653
2856
  return _WAKE_PROMPT
2654
2857
 
2655
2858
 
2859
+ def _sink_failure_detail(target: str) -> str:
2860
+ """Why a delivery returned False.
2861
+
2862
+ A captured pane with no known composer is an unrecognised TUI. A missing
2863
+ pane stays the old 'probably gone' line. Callers print this phrase as
2864
+ the failure line.
2865
+ """
2866
+ lines = _capture_pane_lines(target)
2867
+ if lines and _bottom_tui(lines) is None and _composer_line(lines) is None:
2868
+ return "unrecognised TUI"
2869
+ return "the sink is probably gone (session restart / renamed target)"
2870
+
2871
+
2656
2872
  def _agent_running(target: str) -> Optional[bool]:
2657
2873
  """True while a keystroke would miss the idle composer.
2658
2874
 
@@ -2673,6 +2889,8 @@ def _agent_running(target: str) -> Optional[bool]:
2673
2889
  return _opencode_running(lines)
2674
2890
  if _is_grok_pane(lines):
2675
2891
  return _grok_running(lines)
2892
+ if _is_muse_pane(lines):
2893
+ return _muse_running(lines)
2676
2894
  tail = _nonempty_tail(lines, _CURSOR_COMPOSER_TAIL)
2677
2895
  composer = _composer_line(tail)
2678
2896
  if composer is None or not composer.startswith("→"):
@@ -2721,6 +2939,8 @@ def _composer_state(target: str) -> Optional[str]:
2721
2939
  return _opencode_composer_state(lines)
2722
2940
  if _is_grok_pane(lines):
2723
2941
  return _grok_composer_state(lines)
2942
+ if _is_muse_pane(lines):
2943
+ return _muse_composer_state(lines)
2724
2944
  composer = _composer_line(lines)
2725
2945
  if composer is not None and composer.startswith("→"):
2726
2946
  # A cursor marker above the bottom tail is a quoted row. The
@@ -3125,10 +3345,12 @@ def _monitor_deliver(state: MonitorState) -> None:
3125
3345
  else:
3126
3346
  # A dead sink is VISIBLE instead of silently freezing anything.
3127
3347
  led["consecutive_failures"] = int(led["consecutive_failures"]) + 1
3348
+ detail = getattr(sink, "failure_reason", None) or (
3349
+ "the sink is probably gone (session restart / renamed target)"
3350
+ )
3128
3351
  print(f"{state.log_prefix} DELIVERY FAILED to {sink.name} "
3129
3352
  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)",
3353
+ f"id {observed} were still observed and archived; {detail}",
3132
3354
  file=sys.stderr, flush=True)
3133
3355
  changed = True
3134
3356
 
@@ -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,95 @@ 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
+ def _resolve_channel(cell: Cell) -> str:
397
+ """Effective channel mode: $SWARPH_CHANNEL wins (incl. "off"), else the
398
+ cell.yaml `channel` field (kept by swarph-shared in the forward-compat
399
+ `extra` bag), else "" (absent). Never launches anything; callers validate.
400
+ """
401
+ env_mode = os.environ.get("SWARPH_CHANNEL") or ""
402
+ if env_mode:
403
+ return env_mode
404
+ extra = getattr(cell, "extra", None) or {}
405
+ value = extra.get("channel", "") if isinstance(extra, dict) else ""
406
+ if value is None:
407
+ return ""
408
+ if not isinstance(value, str):
409
+ raise CellError(
410
+ f"cell.yaml: 'channel' must be a string; got "
411
+ f"{type(value).__name__}."
412
+ )
413
+ return value
414
+
415
+
416
+ def _channel_marketplace_dir() -> Path:
417
+ """Install location of the `swarph` Claude marketplace (the allowlist).
418
+
419
+ Resolved through ~/.claude/plugins/known_marketplaces.json, the same
420
+ registry claude reads — never a second hardcoded copy that drifts.
421
+ """
422
+ registry = Path.home() / ".claude" / "plugins" / "known_marketplaces.json"
423
+ try:
424
+ data = json.loads(registry.read_text(encoding="utf-8"))
425
+ except (OSError, ValueError) as exc:
426
+ raise CellError(
427
+ f"cell.yaml: channel 'allowlisted' needs the swarph marketplace, "
428
+ f"but its registry is unreadable: {registry} ({exc})."
429
+ ) from exc
430
+ try:
431
+ location = data["swarph"]["installLocation"]
432
+ except (KeyError, TypeError) as exc:
433
+ raise CellError(
434
+ f"cell.yaml: channel 'allowlisted' needs the swarph marketplace, "
435
+ f"but it is not registered in {registry}."
436
+ ) from exc
437
+ path = Path(location)
438
+ if not path.is_dir():
439
+ raise CellError(
440
+ f"cell.yaml: channel 'allowlisted' needs the swarph marketplace "
441
+ f"at {path}, which is not a directory."
442
+ )
443
+ return path
444
+
445
+
446
+ def _validate_channel(cell: Cell, mode: str) -> str:
447
+ """Fail closed on any channel mode the provider cannot consume.
448
+
449
+ Returns the mode unchanged. "" and "off" are valid everywhere (no-op).
450
+ Raises CellError naming the field (and the file, for a missing
451
+ marketplace) so the operator fixes config, never debugs a deaf cell.
452
+ """
453
+ if mode in ("", "off"):
454
+ return mode
455
+ provider = cell.provider
456
+ if provider == "claude":
457
+ if mode not in _CLAUDE_CHANNELS:
458
+ raise CellError(
459
+ f"cell.yaml: 'channel' {mode!r} is not valid for provider "
460
+ f"'claude'. Valid values: {sorted(_CLAUDE_CHANNELS | {'off'})}."
461
+ )
462
+ if mode == "allowlisted":
463
+ _channel_marketplace_dir()
464
+ return mode
465
+ if provider == "muse":
466
+ if mode not in _MUSE_CHANNELS:
467
+ raise CellError(
468
+ f"cell.yaml: 'channel' {mode!r} is not valid for provider "
469
+ f"'muse'. Valid values: {sorted(_MUSE_CHANNELS | {'off'})}."
470
+ )
471
+ return mode
472
+ raise CellError(
473
+ f"cell.yaml: 'channel' {mode!r} is only valid for providers "
474
+ f"'claude' and 'muse'; provider is {provider!r}."
475
+ )
476
+
477
+
380
478
  def _build_claude_argv(
381
479
  cell: Cell,
382
480
  session_id: str,
@@ -400,7 +498,7 @@ def _build_claude_argv(
400
498
  argv.extend(["--append-system-prompt", starter])
401
499
 
402
500
  argv.extend(passthrough)
403
- mode = os.environ.get("SWARPH_CHANNEL") or ""
501
+ mode = _validate_channel(cell, _resolve_channel(cell))
404
502
  if mode == "allowlisted":
405
503
  argv.extend(["--channels", "plugin:swarph@swarph"])
406
504
  elif mode == "dev":
@@ -447,7 +545,10 @@ def _spawn_env_base(cell: Cell) -> dict[str, str]:
447
545
  env = scrub_env_for_subprocess()
448
546
  env["SWARPH_SPAWN"] = "1"
449
547
  env["SWARPH_SELF"] = cell.name
450
- mode = os.environ.get("SWARPH_CHANNEL") or ""
548
+ mode = _resolve_channel(cell)
549
+ # NOTE: the marketplace/validity gate lives in _validate_channel, which
550
+ # _build_claude_argv (and the muse env builder) run. This base stays a
551
+ # pure reflection of the resolved mode so every provider's env agrees.
451
552
  if mode in {"allowlisted", "dev"}:
452
553
  env["SWARPH_CHANNEL_CELL"] = cell.name
453
554
  else:
@@ -475,6 +576,22 @@ def _claude_env(cell: Cell) -> dict[str, str]:
475
576
  return env
476
577
 
477
578
 
579
+ def _muse_env(cell: Cell) -> dict[str, str]:
580
+ """Launch env for an interactive ``muse`` session.
581
+
582
+ Claude billing scrub + identity stamp (shared), plus the external-agent
583
+ ingress gate when the cell takes the `ingress` channel. The gate must be
584
+ in the environment AT SESSION LAUNCH — it cannot be enabled mid-session
585
+ (measured on card #961: list/send without it reads "external agent
586
+ ingress is unavailable"). `_validate_channel` already ran at the
587
+ run_spawn choke point; the mode read here agrees with it by construction.
588
+ """
589
+ env = _claude_env(cell)
590
+ if _resolve_channel(cell) == "ingress":
591
+ env["MUSE_EXPERIMENTAL_EXTERNAL_AGENT_INGRESS"] = "on"
592
+ return env
593
+
594
+
478
595
  def _agy_env(cell: Cell) -> dict[str, str]:
479
596
  """Subscription-billing env for an ``agy`` (antigravity/Gemini) session.
480
597
 
@@ -2706,6 +2823,8 @@ class MuseMembrane(ClaudeMembrane):
2706
2823
 
2707
2824
  name = "muse"
2708
2825
 
2826
+ env_builder = staticmethod(_muse_env)
2827
+
2709
2828
  def apply_task_injection(self, cell: Cell, argv: list[str], text: str) -> None:
2710
2829
  # muse has no --append-system-prompt; the prompt is POSITIONAL. Appended
2711
2830
  # only when this is a FRESH session — `muse resume` takes no prompt, so
@@ -3184,6 +3303,16 @@ def run_spawn(argv: Optional[list[str]] = None) -> int:
3184
3303
  was_generated = False
3185
3304
  effective_role: Optional[str] = None
3186
3305
 
3306
+ # Channel gate BEFORE session minting: an invalid channel mode must fail
3307
+ # before load_or_create_session_id persists anything (a refused spawn
3308
+ # must not leave a minted sidecar behind). One choke point for every
3309
+ # provider; builders below see only validated modes.
3310
+ try:
3311
+ _validate_channel(cell, _resolve_channel(cell))
3312
+ except CellError as exc:
3313
+ print(f"swarph spawn: {exc}", file=sys.stderr)
3314
+ return 1
3315
+
3187
3316
  if membrane.uses_pinned_session():
3188
3317
  # When user typed a slot-role (e.g. `swarph spawn drop-on-meta-edge-2`)
3189
3318
  # the cell.yaml resolved to the BASE file (drop-on-meta-edge.yaml) so
@@ -3265,6 +3394,18 @@ def run_spawn(argv: Optional[list[str]] = None) -> int:
3265
3394
  )
3266
3395
  return 0
3267
3396
 
3397
+ if args.print_resolved:
3398
+ # Resolved-launch capture: argv + the env keys swarph adds, as JSON
3399
+ # on stdout, then exit BEFORE resolve_binary — no binary needed, no
3400
+ # session started. Banner stays on stderr so stdout parses clean.
3401
+ # env_added is a DIFF against the parent env (not the full env) so
3402
+ # no secret the operator exported leaks into the capture.
3403
+ built_env = membrane.spawn_env(cell)
3404
+ added = {k: v for k, v in built_env.items()
3405
+ if os.environ.get(k) != v}
3406
+ print(json.dumps({"argv": spawn_argv, "env_added": added}))
3407
+ return 0
3408
+
3268
3409
  provider_bin = membrane.resolve_binary()
3269
3410
  if provider_bin is None:
3270
3411
  print(membrane.binary_not_found_message(), file=sys.stderr)
@@ -1,6 +1,9 @@
1
1
  """stall_alert — surface a cell whose live session is perpetually busy so its
2
- undelivered DMs don't rot silently. Exponential backoff (6,12,24,48…) prevents
2
+ undelivered DMs don't rot silently. Exponential backoff (60,120,240,480…) prevents
3
3
  the linear-flood failure (the 145-DM incident, feedback_modal_stalls_cell_wake).
4
+ Measured 2026-09-30/10-01: 7 of 7 stall rows fired on busy builder cells that
5
+ drained on their own within 7-25 min. The first alert waits 60 ticks (30 min
6
+ at the 30s poll) so a long build is not paged at 3 min.
4
7
 
5
8
  Card #989: the surface is one obligation held by lab-ovh, never a DM to
6
9
  commander. A second stall tick while that row is open opens nothing new. A
@@ -15,7 +18,7 @@ import urllib.parse
15
18
  import urllib.request
16
19
  from swarph_cli.console_safe import print_safe
17
20
 
18
- _STALL_FIRST = 6 # first alert after this many consecutive deferred ticks
21
+ _STALL_FIRST = 60 # first alert after this many consecutive deferred ticks
19
22
  _CARD = 989
20
23
  _HOLDER = "lab-ovh"
21
24
  # lab-ovh's own stall cannot escalate to lab-ovh. Another orchestrator holds it.
@@ -25,7 +28,12 @@ _PREFIX = f"STALL card #{_CARD}"
25
28
 
26
29
 
27
30
  def is_alert_tick(deferred_ticks: int) -> bool:
28
- """True exactly at 6, 12, 24, 48, 96 … — first at _STALL_FIRST, doubling."""
31
+ """True exactly at 60, 120, 240, 480 … — first at _STALL_FIRST, doubling.
32
+
33
+ Measured 2026-09-30/10-01: 7 of 7 stall rows fired on busy builder cells
34
+ that drained on their own within 7-25 min. At 6 ticks (3 min on a 30s
35
+ poll) every long build tripped the alert.
36
+ """
29
37
  if deferred_ticks < _STALL_FIRST or deferred_ticks % _STALL_FIRST != 0:
30
38
  return False
31
39
  k = deferred_ticks // _STALL_FIRST
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.71.0
3
+ Version: 0.72.0
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
@@ -187,6 +187,8 @@ src/swarph_cli/systemd/swarph-watchdog.service
187
187
  src/swarph_cli/systemd/swarph-watchdog.timer
188
188
  tests/test_1006_board_publisher.py
189
189
  tests/test_1006_session_board.py
190
+ tests/test_1013_spawn_channel.py
191
+ tests/test_1017_muse_tui.py
190
192
  tests/test_124_register_merge.py
191
193
  tests/test_126_pending_from_complete.py
192
194
  tests/test_132_ensure_monitor_state_dir.py
@@ -207,6 +209,7 @@ tests/test_397_router_preflight.py
207
209
  tests/test_402_one_identity_resolver.py
208
210
  tests/test_467b_onboard_operator_credential.py
209
211
  tests/test_472_register_caller_binding.py
212
+ tests/test_484_codex_queue_sink.py
210
213
  tests/test_523_guide.py
211
214
  tests/test_524_conftest_hermeticity.py
212
215
  tests/test_532_accept_flag.py