swarph-cli 0.48.0__tar.gz → 0.48.2__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 (338) hide show
  1. {swarph_cli-0.48.0/src/swarph_cli.egg-info → swarph_cli-0.48.2}/PKG-INFO +1 -1
  2. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/pyproject.toml +1 -1
  3. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/mesh.py +186 -41
  5. {swarph_cli-0.48.0 → swarph_cli-0.48.2/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  6. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_sidecar.py +18 -1
  7. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_sidecar_cursor_decoupled.py +10 -0
  8. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_sidecar_wake_survives_read.py +10 -0
  9. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_cli.py +10 -0
  10. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_state_split.py +8 -0
  11. swarph_cli-0.48.2/tests/test_tmux_wake_submit_verify.py +537 -0
  12. swarph_cli-0.48.0/tests/test_tmux_wake_submit_verify.py +0 -272
  13. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/LICENSE +0 -0
  14. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/README.md +0 -0
  15. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/setup.cfg +0 -0
  16. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/__main__.py +0 -0
  17. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/__init__.py +0 -0
  18. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/backends.py +0 -0
  19. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/pack.py +0 -0
  20. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/prices.py +0 -0
  21. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/quality.py +0 -0
  22. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/refresh_prices.py +0 -0
  23. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/runner.py +0 -0
  24. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/bench/validate.py +0 -0
  25. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/caller.py +0 -0
  26. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/__init__.py +0 -0
  27. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/harden.py +0 -0
  28. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/lineage.py +0 -0
  29. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/liveness.py +0 -0
  30. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/manifest.py +0 -0
  31. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/paths.py +0 -0
  32. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/capture/verify.py +0 -0
  33. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/cell.py +0 -0
  34. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/chain_token.py +0 -0
  35. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/__init__.py +0 -0
  36. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/_content.py +0 -0
  37. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/_display.py +0 -0
  38. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/_gateway_client.py +0 -0
  39. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/add.py +0 -0
  40. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/bench.py +0 -0
  41. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/board.py +0 -0
  42. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/board_merge_check.py +0 -0
  43. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/brain.py +0 -0
  44. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/brain_ask.py +0 -0
  45. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/cell.py +0 -0
  46. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  47. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/channel.py +0 -0
  48. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/chat.py +0 -0
  49. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/codegraph.py +0 -0
  50. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  51. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  52. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/codex_waker.py +0 -0
  53. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/compress.py +0 -0
  54. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/daemon.py +0 -0
  55. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/event.py +0 -0
  56. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/gateway.py +0 -0
  57. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/gh_route.py +0 -0
  58. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/group.py +0 -0
  59. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/guide.py +0 -0
  60. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/highlight.py +0 -0
  61. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/hook_output.py +0 -0
  62. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/hooks.py +0 -0
  63. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/import_session.py +0 -0
  64. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/init.py +0 -0
  65. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  66. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_hook.py +0 -0
  67. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  68. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  69. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  70. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/lane.py +0 -0
  71. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/mcp_server.py +0 -0
  72. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/memory.py +0 -0
  73. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  74. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/memory_sync.py +0 -0
  75. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/monitor.py +0 -0
  76. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/okf_links.py +0 -0
  77. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/onboard.py +0 -0
  78. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  79. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  80. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/protocol_handler.py +0 -0
  81. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/ratify.py +0 -0
  82. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/schedule.py +0 -0
  83. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/security.py +0 -0
  84. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/service.py +0 -0
  85. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/spawn.py +0 -0
  86. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/timeline.py +0 -0
  87. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  88. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/commands/watchdog.py +0 -0
  89. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/compress/__init__.py +0 -0
  90. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/compress/levers.py +0 -0
  91. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/compress/marker.py +0 -0
  92. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/compress/verify.py +0 -0
  93. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/console_safe.py +0 -0
  94. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/delivery_queue.py +0 -0
  95. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/__init__.py +0 -0
  96. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/feature_registry.py +0 -0
  97. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/lanes_control.py +0 -0
  98. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/schema.sql +0 -0
  99. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/server.py +0 -0
  100. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gateway/services_control.py +0 -0
  101. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/gh_identity.py +0 -0
  102. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/guide/GUIDE.md +0 -0
  103. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/main.py +0 -0
  104. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/multiplexer.py +0 -0
  105. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/parsers/__init__.py +0 -0
  106. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/parsers/claude.py +0 -0
  107. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  108. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  109. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  110. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  111. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  112. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  113. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  114. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  115. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  116. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  117. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  118. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_executor.py +0 -0
  119. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_executor_windows.py +0 -0
  120. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_reconciliation.py +0 -0
  121. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_reply_outbox.py +0 -0
  122. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_service_host.py +0 -0
  123. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_service_payload.py +0 -0
  124. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_service_reply.py +0 -0
  125. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/peer_staging.py +0 -0
  126. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/scripts/__init__.py +0 -0
  127. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  128. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  129. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  130. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/service/__init__.py +0 -0
  131. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/service/app.py +0 -0
  132. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/service/providers.py +0 -0
  133. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/session_bridge.py +0 -0
  134. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/stall_alert.py +0 -0
  135. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/codex-waker.default +0 -0
  136. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  137. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  138. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  139. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  140. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  141. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  142. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  143. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  144. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  145. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  146. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli/tokens.py +0 -0
  147. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
  148. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  149. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  150. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/requires.txt +0 -0
  151. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/src/swarph_cli.egg-info/top_level.txt +0 -0
  152. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_124_register_merge.py +0 -0
  153. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_201_win32_bash_refusal.py +0 -0
  154. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_243_onboard_per_peer_token.py +0 -0
  155. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_307_task4_ask_reply.py +0 -0
  156. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_319_refusal_exit_codes.py +0 -0
  157. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_323_mistral_backend.py +0 -0
  158. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_397_gh_identity_router.py +0 -0
  159. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_397_router_preflight.py +0 -0
  160. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_467b_onboard_operator_credential.py +0 -0
  161. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_523_guide.py +0 -0
  162. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_524_conftest_hermeticity.py +0 -0
  163. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_532_accept_flag.py +0 -0
  164. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_535_version_capability.py +0 -0
  165. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_546_default_gateway.py +0 -0
  166. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_548_gateway_remainder.py +0 -0
  167. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_552_codegraph_gateway.py +0 -0
  168. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_555_relay_row_shape.py +0 -0
  169. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_556_relay_freshness.py +0 -0
  170. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_84_cli_labels.py +0 -0
  171. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_add_security_gate.py +0 -0
  172. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_add.py +0 -0
  173. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_hash.py +0 -0
  174. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_lib.py +0 -0
  175. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_mcp.py +0 -0
  176. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_skill.py +0 -0
  177. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_tool.py +0 -0
  178. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_artifact_uri.py +0 -0
  179. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_backends.py +0 -0
  180. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_command.py +0 -0
  181. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_pack.py +0 -0
  182. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_prices.py +0 -0
  183. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_quality.py +0 -0
  184. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_runner.py +0 -0
  185. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_bench_validate.py +0 -0
  186. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_board_card_thread.py +0 -0
  187. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_board_command.py +0 -0
  188. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_board_merge_check.py +0 -0
  189. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_board_obligation_close.py +0 -0
  190. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_brain_ask_command.py +0 -0
  191. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_brain_ask_env_diagnosis.py +0 -0
  192. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_brain_ask_identity_explicitness.py +0 -0
  193. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_brain_command.py +0 -0
  194. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_caller_meta_guard.py +0 -0
  195. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_lineage.py +0 -0
  196. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_liveness.py +0 -0
  197. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_manifest.py +0 -0
  198. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_paths.py +0 -0
  199. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_capture_security.py +0 -0
  200. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_command_dispatch.py +0 -0
  201. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_harden.py +0 -0
  202. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_loader.py +0 -0
  203. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_selfcheck.py +0 -0
  204. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cell_verify.py +0 -0
  205. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_chain_token.py +0 -0
  206. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_channel_command.py +0 -0
  207. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  208. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_channel_post.py +0 -0
  209. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_channel_wake_policy_filter.py +0 -0
  210. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_chat_command.py +0 -0
  211. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_claude_parser.py +0 -0
  212. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_claude_tmux_template.py +0 -0
  213. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codegraph_caller_scope.py +0 -0
  214. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codegraph_command.py +0 -0
  215. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codegraph_hook.py +0 -0
  216. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codegraph_mcp.py +0 -0
  217. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_codex_waker.py +0 -0
  218. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_caller_convention.py +0 -0
  219. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_command.py +0 -0
  220. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_levers.py +0 -0
  221. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_marker.py +0 -0
  222. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_compress_verify.py +0 -0
  223. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_console_encoding_e2e.py +0 -0
  224. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_content_composition.py +0 -0
  225. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_cursor_membrane.py +0 -0
  226. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_daemon_command.py +0 -0
  227. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_daemon_console_encoding.py +0 -0
  228. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_daemon_delivery.py +0 -0
  229. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_delivery_queue.py +0 -0
  230. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_display_sanitize.py +0 -0
  231. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  232. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_event_emit.py +0 -0
  233. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_feature_to_uri.py +0 -0
  234. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gateway_brain.py +0 -0
  235. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gateway_client.py +0 -0
  236. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gateway_command.py +0 -0
  237. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gateway_message_idempotency.py +0 -0
  238. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  239. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_group_command.py +0 -0
  240. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_heartbeat_check.py +0 -0
  241. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_highlight_command.py +0 -0
  242. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hook_output.py +0 -0
  243. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_activity_marker.py +0 -0
  244. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_add.py +0 -0
  245. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_bundle.py +0 -0
  246. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_init.py +0 -0
  247. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_legacy_generation_removal.py +0 -0
  248. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_lifecycle.py +0 -0
  249. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_merge.py +0 -0
  250. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_verify.py +0 -0
  251. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  252. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_hooks_win32_command_path.py +0 -0
  253. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_import_command.py +0 -0
  254. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_inbox_identity_provenance.py +0 -0
  255. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_init_command.py +0 -0
  256. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_init_provider_menu.py +0 -0
  257. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_codex_hooks.py +0 -0
  258. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_hook.py +0 -0
  259. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_multiplexer_command.py +0 -0
  260. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_postcompact_hook.py +0 -0
  261. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_install_wake_hook.py +0 -0
  262. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_lane_command.py +0 -0
  263. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_main.py +0 -0
  264. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_main_unknown_verb_guard.py +0 -0
  265. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mcp_server.py +0 -0
  266. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_memory_command.py +0 -0
  267. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_memory_gateway.py +0 -0
  268. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_memory_sync.py +0 -0
  269. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_memory_sync_membrane.py +0 -0
  270. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_command.py +0 -0
  271. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_inbox_mark_read.py +0 -0
  272. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_inbox_sanitize.py +0 -0
  273. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_mesh_send_unknown_peer.py +0 -0
  274. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_meta_edge_identity.py +0 -0
  275. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_channel_poll.py +0 -0
  276. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_daemon_collision.py +0 -0
  277. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_monitor_self_name_verification.py +0 -0
  278. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_multiplexer.py +0 -0
  279. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_muse_membrane.py +0 -0
  280. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_muse_own_cli.py +0 -0
  281. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_obligation_close_is_surfaced.py +0 -0
  282. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_okf_links.py +0 -0
  283. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_onboard_command.py +0 -0
  284. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_packaged_artifacts_exist.py +0 -0
  285. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_executor.py +0 -0
  286. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_executor_windows.py +0 -0
  287. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_reply_drain_command.py +0 -0
  288. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_reply_drain_supervisor.py +0 -0
  289. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_reply_outbox.py +0 -0
  290. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_reply_transport.py +0 -0
  291. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_service_e2e.py +0 -0
  292. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_service_host.py +0 -0
  293. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_service_payload.py +0 -0
  294. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_peer_service_reply.py +0 -0
  295. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_postcompact_recall.py +0 -0
  296. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_process_liveness.py +0 -0
  297. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_protocol_handler.py +0 -0
  298. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_providers_isolation.py +0 -0
  299. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_ratify_command.py +0 -0
  300. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_repl_caller_convention.py +0 -0
  301. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_schedule_command.py +0 -0
  302. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_schedule_create_contract.py +0 -0
  303. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_security.py +0 -0
  304. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_service_app.py +0 -0
  305. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_service_command.py +0 -0
  306. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_service_providers.py +0 -0
  307. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_session_bridge_inject.py +0 -0
  308. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_session_bridge_probe.py +0 -0
  309. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_session_bridge_resolve.py +0 -0
  310. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_session_path_role_gate.py +0 -0
  311. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_shipped_units_match_the_cli.py +0 -0
  312. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_smoke_chat.py +0 -0
  313. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_smoke_one_shot.py +0 -0
  314. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_smoke_phase_5_5.py +0 -0
  315. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_base_hoist.py +0 -0
  316. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_command.py +0 -0
  317. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_identity_stamp.py +0 -0
  318. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_live_pin.py +0 -0
  319. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_mitosis_lineage.py +0 -0
  320. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_tmux_session.py +0 -0
  321. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_spawn_windows_relaunch.py +0 -0
  322. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_stall_alert.py +0 -0
  323. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_subprocess_decode_encoding.py +0 -0
  324. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_timeline_command.py +0 -0
  325. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_token_file_one_parser.py +0 -0
  326. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_token_precedence_explicit_first.py +0 -0
  327. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_unified_token_resolver.py +0 -0
  328. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_version_consistency.py +0 -0
  329. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_vibe_membrane.py +0 -0
  330. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog.py +0 -0
  331. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_dm_wake.py +0 -0
  332. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  333. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  334. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_emit_health.py +0 -0
  335. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_install_flags.py +0 -0
  336. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_model_rung.py +0 -0
  337. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/tests/test_watchdog_stale_peers.py +0 -0
  338. {swarph_cli-0.48.0 → swarph_cli-0.48.2}/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.48.0
3
+ Version: 0.48.2
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.48.0"
7
+ version = "0.48.2"
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.48.0"
19
+ __version__ = "0.48.2"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -1045,8 +1045,13 @@ class Sink:
1045
1045
  def __init__(self, name: str):
1046
1046
  self.name = name
1047
1047
 
1048
- def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> bool:
1049
- """Push `dms` (may be a bounded/empty replay). True == delivered."""
1048
+ def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> Optional[bool]:
1049
+ """Push `dms` (may be a bounded/empty replay). Three outcomes:
1050
+ True == delivered (cursor advances); False == FAILED (loud, counted —
1051
+ the sink is probably gone); None == DEFERRED (still owed, but neither
1052
+ delivered nor failed — e.g. TmuxSink's politeness gate finding a
1053
+ human mid-write. A deferral never increments consecutive_failures:
1054
+ a busy composer is not a dead sink)."""
1050
1055
  raise NotImplementedError
1051
1056
 
1052
1057
  def observe(self, state: "MonitorState", ledger: dict, window: list) -> bool:
@@ -1134,6 +1139,16 @@ class TmuxSink(Sink):
1134
1139
  ('check meshcheck mesh…' ×7 observed live on cursor-lin). If the pane
1135
1140
  observably still HOLDS an unsubmitted wake, the gate sends a single
1136
1141
  verified Enter nudge instead of new text — the anti-stack retry.
1142
+
1143
+ POLITENESS GATE (commander, 2026-08-24, same day): even a verified wake
1144
+ INJECTS BLIND — '-l' appends to whatever sits in the composer, so a wake
1145
+ landing mid-keystroke merges into the human's half-typed line. The gate:
1146
+ no inject and no nudge unless the composer is OBSERVED in a compatible
1147
+ state ("clear" to inject, "wake"-only to nudge). A BUSY composer DEFERS
1148
+ (deliver returns None): the wake stays owed, the cursor does not advance,
1149
+ and no failure is counted — a human typing is not a dead sink. An
1150
+ UNREADABLE composer FAILS without a keystroke: never type blind, and a
1151
+ dead pane must keep the failure loud.
1137
1152
  """
1138
1153
 
1139
1154
  is_push = True
@@ -1142,7 +1157,7 @@ class TmuxSink(Sink):
1142
1157
  super().__init__(f"tmux:{target}")
1143
1158
  self.target = target
1144
1159
 
1145
- def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> bool:
1160
+ def deliver(self, state: "MonitorState", dms: list, up_to_id: int) -> Optional[bool]:
1146
1161
  led = state.ledger(self.name)
1147
1162
  if led.get("wake_outstanding"):
1148
1163
  # Lazy: watchdog imports mesh module-level, so the reverse must
@@ -1154,18 +1169,47 @@ class TmuxSink(Sink):
1154
1169
  if unread == 0:
1155
1170
  led["wake_outstanding"] = False # drained since — re-arm
1156
1171
  else:
1157
- pending = _wake_still_pending(self.target)
1158
- if pending is True:
1172
+ composer = _composer_state(self.target)
1173
+ if composer == "wake":
1159
1174
  _tmux_enter(self.target) # one verified nudge, no new text
1160
1175
  return True
1161
- if pending is False:
1176
+ if composer == "clear":
1162
1177
  # Wake submitted; the cell simply hasn't drained yet. The
1163
1178
  # wake did its job — re-injecting would only stack.
1164
1179
  return True
1180
+ if composer == "busy":
1181
+ # Human text shares the composer (possibly merged into our
1182
+ # wake) — an Enter here submits THEIR line (#403's shape).
1183
+ return None
1165
1184
  return False # pane unreadable: keep the failure loud
1185
+ # Fresh path, gated on the OBSERVED composer (gpt-ops REVISE, #312):
1186
+ # "wake" — wake text already sits ALONE in the composer (a monitor
1187
+ # restart loses wake_outstanding; the ledger flag is gone but the
1188
+ # keystrokes landed). Nudge it with one Enter — injecting here would
1189
+ # stack a second copy, the exact defect the gate exists to prevent.
1190
+ # "busy" DEFERS (None — a human mid-write is not a dead sink).
1191
+ # Unreadable FAILS (False) without a single keystroke: we never type
1192
+ # blind into a pane, and a dead pane must stay loud — deferring it
1193
+ # would silently freeze the dead-sink alarm the ledger exists to ring.
1194
+ composer = _composer_state(self.target)
1195
+ if composer == "wake":
1196
+ if _tmux_enter(self.target):
1197
+ led["wake_outstanding"] = True
1198
+ return True
1199
+ return False
1200
+ if composer == "busy":
1201
+ return None
1202
+ if composer != "clear":
1203
+ return False
1166
1204
  # Module-global lookup on purpose: the sidecar regression suites patch
1167
1205
  # `mesh._tmux_wake`, and a `from`-import here would silently bypass them.
1168
1206
  ok = _tmux_wake(self.target)
1207
+ if ok is None:
1208
+ # The human ADOPTED the composer mid-settle (gpt-ops, #312 round
1209
+ # 5): the wake text never submitted as ours. DEFER — the cursor
1210
+ # must NOT advance, or the wake is silently lost when the human
1211
+ # never sends their line.
1212
+ return None
1169
1213
  if ok:
1170
1214
  led["wake_outstanding"] = True
1171
1215
  return True
@@ -1514,15 +1558,17 @@ _WAKE_SETTLE_S = 0.6
1514
1558
  _WAKE_SUBMIT_ATTEMPTS = 4
1515
1559
 
1516
1560
 
1517
- def _wake_still_pending(target: str) -> Optional[bool]:
1518
- """Three-way: True = the wake prompt still sits in the pane's bottom lines
1519
- (UNSUBMITTED, waiting in the composer — a concatenated backlog like
1520
- 'check meshcheck mesh' matches the same substring check and is drained by
1521
- the retry Enter that submits it). False = observed clear. None = UNKNOWN
1522
- (capture failed) — and unknown must FAIL CLOSED (gpt-ops, PR #306): an
1523
- unverified retry Enter is not a no-op, it is a keypress into a pane whose
1524
- composer may hold a human's half-typed line (#403's shape). Unknown stops
1525
- the loop and owes the wake; it never earns another Enter."""
1561
+ def _capture_pane_lines(target: str) -> Optional[list[str]]:
1562
+ """The pane's non-empty lines, or None when the pane is UNREADABLE
1563
+ (capture error / non-zero rc). Shared by the wake verifier and the
1564
+ politeness gate — None must always fail closed, never "probably fine".
1565
+
1566
+ NOT a fixed tail window: cursor's TUI renders chrome BELOW the composer
1567
+ (task count, model/status bar, '~' — measured live on cursor-lin
1568
+ 2026-08-24: the composer sits 4 non-empty lines from the bottom), so a
1569
+ tail-3 lands entirely in the chrome and reads every cursor pane as
1570
+ unknown. The composer is identified structurally instead — see
1571
+ _composer_line."""
1526
1572
  try:
1527
1573
  r = subprocess.run(
1528
1574
  ["tmux", "capture-pane", "-p", "-t", target],
@@ -1534,21 +1580,98 @@ def _wake_still_pending(target: str) -> Optional[bool]:
1534
1580
  return None
1535
1581
  if r.returncode != 0:
1536
1582
  return None
1537
- lines = [ln for ln in (r.stdout or "").splitlines() if ln.strip()]
1538
- tail = lines[-3:]
1539
- if any(_WAKE_PROMPT in ln for ln in tail):
1540
- return True
1541
- # "Clear" must be an OBSERVATION, not an absence (gpt-ops, PR #306 round
1542
- # 2): a successful-but-empty or unrecognizable capture proves nothing
1543
- # about the composer. Only a pane whose bottom shows a composer prompt
1544
- # line with no wake text in it counts as submitted. The marker is
1545
- # per-TUI: '>' for claude/codex composers, '→' for cursor's Linux TUI
1546
- # (measured live on cursor-lin 2026-08-24: the composer renders
1547
- # '→ Add a follow-up', never '>'). A pane showing NEITHER marker is
1548
- # unrecognizable → unknown → fail closed.
1549
- if any(ln.strip().startswith((">", "→")) for ln in tail):
1550
- return False
1551
- return None
1583
+ return [ln for ln in (r.stdout or "").splitlines() if ln.strip()]
1584
+
1585
+
1586
+ _COMPOSER_MARKERS = (">", "›", "→")
1587
+
1588
+
1589
+ def _composer_line(lines: list[str]) -> Optional[str]:
1590
+ """The LAST non-empty line starting with a composer marker — by TUI
1591
+ layout the composer is always the bottom-most prompt line; everything
1592
+ below it is chrome (status bars never start with a marker), and
1593
+ everything above is history. Cursor echoes submitted prompts as
1594
+ '│ ○ text │' and claude/codex as '> text' ABOVE the composer, so the
1595
+ last-marker rule is what keeps history out of the reading. Markers:
1596
+ '>' claude, '›' codex (gpt-ops live capture), '→' cursor (measured live
1597
+ on cursor-lin 2026-08-24: the composer renders '→ Add a follow-up')."""
1598
+ composer = None
1599
+ for ln in lines:
1600
+ s = ln.strip()
1601
+ if s.startswith(_COMPOSER_MARKERS):
1602
+ composer = s
1603
+ return composer
1604
+
1605
+
1606
+ def _wake_still_pending(target: str) -> Optional[bool]:
1607
+ """Projection of the four-way composer state (gpt-ops, PR #312 round 4):
1608
+
1609
+ True = the composer holds ONLY wake text (one unsubmitted wake, or the
1610
+ pre-fix stacked concatenation 'check meshcheck mesh' — both are drained
1611
+ by the retry Enter). False = anything else OBSERVED: a clear composer
1612
+ (the wake submitted) OR human text in the line — including wake text
1613
+ MERGED with human text ('> check mesh half-typed', typed during the
1614
+ settle). Merged must stop the loop WITHOUT another Enter: that Enter
1615
+ would submit the human's half-typed line (#403's shape). The wake is
1616
+ then out of our hands — the human owns the line, and the busy-deferral
1617
+ in TmuxSink.deliver keeps the wake owed until they send it. None =
1618
+ UNKNOWN (unreadable / no composer line recognized) — and unknown must
1619
+ FAIL CLOSED (gpt-ops, PR #306): an unverified retry Enter is not a
1620
+ no-op, it is a keypress into a pane whose composer may hold a human's
1621
+ half-typed line. Unknown stops the loop and owes the wake; it never
1622
+ earns another Enter."""
1623
+ state = _composer_state(target)
1624
+ if state is None:
1625
+ return None
1626
+ return state == "wake"
1627
+
1628
+
1629
+ #: cursor's EMPTY composer is not bare: it renders the marker plus a dimmed
1630
+ #: placeholder ('→ Add a follow-up', measured live on cursor-lin 2026-08-24).
1631
+ #: Treating any text-after-marker as busy would defer every wake forever on
1632
+ #: cursor; treating the placeholder as human text is the only false positive
1633
+ #: it can produce, and the failure direction there is a wake never sent —
1634
+ #: visible in the log, never a keystroke into someone's line.
1635
+ _CURSOR_COMPOSER_PLACEHOLDER = "Add a follow-up"
1636
+
1637
+
1638
+ def _composer_state(target: str) -> Optional[str]:
1639
+ """What the pane's composer LINE holds, four-way (the politeness gate):
1640
+
1641
+ "clear" — a composer prompt observed EMPTY (bare marker, or cursor's
1642
+ placeholder). Safe to inject.
1643
+ "wake" — the composer holds ONLY wake text (one wake, or the pre-fix
1644
+ stacked concatenation 'check meshcheck mesh'). Safe to
1645
+ nudge with ONE Enter; injecting more text would stack.
1646
+ "busy" — the composer holds anything else: a human mid-write, or
1647
+ human text merged into our wake. Neither inject nor Enter.
1648
+ None — pane unreadable or no composer line recognized. UNKNOWN,
1649
+ and unknown fails closed: we never type blind into a pane.
1650
+
1651
+ The composer line comes from _composer_line (last marker line in the
1652
+ capture — cursor renders chrome BELOW the composer, so no fixed tail
1653
+ window can find it).
1654
+ """
1655
+ lines = _capture_pane_lines(target)
1656
+ if lines is None:
1657
+ return None
1658
+ composer = _composer_line(lines)
1659
+ if composer is None:
1660
+ return None
1661
+ content = composer[1:].strip()
1662
+ # cursor right-aligns run-state hints ('ctrl+c to stop') on the composer
1663
+ # ROW, so the placeholder is a PREFIX of the line while the agent runs,
1664
+ # not the whole line. A human message beginning with the exact placeholder
1665
+ # text is the accepted false positive — the only failure it can produce
1666
+ # is an inject alongside contrived text, never a silent deferral.
1667
+ if not content or content.startswith(_CURSOR_COMPOSER_PLACEHOLDER):
1668
+ return "clear"
1669
+ # A human who types exactly 'check mesh' by hand is indistinguishable
1670
+ # from our own unsubmitted wake — and submitting it runs the same
1671
+ # command, so the collision is harmless in that direction only.
1672
+ if content.replace(_WAKE_PROMPT, "").strip() == "":
1673
+ return "wake"
1674
+ return "busy"
1552
1675
 
1553
1676
 
1554
1677
  def _tmux_enter(target: str) -> bool:
@@ -1567,7 +1690,7 @@ def _tmux_enter(target: str) -> bool:
1567
1690
  return False
1568
1691
 
1569
1692
 
1570
- def _tmux_wake(target: str) -> bool:
1693
+ def _tmux_wake(target: str) -> Optional[bool]:
1571
1694
  """Inject the wake prompt and confirm it SUBMITTED (#533).
1572
1695
 
1573
1696
  Was: Codex's blind double-Enter, on the assumption that a second Enter in
@@ -1577,13 +1700,16 @@ def _tmux_wake(target: str) -> bool:
1577
1700
  prompt on cursor-lin, 2026-08-24, three times in a row.
1578
1701
 
1579
1702
  Now: inject, settle, then Enter-and-verify in a bounded loop — re-Enter
1580
- only while the prompt is OBSERVED still sitting in the composer. Codex's
1703
+ only while the composer is OBSERVED holding wake text alone. Codex's
1581
1704
  second Enter falls out naturally (it fires only when the first did not
1582
- submit). Returns True only on an observed-clear composer: a wake we
1583
- cannot confirm stays owed, because the cursor advances only inside
1584
- `if _tmux_wake(...)`. An UNREADABLE pane (capture failure) fails closed
1585
- — no unverified retries, no claimed success; the wake stays owed and the
1586
- ledger retries the whole wake later.
1705
+ submit). Three outcomes: True on an observed-clear composer (submitted);
1706
+ None when the human ADOPTED the composer mid-settle (wake text merged
1707
+ with their typing — another Enter would submit their line, so the loop
1708
+ stops, and the sink DEFERS: the cursor must not advance on a wake that
1709
+ never submitted as ours — gpt-ops, #312 round 5); False when the pane
1710
+ could not be verified (the wake stays owed). An UNREADABLE pane (capture
1711
+ failure) fails closed — no unverified retries, no claimed success; the
1712
+ wake stays owed and the ledger retries the whole wake later.
1587
1713
  """
1588
1714
  try:
1589
1715
  subprocess.run(
@@ -1603,8 +1729,8 @@ def _tmux_wake(target: str) -> bool:
1603
1729
  text=True, encoding="utf-8", errors="replace",
1604
1730
  )
1605
1731
  time.sleep(_WAKE_SETTLE_S)
1606
- pending = _wake_still_pending(target)
1607
- if pending is None:
1732
+ state = _composer_state(target)
1733
+ if state is None:
1608
1734
  print(
1609
1735
  "[monitor] tmux wake unverifiable (capture failed); "
1610
1736
  "failing closed — wake stays owed",
@@ -1612,8 +1738,16 @@ def _tmux_wake(target: str) -> bool:
1612
1738
  flush=True,
1613
1739
  )
1614
1740
  return False
1615
- if not pending:
1741
+ if state == "clear":
1616
1742
  return True
1743
+ if state == "busy":
1744
+ # The human typed into the wake mid-settle (gpt-ops, #312
1745
+ # round 5): the wake text never submitted as ours, so this
1746
+ # must NOT acknowledge delivery — return None and let the
1747
+ # sink DEFER, keeping the cursor back so a later poll
1748
+ # retries the wake when the composer clears.
1749
+ return None
1750
+ # "wake": still sitting unsubmitted — loop another Enter.
1617
1751
  print(
1618
1752
  f"[monitor] tmux wake still unsubmitted after "
1619
1753
  f"{_WAKE_SUBMIT_ATTEMPTS} Enters; wake stays owed",
@@ -1805,7 +1939,18 @@ def _monitor_deliver(state: MonitorState) -> None:
1805
1939
  f"delivering the newest {len(dms)} (limit "
1806
1940
  f"{state.replay_limit}) -- see {state.inbox_log_path}",
1807
1941
  file=sys.stderr, flush=True)
1808
- if sink.deliver(state, dms, observed):
1942
+ outcome = sink.deliver(state, dms, observed)
1943
+ if outcome is None:
1944
+ # DEFERRED (e.g. TmuxSink's politeness gate): the wake stays
1945
+ # owed — the cursor does NOT advance — but a deferral is not
1946
+ # a failure: no count, no alarm, no ledger write. Retried on
1947
+ # the next poll like any owed delivery.
1948
+ print(f"{state.log_prefix} delivery DEFERRED for {sink.name} "
1949
+ f"(composer holds human text); wake stays owed, "
1950
+ f"no failure counted",
1951
+ flush=True)
1952
+ continue
1953
+ if outcome:
1809
1954
  led["last_delivered_id"] = observed
1810
1955
  led["last_delivery_at"] = now
1811
1956
  led["consecutive_failures"] = 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.48.0
3
+ Version: 0.48.2
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
@@ -6,9 +6,19 @@ import json
6
6
  import time
7
7
  from pathlib import Path
8
8
 
9
+ import pytest
10
+
9
11
  from swarph_cli.commands import mesh
10
12
 
11
13
 
14
+ @pytest.fixture(autouse=True)
15
+ def _clean_composer(monkeypatch):
16
+ """These tests pin ledger/cursor mechanics, not the politeness gate —
17
+ default every composer to OBSERVED-CLEAN so the gate stays out of the
18
+ way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
19
+ monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
20
+
21
+
12
22
  def _state(tmp_path: Path) -> mesh.MeshSidecarState:
13
23
  return mesh.MeshSidecarState(
14
24
  self_name="gpt-ops",
@@ -39,12 +49,19 @@ def test_tmux_wake_sends_literal_prompt_then_submits_and_verifies(monkeypatch):
39
49
  lambda command, **kwargs: calls.append((command, kwargs)) or Result(),
40
50
  )
41
51
  monkeypatch.setattr(mesh.time, "sleep", lambda _s: None)
52
+ # The verifier is now a projection of the four-way composer state; the
53
+ # autouse fixture stubs that state. Record the consultation instead of
54
+ # expecting a raw capture-pane call in the sequence.
55
+ seen = []
56
+ monkeypatch.setattr(
57
+ mesh, "_composer_state",
58
+ lambda t: seen.append(t) or "clear")
42
59
 
43
60
  assert mesh._tmux_wake("gpt-lc:0.0") is True
61
+ assert seen == ["gpt-lc:0.0"] # verified through the composer gate
44
62
  assert [command for command, _ in calls] == [
45
63
  ["tmux", "send-keys", "-t", "gpt-lc:0.0", "-l", "check mesh"],
46
64
  ["tmux", "send-keys", "-t", "gpt-lc:0.0", "Enter"],
47
- ["tmux", "capture-pane", "-p", "-t", "gpt-lc:0.0"],
48
65
  ]
49
66
 
50
67
 
@@ -17,9 +17,19 @@ succeeds. Waking is DELIVERY; the cursor is BOOKKEEPING.
17
17
 
18
18
  Run: venv/bin/python -m pytest tests/test_mesh_sidecar_cursor_decoupled.py -v
19
19
  """
20
+ import pytest
21
+
20
22
  from swarph_cli.commands import mesh
21
23
 
22
24
 
25
+ @pytest.fixture(autouse=True)
26
+ def _clean_composer(monkeypatch):
27
+ """These tests pin cursor/ledger decoupling, not the politeness gate —
28
+ default every composer to OBSERVED-CLEAN so the gate stays out of the
29
+ way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
30
+ monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
31
+
32
+
23
33
  def _state(tmp_path, wake_min_interval_s=0):
24
34
  return mesh.MeshSidecarState(
25
35
  self_name="lab-ovh",
@@ -9,9 +9,19 @@ Run: venv/bin/python -m pytest tests/test_mesh_sidecar_wake_survives_read.py -v
9
9
  """
10
10
  import urllib.parse
11
11
 
12
+ import pytest
13
+
12
14
  from swarph_cli.commands import mesh
13
15
 
14
16
 
17
+ @pytest.fixture(autouse=True)
18
+ def _clean_composer(monkeypatch):
19
+ """These tests pin ledger/cursor mechanics, not the politeness gate —
20
+ default every composer to OBSERVED-CLEAN so the gate stays out of the
21
+ way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
22
+ monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
23
+
24
+
15
25
  def _state(tmp_path, wake_min_interval_s=0):
16
26
  return mesh.MeshSidecarState(
17
27
  self_name="grok-researcher",
@@ -16,9 +16,19 @@ from __future__ import annotations
16
16
  import json
17
17
  import os
18
18
 
19
+ import pytest
20
+
19
21
  from swarph_cli.commands import mesh, monitor
20
22
 
21
23
 
24
+ @pytest.fixture(autouse=True)
25
+ def _clean_composer(monkeypatch):
26
+ """These tests pin CLI/ledger mechanics, not the politeness gate —
27
+ default every composer to OBSERVED-CLEAN so the gate stays out of the
28
+ way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
29
+ monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
30
+
31
+
22
32
  def _env(monkeypatch):
23
33
  monkeypatch.setenv("SWARPH_SELF", "lab-ovh")
24
34
  monkeypatch.setenv("MESH_GATEWAY_TOKEN", "tok")
@@ -25,6 +25,14 @@ import pytest
25
25
  from swarph_cli.commands import mesh
26
26
 
27
27
 
28
+ @pytest.fixture(autouse=True)
29
+ def _clean_composer(monkeypatch):
30
+ """These tests pin ledger/cursor mechanics, not the politeness gate —
31
+ default every composer to OBSERVED-CLEAN so the gate stays out of the
32
+ way. The gate's own matrix lives in test_tmux_wake_submit_verify.py."""
33
+ monkeypatch.setattr(mesh, "_composer_state", lambda t: "clear")
34
+
35
+
28
36
  def _state(tmp_path, sinks, *, min_interval_s=0, self_name="lab-ovh", replay_limit=50):
29
37
  return mesh.MonitorState(
30
38
  self_name=self_name,