swarph-cli 0.69.4__tar.gz → 0.69.5__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 (450) hide show
  1. {swarph_cli-0.69.4/src/swarph_cli.egg-info → swarph_cli-0.69.5}/PKG-INFO +1 -1
  2. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/pyproject.toml +1 -1
  3. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/daemon.py +4 -0
  5. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/mcp_server.py +4 -3
  6. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/mesh.py +40 -15
  7. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/scripts/ensure_monitor.sh +61 -2
  8. swarph_cli-0.69.5/src/swarph_cli/stall_alert.py +144 -0
  9. {swarph_cli-0.69.4 → swarph_cli-0.69.5/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  10. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli.egg-info/SOURCES.txt +1 -0
  11. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_735_dm_mcp_tools.py +2 -2
  12. swarph_cli-0.69.5/tests/test_960_ensure_monitor_heartbeat.py +166 -0
  13. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mesh_inbox_mark_read.py +60 -7
  14. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_monitor_state_split.py +5 -0
  15. swarph_cli-0.69.5/tests/test_stall_alert.py +140 -0
  16. swarph_cli-0.69.4/src/swarph_cli/stall_alert.py +0 -52
  17. swarph_cli-0.69.4/tests/test_stall_alert.py +0 -52
  18. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/LICENSE +0 -0
  19. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/README.md +0 -0
  20. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/setup.cfg +0 -0
  21. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/__main__.py +0 -0
  22. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/__init__.py +0 -0
  23. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/backends.py +0 -0
  24. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/claude_cli.py +0 -0
  25. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/pack.py +0 -0
  26. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/prices.py +0 -0
  27. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/providers.py +0 -0
  28. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/quality.py +0 -0
  29. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/refresh_prices.py +0 -0
  30. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/rules.py +0 -0
  31. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/runner.py +0 -0
  32. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/bench/validate.py +0 -0
  33. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/caller.py +0 -0
  34. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/capture/__init__.py +0 -0
  35. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/capture/harden.py +0 -0
  36. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/capture/lineage.py +0 -0
  37. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/capture/liveness.py +0 -0
  38. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/capture/manifest.py +0 -0
  39. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/capture/paths.py +0 -0
  40. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/capture/verify.py +0 -0
  41. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/cell.py +0 -0
  42. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/chain_token.py +0 -0
  43. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/channel.py +0 -0
  44. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/__init__.py +0 -0
  45. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/_content.py +0 -0
  46. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/_display.py +0 -0
  47. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/_gateway_client.py +0 -0
  48. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/add.py +0 -0
  49. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/bench.py +0 -0
  50. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/board.py +0 -0
  51. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/board_merge_check.py +0 -0
  52. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/brain.py +0 -0
  53. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/brain_ask.py +0 -0
  54. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/cell.py +0 -0
  55. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/cell_probe.py +0 -0
  56. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  57. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/channel.py +0 -0
  58. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/chat.py +0 -0
  59. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/codegraph.py +0 -0
  60. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  61. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  62. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/codex_waker.py +0 -0
  63. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/compress.py +0 -0
  64. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/dreaming.py +0 -0
  65. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/event.py +0 -0
  66. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/gateway.py +0 -0
  67. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/gh_route.py +0 -0
  68. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/group.py +0 -0
  69. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/guide.py +0 -0
  70. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/highlight.py +0 -0
  71. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  72. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/hook_output.py +0 -0
  73. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/hooks.py +0 -0
  74. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/import_session.py +0 -0
  75. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/init.py +0 -0
  76. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  77. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/install_hook.py +0 -0
  78. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  79. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  80. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  81. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  82. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/lane.py +0 -0
  83. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/memory.py +0 -0
  84. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  85. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/memory_sync.py +0 -0
  86. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/monitor.py +0 -0
  87. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/okf_links.py +0 -0
  88. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/onboard.py +0 -0
  89. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  90. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  91. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/protocol_handler.py +0 -0
  92. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/ratify.py +0 -0
  93. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/schedule.py +0 -0
  94. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/security.py +0 -0
  95. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/service.py +0 -0
  96. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/spawn.py +0 -0
  97. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/timeline.py +0 -0
  98. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/version.py +0 -0
  99. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  100. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/commands/watchdog.py +0 -0
  101. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/compress/__init__.py +0 -0
  102. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/compress/levers.py +0 -0
  103. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/compress/marker.py +0 -0
  104. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/compress/verify.py +0 -0
  105. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/console_safe.py +0 -0
  106. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/delivery_queue.py +0 -0
  107. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dm_frame.py +0 -0
  108. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/__init__.py +0 -0
  109. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/candidates.py +0 -0
  110. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/clone.py +0 -0
  111. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/enrich.py +0 -0
  112. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/index_check.py +0 -0
  113. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/notify.py +0 -0
  114. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/organize.py +0 -0
  115. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/probes.py +0 -0
  116. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/report.py +0 -0
  117. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/run.py +0 -0
  118. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/slm.py +0 -0
  119. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/transcripts.py +0 -0
  120. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/dreaming/verify.py +0 -0
  121. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/gateway/__init__.py +0 -0
  122. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/gateway/feature_registry.py +0 -0
  123. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/gateway/lanes_control.py +0 -0
  124. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/gateway/schema.sql +0 -0
  125. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/gateway/server.py +0 -0
  126. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/gateway/services_control.py +0 -0
  127. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/gateway_default.py +0 -0
  128. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/gh_identity.py +0 -0
  129. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/guide/GUIDE.md +0 -0
  130. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/identity.py +0 -0
  131. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/main.py +0 -0
  132. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/multiplexer.py +0 -0
  133. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/orphan_daemons.py +0 -0
  134. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/pane_probe.py +0 -0
  135. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/parsers/__init__.py +0 -0
  136. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/parsers/claude.py +0 -0
  137. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  138. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  139. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  140. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  141. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  142. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  143. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  144. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  145. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  146. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  147. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  148. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  149. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/peer_executor.py +0 -0
  150. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/peer_executor_windows.py +0 -0
  151. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/peer_reconciliation.py +0 -0
  152. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/peer_reply_outbox.py +0 -0
  153. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/peer_service_host.py +0 -0
  154. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/peer_service_payload.py +0 -0
  155. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/peer_service_reply.py +0 -0
  156. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/peer_staging.py +0 -0
  157. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/query_filters.py +0 -0
  158. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/scripts/__init__.py +0 -0
  159. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  160. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  161. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  162. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  163. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/service/__init__.py +0 -0
  164. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/service/app.py +0 -0
  165. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/service/providers.py +0 -0
  166. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/session_bridge.py +0 -0
  167. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  168. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  169. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/source_text.py +0 -0
  170. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/codex-waker.default +0 -0
  171. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  172. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  173. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  174. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  175. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  176. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  177. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  178. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  179. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  180. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  181. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  182. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  183. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  184. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/timeline_paths.py +0 -0
  185. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli/tokens.py +0 -0
  186. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  187. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  188. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli.egg-info/requires.txt +0 -0
  189. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/src/swarph_cli.egg-info/top_level.txt +0 -0
  190. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_124_register_merge.py +0 -0
  191. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_126_pending_from_complete.py +0 -0
  192. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  193. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_138_memory_navigate.py +0 -0
  194. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_139_tool_probe.py +0 -0
  195. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_183_card_anchor.py +0 -0
  196. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_184_notify_honesty.py +0 -0
  197. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_201_win32_bash_refusal.py +0 -0
  198. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_243_onboard_per_peer_token.py +0 -0
  199. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_307_task4_ask_reply.py +0 -0
  200. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  201. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_319_refusal_exit_codes.py +0 -0
  202. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_323_mistral_backend.py +0 -0
  203. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_332_report_organize_findings.py +0 -0
  204. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_356_query_filters.py +0 -0
  205. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_397_gh_identity_router.py +0 -0
  206. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_397_router_preflight.py +0 -0
  207. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_402_one_identity_resolver.py +0 -0
  208. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_467b_onboard_operator_credential.py +0 -0
  209. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_472_register_caller_binding.py +0 -0
  210. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_523_guide.py +0 -0
  211. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_524_conftest_hermeticity.py +0 -0
  212. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_532_accept_flag.py +0 -0
  213. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_535_version_capability.py +0 -0
  214. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_546_default_gateway.py +0 -0
  215. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_547_verbs_from_registry.py +0 -0
  216. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_548_gateway_remainder.py +0 -0
  217. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_552_codegraph_gateway.py +0 -0
  218. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_555_relay_row_shape.py +0 -0
  219. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_556_relay_freshness.py +0 -0
  220. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_559_edges_truncated.py +0 -0
  221. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_564_bootstrap_first_peer.py +0 -0
  222. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  223. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_579_register_bootstrap_message.py +0 -0
  224. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_591_cli_step_graph.py +0 -0
  225. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_644_monitor_supervision.py +0 -0
  226. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_648_install_unit.py +0 -0
  227. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_654_reexec.py +0 -0
  228. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_656_owned_slm.py +0 -0
  229. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_684_dreaming_verb.py +0 -0
  230. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_689_pkg_version_order.py +0 -0
  231. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_716_timeline_one_path.py +0 -0
  232. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  233. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_725_stdio_reconfigure.py +0 -0
  234. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_729_dm_once.py +0 -0
  235. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_729_wake_watchdog.py +0 -0
  236. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  237. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_740_edit_project.py +0 -0
  238. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_762_cards_edit_priority.py +0 -0
  239. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_764_enrich_collapse.py +0 -0
  240. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_801_confirm_flow_cli.py +0 -0
  241. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_802_stage_history.py +0 -0
  242. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_825_codegraph_hook_noise.py +0 -0
  243. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_825_install_posttooluse_only.py +0 -0
  244. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  245. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_829_counterfactual_column.py +0 -0
  246. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_830_hook_binding_version.py +0 -0
  247. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_84_cli_labels.py +0 -0
  248. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_866_doctrine_guide.py +0 -0
  249. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_872_identity_refuses_not_warns.py +0 -0
  250. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_874_source_text.py +0 -0
  251. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  252. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_937_dreaming_notify.py +0 -0
  253. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_938_organize_line_limit.py +0 -0
  254. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_941_bench_backends.py +0 -0
  255. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_941_harness_342.py +0 -0
  256. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_945_same_line_pairing.py +0 -0
  257. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_955_prompt_audit.py +0 -0
  258. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_957_auth_http.py +0 -0
  259. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_959_venv_interpreter.py +0 -0
  260. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_960_channel.py +0 -0
  261. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_963_plugin_manifest.py +0 -0
  262. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_964_channel_cell.py +0 -0
  263. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_965_mesh_wait.py +0 -0
  264. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_975_channel_hop.py +0 -0
  265. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_add_security_gate.py +0 -0
  266. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_artifact_add.py +0 -0
  267. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_artifact_hash.py +0 -0
  268. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_artifact_lib.py +0 -0
  269. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_artifact_mcp.py +0 -0
  270. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_artifact_skill.py +0 -0
  271. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_artifact_tool.py +0 -0
  272. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_artifact_uri.py +0 -0
  273. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_bench_backends.py +0 -0
  274. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_bench_claude_cli.py +0 -0
  275. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_bench_command.py +0 -0
  276. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_bench_pack.py +0 -0
  277. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_bench_prices.py +0 -0
  278. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_bench_quality.py +0 -0
  279. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_bench_runner.py +0 -0
  280. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_bench_validate.py +0 -0
  281. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_board_card_thread.py +0 -0
  282. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_board_command.py +0 -0
  283. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_board_merge_check.py +0 -0
  284. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_board_obligation_close.py +0 -0
  285. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_brain_ask_command.py +0 -0
  286. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_brain_ask_env_diagnosis.py +0 -0
  287. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_brain_ask_identity_explicitness.py +0 -0
  288. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_brain_command.py +0 -0
  289. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_caller_meta_guard.py +0 -0
  290. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_capture_lineage.py +0 -0
  291. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_capture_liveness.py +0 -0
  292. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_capture_manifest.py +0 -0
  293. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_capture_paths.py +0 -0
  294. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_capture_security.py +0 -0
  295. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_cell_command_dispatch.py +0 -0
  296. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_cell_harden.py +0 -0
  297. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_cell_loader.py +0 -0
  298. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_cell_selfcheck.py +0 -0
  299. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_cell_verify.py +0 -0
  300. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_chain_token.py +0 -0
  301. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_channel_command.py +0 -0
  302. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  303. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_channel_post.py +0 -0
  304. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_channel_wake_policy_filter.py +0 -0
  305. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_chat_command.py +0 -0
  306. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_claude_parser.py +0 -0
  307. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_claude_tmux_template.py +0 -0
  308. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_codegraph_caller_scope.py +0 -0
  309. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_codegraph_command.py +0 -0
  310. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_codegraph_hook.py +0 -0
  311. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_codegraph_mcp.py +0 -0
  312. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_codex_waker.py +0 -0
  313. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_compress_caller_convention.py +0 -0
  314. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_compress_command.py +0 -0
  315. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_compress_levers.py +0 -0
  316. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_compress_marker.py +0 -0
  317. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_compress_verify.py +0 -0
  318. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_console_encoding_e2e.py +0 -0
  319. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_content_composition.py +0 -0
  320. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_cursor_membrane.py +0 -0
  321. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_daemon_command.py +0 -0
  322. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_daemon_console_encoding.py +0 -0
  323. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_daemon_delivery.py +0 -0
  324. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_delivery_queue.py +0 -0
  325. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_display_sanitize.py +0 -0
  326. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  327. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_event_emit.py +0 -0
  328. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_feature_to_uri.py +0 -0
  329. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_gateway_brain.py +0 -0
  330. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_gateway_client.py +0 -0
  331. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_gateway_command.py +0 -0
  332. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_gateway_message_idempotency.py +0 -0
  333. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  334. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_group_command.py +0 -0
  335. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_guide_commands_parse.py +0 -0
  336. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_guide_selfcheck.py +0 -0
  337. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_heartbeat_check.py +0 -0
  338. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_highlight_command.py +0 -0
  339. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hook_output.py +0 -0
  340. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_activity_marker.py +0 -0
  341. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_add.py +0 -0
  342. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_bundle.py +0 -0
  343. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_init.py +0 -0
  344. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_legacy_generation_removal.py +0 -0
  345. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_lifecycle.py +0 -0
  346. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_merge.py +0 -0
  347. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_verify.py +0 -0
  348. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  349. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_hooks_win32_command_path.py +0 -0
  350. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_import_command.py +0 -0
  351. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_inbox_identity_provenance.py +0 -0
  352. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_init_command.py +0 -0
  353. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_init_provider_menu.py +0 -0
  354. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_install_codex_hooks.py +0 -0
  355. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_install_hook.py +0 -0
  356. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_install_multiplexer_command.py +0 -0
  357. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_install_postcompact_hook.py +0 -0
  358. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_install_wake_hook.py +0 -0
  359. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_lane_command.py +0 -0
  360. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_main.py +0 -0
  361. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_main_unknown_verb_guard.py +0 -0
  362. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mcp_missing_hint.py +0 -0
  363. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mcp_server.py +0 -0
  364. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_memory_command.py +0 -0
  365. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_memory_emit_snippet.py +0 -0
  366. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_memory_gateway.py +0 -0
  367. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_memory_sync.py +0 -0
  368. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_memory_sync_membrane.py +0 -0
  369. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mesh_command.py +0 -0
  370. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mesh_inbox_sanitize.py +0 -0
  371. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mesh_originate_thread_623.py +0 -0
  372. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mesh_send_unknown_peer.py +0 -0
  373. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mesh_sidecar.py +0 -0
  374. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  375. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  376. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_meta_edge_identity.py +0 -0
  377. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_monitor_channel_poll.py +0 -0
  378. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_monitor_cli.py +0 -0
  379. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_monitor_daemon_collision.py +0 -0
  380. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_monitor_self_name_verification.py +0 -0
  381. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_multiplexer.py +0 -0
  382. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_muse_membrane.py +0 -0
  383. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_muse_own_cli.py +0 -0
  384. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_obligation_close_is_surfaced.py +0 -0
  385. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_okf_links.py +0 -0
  386. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_onboard_command.py +0 -0
  387. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_opencode_membrane.py +0 -0
  388. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_opencode_plugin.py +0 -0
  389. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_orphan_daemons.py +0 -0
  390. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_packaged_artifacts_exist.py +0 -0
  391. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_packaged_skills.py +0 -0
  392. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_executor.py +0 -0
  393. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_executor_windows.py +0 -0
  394. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_reply_drain_command.py +0 -0
  395. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_reply_drain_supervisor.py +0 -0
  396. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_reply_outbox.py +0 -0
  397. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_reply_transport.py +0 -0
  398. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_service_e2e.py +0 -0
  399. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_service_host.py +0 -0
  400. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_service_payload.py +0 -0
  401. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_peer_service_reply.py +0 -0
  402. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_postcompact_recall.py +0 -0
  403. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_process_liveness.py +0 -0
  404. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_protocol_handler.py +0 -0
  405. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_providers_isolation.py +0 -0
  406. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_ratify_command.py +0 -0
  407. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_readme_claims_no_gateway_default.py +0 -0
  408. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_repl_caller_convention.py +0 -0
  409. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_schedule_command.py +0 -0
  410. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_schedule_create_contract.py +0 -0
  411. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_security.py +0 -0
  412. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_service_app.py +0 -0
  413. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_service_command.py +0 -0
  414. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_service_providers.py +0 -0
  415. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_session_bridge_inject.py +0 -0
  416. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_session_bridge_probe.py +0 -0
  417. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_session_bridge_resolve.py +0 -0
  418. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_session_path_role_gate.py +0 -0
  419. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_shipped_units_match_the_cli.py +0 -0
  420. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_smoke_chat.py +0 -0
  421. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_smoke_one_shot.py +0 -0
  422. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_smoke_phase_5_5.py +0 -0
  423. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_spawn_base_hoist.py +0 -0
  424. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_spawn_command.py +0 -0
  425. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_spawn_identity_stamp.py +0 -0
  426. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_spawn_live_pin.py +0 -0
  427. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_spawn_mitosis_lineage.py +0 -0
  428. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_spawn_tmux_session.py +0 -0
  429. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_spawn_windows_relaunch.py +0 -0
  430. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_subprocess_decode_encoding.py +0 -0
  431. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_timeline_command.py +0 -0
  432. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_tmux_wake_runstate_defer.py +0 -0
  433. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_tmux_wake_session_reanchor.py +0 -0
  434. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_tmux_wake_stale.py +0 -0
  435. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_tmux_wake_submit_verify.py +0 -0
  436. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_token_file_one_parser.py +0 -0
  437. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_token_precedence_explicit_first.py +0 -0
  438. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_unified_token_resolver.py +0 -0
  439. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_version_command.py +0 -0
  440. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_version_consistency.py +0 -0
  441. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_vibe_membrane.py +0 -0
  442. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_watchdog.py +0 -0
  443. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_watchdog_dm_wake.py +0 -0
  444. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  445. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  446. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_watchdog_emit_health.py +0 -0
  447. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_watchdog_install_flags.py +0 -0
  448. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_watchdog_model_rung.py +0 -0
  449. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/tests/test_watchdog_stale_peers.py +0 -0
  450. {swarph_cli-0.69.4 → swarph_cli-0.69.5}/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.69.4
3
+ Version: 0.69.5
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.69.4"
7
+ version = "0.69.5"
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.69.4"
19
+ __version__ = "0.69.5"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -381,8 +381,12 @@ def _legacy_attempt_delivery_removed(state: DaemonState) -> None:
381
381
  entries = state.queue.pending()
382
382
  block = _render_delivery_block(entries)
383
383
  if session_bridge.inject(pane, block):
384
+ stalled = state.queue.deferred_ticks
384
385
  state.queue.remove({e["id"] for e in entries})
385
386
  state.queue.reset_deferred()
387
+ if stalled:
388
+ stall_alert.clear_stall_alert(
389
+ state.gateway, state.token, state.self_name)
386
390
  # inject failure → leave queued, retry next tick (no counter bump —
387
391
  # the cell was idle; a send failure is transient, not a stall).
388
392
  except Exception as exc: # noqa: BLE001 — bridge must never crash the loop
@@ -344,14 +344,15 @@ def _dm_reply(message_id: int, text: str, kind: str = "answer",
344
344
  st, d = _mesh._post_json(f"{gw}/messages", body, tok)
345
345
  if not (200 <= st < 300):
346
346
  return {"error": f"gateway {st}: {d.get('detail', d) if isinstance(d, dict) else d}"}
347
- ack_st, _ = _mesh._post_json(f"{gw}/messages/{int(message_id)}/read", {}, tok)
347
+ failed = _mesh._mark_read(gw, tok, [{"id": int(message_id)}])
348
+ acked = int(message_id) not in failed
348
349
  return {
349
350
  "id": d.get("id"),
350
351
  "to_node": to,
351
352
  "kind": kind,
352
353
  "thread_id": d.get("thread_id"),
353
- "acked": int(message_id) if 200 <= ack_st < 300 else None,
354
- "ack_status": ack_st,
354
+ "acked": int(message_id) if acked else None,
355
+ "ack_status": 200 if acked else 0,
355
356
  "closed_obligations": d.get("closed_obligations") or [],
356
357
  }
357
358
  except Exception as exc:
@@ -944,38 +944,58 @@ def _run_reply(args: argparse.Namespace) -> int:
944
944
  return 0
945
945
 
946
946
 
947
- def _mark_read(gateway: str, token: str, messages: list) -> None:
947
+ _BULK_READ_MAX = 500
948
+
949
+
950
+ def _mark_read(gateway: str, token: str, messages: list) -> list:
948
951
  """Mark every unread DM we just surfaced as read. Best-effort.
949
952
 
950
- Reading your inbox consumes it — like any mail client. Without this, a peer's
951
- request stays "unread" no matter how many times it is answered, and an agent
952
- whose loop asks "is it still unread?" can never exit. That is not theoretical:
953
- on 2026-07-10 grok-researcher answered one request 67 times (~410k tokens)
954
- because the CLI never called the gateway's POST /messages/{id}/read.
953
+ One POST /messages/read per 500 ids. The gateway's single worker spends a
954
+ synchronous sqlite write on every request, so a per-id loop is how a drain
955
+ turns into HTTP 503. A 404 means that route is not on this gateway yet, and
956
+ only then does this fall back to one POST per id.
955
957
 
956
958
  A mark-read failure must NEVER fail the listing — the caller has already seen
957
959
  the messages, and losing the read receipt is strictly less bad than losing them.
958
960
  """
959
961
  base = gateway.rstrip("/")
960
- failed = []
962
+ ids = []
961
963
  for dm in messages:
962
- if dm.get("read_at"):
963
- continue
964
- msg_id = dm.get("id")
964
+ if isinstance(dm, dict):
965
+ if dm.get("read_at"):
966
+ continue
967
+ msg_id = dm.get("id")
968
+ else:
969
+ msg_id = dm
965
970
  if msg_id is None:
966
971
  continue
972
+ ids.append(msg_id)
973
+ failed = []
974
+ for start in range(0, len(ids), _BULK_READ_MAX):
975
+ chunk = ids[start:start + _BULK_READ_MAX]
967
976
  try:
968
- status, _ = _post_json(f"{base}/messages/{msg_id}/read", {}, token)
969
- if status < 200 or status >= 300:
970
- failed.append(msg_id)
977
+ status, _ = _post_json(f"{base}/messages/read", {"ids": chunk}, token)
971
978
  except Exception: # network, DNS, timeout — never break the listing
972
- failed.append(msg_id)
979
+ failed.extend(chunk)
980
+ continue
981
+ if status == 404:
982
+ for msg_id in chunk:
983
+ try:
984
+ one, _ = _post_json(f"{base}/messages/{msg_id}/read", {}, token)
985
+ if one < 200 or one >= 300:
986
+ failed.append(msg_id)
987
+ except Exception:
988
+ failed.append(msg_id)
989
+ continue
990
+ if status < 200 or status >= 300:
991
+ failed.extend(chunk)
973
992
  if failed:
974
993
  print(
975
994
  f"swarph mesh inbox: mark-read failed for id(s) {failed} "
976
995
  "(messages shown above; they will be re-listed as unread)",
977
996
  file=sys.stderr,
978
997
  )
998
+ return failed
979
999
 
980
1000
 
981
1001
  def _run_inbox(args: argparse.Namespace) -> int:
@@ -2673,7 +2693,8 @@ def _monitor_deliver(state: MonitorState) -> None:
2673
2693
  f"no failure counted",
2674
2694
  flush=True)
2675
2695
  continue
2676
- if led.get("deferred_ticks"):
2696
+ was_stalled = bool(led.get("deferred_ticks"))
2697
+ if was_stalled:
2677
2698
  led["deferred_ticks"] = 0
2678
2699
  if outcome:
2679
2700
  led["last_delivered_id"] = observed
@@ -2682,6 +2703,10 @@ def _monitor_deliver(state: MonitorState) -> None:
2682
2703
  state.deliveries[sink.name] = state.deliveries.get(sink.name, 0) + 1
2683
2704
  print(f"{state.log_prefix} delivered to {sink.name} up to id {observed}",
2684
2705
  flush=True)
2706
+ if was_stalled:
2707
+ from swarph_cli import stall_alert
2708
+ stall_alert.clear_stall_alert(
2709
+ state.gateway, state.token, state.self_name)
2685
2710
  else:
2686
2711
  # A dead sink is VISIBLE instead of silently freezing anything.
2687
2712
  led["consecutive_failures"] = int(led["consecutive_failures"]) + 1
@@ -64,12 +64,71 @@ STATE_DIR="${1:-${SWARPH_STATE_DIR:-}}"
64
64
  SD=()
65
65
  [ -n "$STATE_DIR" ] && SD=(--state-dir "$STATE_DIR")
66
66
 
67
+ # >>> #960: a fresh channel heartbeat is already a reader. <<<
68
+ # swarph channel-serve writes channel_heartbeat.json beside the inbox every
69
+ # 60s and names no path on its command line, so `monitor status` cannot see
70
+ # it. Starting a pull monitor beside a heartbeat younger than 180s is the
71
+ # duplicate this script exists to prevent. Absent or older: start, as before.
72
+ # A missing python3 is treated as absent (start), never as a failed caller.
73
+ _heartbeat_fresh() {
74
+ local f="$1"
75
+ [ -n "$f" ] && [ -f "$f" ] || return 1
76
+ command -v python3 >/dev/null 2>&1 || return 1
77
+ python3 -c 'import json,os,sys,time
78
+ try:
79
+ data=json.load(open(sys.argv[1],encoding="utf-8"))
80
+ ts=float(data.get("ts") or 0)
81
+ except Exception:
82
+ raise SystemExit(1)
83
+ age=time.time()-ts
84
+ # A clock a few seconds ahead is still a live writer. An hour ahead is not.
85
+ if age < -30 or age >= 180:
86
+ raise SystemExit(1)
87
+ pid=data.get("pid")
88
+ if pid is None:
89
+ raise SystemExit(0)
90
+ try:
91
+ pid=int(pid)
92
+ except (TypeError, ValueError):
93
+ raise SystemExit(1)
94
+ # pid 0 is this process group and pid -1 is every process we can signal.
95
+ # Neither is the channel server. os.kill would report both as alive.
96
+ if pid <= 0:
97
+ raise SystemExit(1)
98
+ try:
99
+ os.kill(pid, 0)
100
+ except PermissionError:
101
+ pass
102
+ except OSError:
103
+ raise SystemExit(1)
104
+ try:
105
+ raw=open("/proc/%s/cmdline" % pid, "rb").read()
106
+ except OSError:
107
+ # No command line to read (macOS, or a pid we may not inspect).
108
+ # A live pid still counts, which is the check this host can make.
109
+ raise SystemExit(0)
110
+ text=raw.replace(b"\x00", b" ").decode("utf-8", "replace")
111
+ if "channel-serve" in text or "swarph_cli.channel" in text:
112
+ raise SystemExit(0)
113
+ raise SystemExit(1)' "$f"
114
+ }
115
+
116
+ HB_ROOT="${STATE_DIR:-$HOME/swarph_state/$SELF}"
117
+ HB_FILE=""
118
+ if [ -f "$HB_ROOT/channel_heartbeat.json" ]; then
119
+ HB_FILE="$HB_ROOT/channel_heartbeat.json"
120
+ elif [ -f "$HB_ROOT/mesh-sidecar/channel_heartbeat.json" ]; then
121
+ HB_FILE="$HB_ROOT/mesh-sidecar/channel_heartbeat.json"
122
+ fi
123
+
67
124
  "$SW" monitor status --as "$SELF" ${SD[@]+"${SD[@]}"} >/dev/null 2>&1
68
125
  case $? in
69
126
  0) ;; # running, nothing pending
70
127
  1) ;; # running, DMs pending — reported below
71
- *) # not running (2) or unknown: start it. Idempotent, so a race is harmless.
72
- if "$SW" monitor start --as "$SELF" --deliver pull ${SD[@]+"${SD[@]}"} >/tmp/.swarph-monitor-start.$$ 2>&1; then
128
+ *) # not running (2) or unknown.
129
+ if _heartbeat_fresh "$HB_FILE"; then
130
+ echo "[monitor] channel heartbeat is under 180s old — not starting a monitor"
131
+ elif "$SW" monitor start --as "$SELF" --deliver pull ${SD[@]+"${SD[@]}"} >/tmp/.swarph-monitor-start.$$ 2>&1; then
73
132
  echo "[monitor] started (--deliver pull)"
74
133
  else
75
134
  # LOUD, not silent: an auto-start that fails quietly rebuilds the exact
@@ -0,0 +1,144 @@
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
3
+ the linear-flood failure (the 145-DM incident, feedback_modal_stalls_cell_wake).
4
+
5
+ Card #989: the surface is one obligation held by lab-ovh, never a DM to
6
+ commander. A second stall tick while that row is open opens nothing new. A
7
+ drain closes it. Fail-safe: a failed POST never raises and never blocks
8
+ delivery."""
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ import sys
13
+ import urllib.error
14
+ import urllib.parse
15
+ import urllib.request
16
+ from swarph_cli.console_safe import print_safe
17
+
18
+ _STALL_FIRST = 6 # first alert after this many consecutive deferred ticks
19
+ _CARD = 989
20
+ _HOLDER = "lab-ovh"
21
+ # lab-ovh's own stall cannot escalate to lab-ovh. Another orchestrator holds it.
22
+ _OTHER = "drop-on-meta-edge"
23
+ _TIMEOUT_HOURS = 24
24
+ _PREFIX = f"STALL card #{_CARD}"
25
+
26
+
27
+ def is_alert_tick(deferred_ticks: int) -> bool:
28
+ """True exactly at 6, 12, 24, 48, 96 … — first at _STALL_FIRST, doubling."""
29
+ if deferred_ticks < _STALL_FIRST or deferred_ticks % _STALL_FIRST != 0:
30
+ return False
31
+ k = deferred_ticks // _STALL_FIRST
32
+ return (k & (k - 1)) == 0 # k is a power of two
33
+
34
+
35
+ def _accept(self_name: str, count: int, pending_n: int) -> str:
36
+ return (
37
+ f"{_PREFIX} cell={self_name} ticks={count} pending={pending_n} | "
38
+ "PASS = the cell drained and this row is closed | "
39
+ "FAIL = the cell is still unable to deliver"
40
+ )
41
+
42
+
43
+ def _what(self_name: str, count: int, pending_n: int) -> str:
44
+ return (
45
+ f"{_PREFIX}\n"
46
+ f"cell={self_name} ticks={count} pending_dms={pending_n}"
47
+ )
48
+
49
+
50
+ def _open_url(req, timeout):
51
+ host = urllib.parse.urlparse(req.full_url).hostname
52
+ if host in {"127.0.0.1", "localhost", "::1"}:
53
+ return urllib.request.build_opener(
54
+ urllib.request.ProxyHandler({})).open(req, timeout=timeout)
55
+ return urllib.request.urlopen(req, timeout=timeout)
56
+
57
+
58
+ def _request(method: str, url: str, token: str, payload: dict | None = None) -> dict:
59
+ data = None if payload is None else json.dumps(payload).encode()
60
+ req = urllib.request.Request(
61
+ url, data=data, method=method,
62
+ headers={"Authorization": f"Bearer {token}",
63
+ "Content-Type": "application/json"})
64
+ with _open_url(req, timeout=10) as resp:
65
+ raw = resp.read().decode()
66
+ if not (200 <= resp.status < 300):
67
+ raise urllib.error.URLError(f"HTTP {resp.status}")
68
+ return json.loads(raw) if raw else {}
69
+
70
+
71
+ def _holder_for(self_name: str) -> str:
72
+ return _OTHER if self_name == _HOLDER else _HOLDER
73
+
74
+
75
+ def _cell_row(row: dict, self_name: str) -> bool:
76
+ needle = f"cell={self_name} "
77
+ return needle in (row.get("accept") or "") or needle in (row.get("what") or "")
78
+
79
+
80
+ def _live(gateway: str, token: str, self_name: str) -> list[dict]:
81
+ rows: list[dict] = []
82
+ for status in ("open", "fallback_fired"):
83
+ data = _request(
84
+ "GET",
85
+ f"{gateway}/board/obligations?card_id={_CARD}&status={status}",
86
+ token)
87
+ rows.extend(data.get("obligations") or [])
88
+ seen = set()
89
+ live = []
90
+ for row in rows:
91
+ if row.get("id") in seen:
92
+ continue
93
+ seen.add(row.get("id"))
94
+ if _cell_row(row, self_name):
95
+ live.append(row)
96
+ return live
97
+
98
+
99
+ def send_stall_alert(gateway: str, token: str, self_name: str,
100
+ count: int, pending_n: int) -> bool:
101
+ """Open one obligation on card #989 held by lab-ovh. True when that row
102
+ is open (newly or already). False on any error. Never raises, never
103
+ DMs commander, and a second call while the row is open opens nothing."""
104
+ try:
105
+ gateway = gateway.rstrip("/")
106
+ if _live(gateway, token, self_name):
107
+ return True
108
+ holder = _holder_for(self_name)
109
+ _request(
110
+ "POST", f"{gateway}/board/cards/{_CARD}/ask", token,
111
+ {"holder": holder,
112
+ "what": _what(self_name, count, pending_n),
113
+ "created_by": self_name,
114
+ "kind": "action",
115
+ "fallback_mode": "escalate",
116
+ "fallback_target": holder,
117
+ "timeout_hours": _TIMEOUT_HOURS,
118
+ "accept": _accept(self_name, count, pending_n)})
119
+ return True
120
+ except (urllib.error.URLError, OSError, ValueError, json.JSONDecodeError) as exc:
121
+ print_safe(f"[swarph-daemon] stall-alert POST failed: {exc}",
122
+ file=sys.stderr, flush=True)
123
+ return False
124
+
125
+
126
+ def clear_stall_alert(gateway: str, token: str, self_name: str) -> bool:
127
+ """Close the open stall row after a drain. True when none remain open.
128
+ False on any error. Never raises."""
129
+ evidence = (
130
+ f"{_PREFIX} cell={self_name} drained. "
131
+ "The deferred delivery completed, so this row is closed."
132
+ )
133
+ try:
134
+ gateway = gateway.rstrip("/")
135
+ live = _live(gateway, token, self_name)
136
+ for row in live:
137
+ _request(
138
+ "POST", f"{gateway}/board/obligations/{row['id']}/close", token,
139
+ {"outcome": "pass", "evidence": evidence})
140
+ return True
141
+ except (urllib.error.URLError, OSError, ValueError, json.JSONDecodeError) as exc:
142
+ print_safe(f"[swarph-daemon] stall-alert close failed: {exc}",
143
+ file=sys.stderr, flush=True)
144
+ return False
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.69.4
3
+ Version: 0.69.5
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
@@ -250,6 +250,7 @@ tests/test_955_prompt_audit.py
250
250
  tests/test_957_auth_http.py
251
251
  tests/test_959_venv_interpreter.py
252
252
  tests/test_960_channel.py
253
+ tests/test_960_ensure_monitor_heartbeat.py
253
254
  tests/test_963_plugin_manifest.py
254
255
  tests/test_964_channel_cell.py
255
256
  tests/test_965_mesh_wait.py
@@ -143,8 +143,8 @@ def test_reply_is_the_ack_and_only_that_message(monkeypatch):
143
143
  assert posts[0][0] == "http://gw/messages" and posts[0][1] == {
144
144
  "from_node": "lab-ovh", "to_node": "cursor-lin", "kind": "answer",
145
145
  "content": "on it — thread 81570cab", "thread_id": "81570cab-0000-4000-8000-000000000001"}
146
- reads = [u for u, _ in posts if u.endswith("/read")]
147
- assert reads == ["http://gw/messages/32004/read"], reads # exactly one ack, the replied-to one
146
+ reads = [(u, b) for u, b in posts if str(u).endswith("/read")]
147
+ assert reads == [("http://gw/messages/read", {"ids": [32004]})], reads
148
148
  assert mcp_server._dm_reply(32004, " ")["error"] == "empty reply text"
149
149
 
150
150
 
@@ -0,0 +1,166 @@
1
+ """#960: a fresh channel heartbeat is a reader. Do not start a monitor beside it.
2
+
3
+ channel-serve writes channel_heartbeat.json beside the inbox and puts nothing
4
+ on its command line, so monitor status reports "not running" while the channel
5
+ is alive. On main, that status is enough to start a second reader. The head
6
+ starts only when the file is absent or older than 180s.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import json
11
+ import os
12
+ import shutil
13
+ import subprocess
14
+ import sys
15
+ import time
16
+ from pathlib import Path
17
+
18
+ import pytest
19
+
20
+ SCRIPT = (
21
+ Path(__file__).resolve().parent.parent
22
+ / "src" / "swarph_cli" / "scripts" / "ensure_monitor.sh"
23
+ )
24
+
25
+ FAKE = """#!/usr/bin/env bash
26
+ printf '%s\\n' "$*" >> "$FAKE_LOG"
27
+ exit 2
28
+ """
29
+
30
+
31
+ def _bash():
32
+ if sys.platform == "win32":
33
+ pytest.skip("windows-latest bash is the WSL stub")
34
+ found = shutil.which("bash")
35
+ if not found:
36
+ pytest.skip("no bash")
37
+ return found
38
+
39
+
40
+ def _run(tmp_path: Path, *, age: float | None):
41
+ bindir = tmp_path / "bin"
42
+ bindir.mkdir()
43
+ shim = bindir / "swarph"
44
+ shim.write_text(FAKE, encoding="utf-8")
45
+ shim.chmod(0o755)
46
+ log = tmp_path / "calls.log"
47
+ state = tmp_path / "state"
48
+ side = state / "mesh-sidecar"
49
+ side.mkdir(parents=True)
50
+ if age is not None:
51
+ (side / "channel_heartbeat.json").write_text(
52
+ json.dumps({"ts": time.time() - age}), encoding="utf-8")
53
+ env = dict(os.environ)
54
+ env["PATH"] = f"{bindir}{os.pathsep}{env.get('PATH', '')}"
55
+ env["SWARPH_SELF"] = "cursor-lin"
56
+ env["SWARPH_STATE_DIR"] = str(state)
57
+ env["FAKE_LOG"] = str(log)
58
+ proc = subprocess.run(
59
+ [_bash(), str(SCRIPT)], env=env, capture_output=True, text=True, timeout=60)
60
+ calls = log.read_text(encoding="utf-8") if log.exists() else ""
61
+ return proc, calls
62
+
63
+
64
+ def test_fresh_heartbeat_does_not_start(tmp_path):
65
+ proc, calls = _run(tmp_path, age=10)
66
+ assert proc.returncode == 0
67
+ assert "monitor start" not in calls
68
+ assert "under 180s" in proc.stdout
69
+
70
+
71
+ def test_stale_heartbeat_still_starts(tmp_path):
72
+ proc, calls = _run(tmp_path, age=200)
73
+ assert proc.returncode == 0
74
+ assert "monitor start" in calls
75
+
76
+
77
+ def test_absent_heartbeat_still_starts(tmp_path):
78
+ proc, calls = _run(tmp_path, age=None)
79
+ assert proc.returncode == 0
80
+ assert "monitor start" in calls
81
+
82
+
83
+ def _run_payload(tmp_path: Path, payload: dict):
84
+ bindir = tmp_path / "bin"
85
+ bindir.mkdir()
86
+ shim = bindir / "swarph"
87
+ shim.write_text(FAKE, encoding="utf-8")
88
+ shim.chmod(0o755)
89
+ log = tmp_path / "calls.log"
90
+ side = tmp_path / "state" / "mesh-sidecar"
91
+ side.mkdir(parents=True)
92
+ (side / "channel_heartbeat.json").write_text(json.dumps(payload), encoding="utf-8")
93
+ env = dict(os.environ)
94
+ env["PATH"] = f"{bindir}{os.pathsep}{env.get('PATH', '')}"
95
+ env["SWARPH_SELF"] = "cursor-lin"
96
+ env["SWARPH_STATE_DIR"] = str(tmp_path / "state")
97
+ env["FAKE_LOG"] = str(log)
98
+ proc = subprocess.run(
99
+ [_bash(), str(SCRIPT)], env=env, capture_output=True, text=True, timeout=60)
100
+ calls = log.read_text(encoding="utf-8") if log.exists() else ""
101
+ return proc, calls
102
+
103
+
104
+ def test_a_future_timestamp_an_hour_ahead_starts(tmp_path):
105
+ proc, calls = _run_payload(tmp_path, {"ts": time.time() + 3600})
106
+ assert proc.returncode == 0
107
+ assert "monitor start" in calls
108
+
109
+
110
+ def test_a_timestamp_within_skew_stays_fresh(tmp_path):
111
+ proc, calls = _run_payload(tmp_path, {"ts": time.time() + 10})
112
+ assert proc.returncode == 0
113
+ assert "monitor start" not in calls
114
+
115
+
116
+ def test_a_fresh_heartbeat_with_a_dead_pid_starts(tmp_path):
117
+ # Above any host pid_max. macOS has no /proc, so the dead pid cannot come from there.
118
+ proc, calls = _run_payload(tmp_path, {"ts": time.time(), "pid": 2**31 - 1})
119
+ assert proc.returncode == 0
120
+ assert "monitor start" in calls
121
+
122
+
123
+ def test_a_fresh_heartbeat_with_a_live_pid_skips(tmp_path):
124
+ """A live channel-server process still suppresses the start.
125
+
126
+ Where /proc is missing the command line cannot be checked, so any live pid
127
+ still counts, which is what this host can see.
128
+ """
129
+ child = None
130
+ if os.path.isdir("/proc/self"):
131
+ child = subprocess.Popen(
132
+ [sys.executable, "-c", "import time; time.sleep(30)", "channel-serve"])
133
+ pid = child.pid
134
+ else:
135
+ pid = os.getpid()
136
+ try:
137
+ proc, calls = _run_payload(tmp_path, {"ts": time.time(), "pid": pid})
138
+ finally:
139
+ if child is not None:
140
+ child.kill()
141
+ child.wait(timeout=5)
142
+ assert proc.returncode == 0
143
+ assert "monitor start" not in calls
144
+
145
+
146
+ def test_pid_zero_starts(tmp_path):
147
+ proc, calls = _run_payload(tmp_path, {"ts": time.time(), "pid": 0})
148
+ assert proc.returncode == 0
149
+ assert "monitor start" in calls
150
+
151
+
152
+ def test_pid_minus_one_starts(tmp_path):
153
+ proc, calls = _run_payload(tmp_path, {"ts": time.time(), "pid": -1})
154
+ assert proc.returncode == 0
155
+ assert "monitor start" in calls
156
+
157
+
158
+ def test_an_unrelated_live_pid_starts_when_its_command_line_is_readable(tmp_path):
159
+ if not os.path.isdir("/proc/self"):
160
+ pytest.skip("command-line check is not available")
161
+ parent = os.getppid()
162
+ text = open(f"/proc/{parent}/cmdline", "rb").read().replace(b"\x00", b" ").decode()
163
+ assert "channel-serve" not in text
164
+ proc, calls = _run_payload(tmp_path, {"ts": time.time(), "pid": parent})
165
+ assert proc.returncode == 0
166
+ assert "monitor start" in calls