swarph-cli 0.69.7__tar.gz → 0.69.9__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 (454) hide show
  1. {swarph_cli-0.69.7/src/swarph_cli.egg-info → swarph_cli-0.69.9}/PKG-INFO +1 -1
  2. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/pyproject.toml +1 -1
  3. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/monitor.py +32 -3
  5. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/ensure_monitor.sh +9 -61
  6. swarph_cli-0.69.9/src/swarph_cli/scripts/wake_watchdog.py +385 -0
  7. {swarph_cli-0.69.7 → swarph_cli-0.69.9/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  8. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/SOURCES.txt +2 -0
  9. swarph_cli-0.69.9/tests/test_1006_session_board.py +291 -0
  10. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_644_monitor_supervision.py +32 -0
  11. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_729_wake_watchdog.py +2 -0
  12. swarph_cli-0.69.9/tests/test_739_wake_writer.py +431 -0
  13. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_960_ensure_monitor_heartbeat.py +47 -16
  14. swarph_cli-0.69.7/src/swarph_cli/scripts/wake_watchdog.py +0 -209
  15. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/LICENSE +0 -0
  16. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/README.md +0 -0
  17. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/setup.cfg +0 -0
  18. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/__main__.py +0 -0
  19. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/__init__.py +0 -0
  20. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/backends.py +0 -0
  21. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/claude_cli.py +0 -0
  22. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/pack.py +0 -0
  23. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/prices.py +0 -0
  24. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/providers.py +0 -0
  25. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/quality.py +0 -0
  26. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/refresh_prices.py +0 -0
  27. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/rules.py +0 -0
  28. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/runner.py +0 -0
  29. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/bench/validate.py +0 -0
  30. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/caller.py +0 -0
  31. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/__init__.py +0 -0
  32. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/harden.py +0 -0
  33. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/lineage.py +0 -0
  34. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/liveness.py +0 -0
  35. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/manifest.py +0 -0
  36. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/paths.py +0 -0
  37. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/capture/verify.py +0 -0
  38. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/cell.py +0 -0
  39. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/chain_token.py +0 -0
  40. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/channel.py +0 -0
  41. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/__init__.py +0 -0
  42. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/_content.py +0 -0
  43. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/_display.py +0 -0
  44. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/_gateway_client.py +0 -0
  45. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/add.py +0 -0
  46. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/bench.py +0 -0
  47. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/board.py +0 -0
  48. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/board_merge_check.py +0 -0
  49. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/brain.py +0 -0
  50. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/brain_ask.py +0 -0
  51. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/cell.py +0 -0
  52. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/cell_probe.py +0 -0
  53. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  54. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/channel.py +0 -0
  55. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/chat.py +0 -0
  56. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/codegraph.py +0 -0
  57. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  58. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  59. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/codex_waker.py +0 -0
  60. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/compress.py +0 -0
  61. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/daemon.py +0 -0
  62. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/dreaming.py +0 -0
  63. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/event.py +0 -0
  64. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/gateway.py +0 -0
  65. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/gh_route.py +0 -0
  66. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/group.py +0 -0
  67. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/guide.py +0 -0
  68. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/highlight.py +0 -0
  69. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  70. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/hook_output.py +0 -0
  71. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/hooks.py +0 -0
  72. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/import_session.py +0 -0
  73. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/init.py +0 -0
  74. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  75. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_hook.py +0 -0
  76. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  77. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  78. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  79. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  80. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/lane.py +0 -0
  81. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/mcp_server.py +0 -0
  82. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/memory.py +0 -0
  83. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  84. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/memory_sync.py +0 -0
  85. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/mesh.py +0 -0
  86. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/okf_links.py +0 -0
  87. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/onboard.py +0 -0
  88. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  89. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  90. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/protocol_handler.py +0 -0
  91. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/ratify.py +0 -0
  92. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/schedule.py +0 -0
  93. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/security.py +0 -0
  94. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/service.py +0 -0
  95. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/spawn.py +0 -0
  96. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/timeline.py +0 -0
  97. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/version.py +0 -0
  98. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  99. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/commands/watchdog.py +0 -0
  100. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/compress/__init__.py +0 -0
  101. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/compress/levers.py +0 -0
  102. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/compress/marker.py +0 -0
  103. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/compress/verify.py +0 -0
  104. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/console_safe.py +0 -0
  105. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/delivery_queue.py +0 -0
  106. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dm_frame.py +0 -0
  107. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/__init__.py +0 -0
  108. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/candidates.py +0 -0
  109. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/clone.py +0 -0
  110. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/enrich.py +0 -0
  111. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/index_check.py +0 -0
  112. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/notify.py +0 -0
  113. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/organize.py +0 -0
  114. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/probes.py +0 -0
  115. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/report.py +0 -0
  116. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/run.py +0 -0
  117. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/slm.py +0 -0
  118. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/transcripts.py +0 -0
  119. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/dreaming/verify.py +0 -0
  120. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/__init__.py +0 -0
  121. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/feature_registry.py +0 -0
  122. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/lanes_control.py +0 -0
  123. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/schema.sql +0 -0
  124. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/server.py +0 -0
  125. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway/services_control.py +0 -0
  126. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gateway_default.py +0 -0
  127. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/gh_identity.py +0 -0
  128. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/guide/GUIDE.md +0 -0
  129. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/identity.py +0 -0
  130. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/main.py +0 -0
  131. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/multiplexer.py +0 -0
  132. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/orphan_daemons.py +0 -0
  133. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/pane_probe.py +0 -0
  134. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/parsers/__init__.py +0 -0
  135. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/parsers/claude.py +0 -0
  136. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  137. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  138. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  139. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  140. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  141. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  142. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  143. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  144. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  145. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  146. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  147. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  148. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_executor.py +0 -0
  149. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_executor_windows.py +0 -0
  150. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_reconciliation.py +0 -0
  151. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_reply_outbox.py +0 -0
  152. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_service_host.py +0 -0
  153. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_service_payload.py +0 -0
  154. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_service_reply.py +0 -0
  155. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/peer_staging.py +0 -0
  156. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/query_filters.py +0 -0
  157. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/__init__.py +0 -0
  158. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  159. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  160. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  161. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/service/__init__.py +0 -0
  162. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/service/app.py +0 -0
  163. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/service/providers.py +0 -0
  164. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/session_bridge.py +0 -0
  165. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  166. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  167. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/source_text.py +0 -0
  168. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/stall_alert.py +0 -0
  169. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/codex-waker.default +0 -0
  170. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  171. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  172. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  173. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  174. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  175. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  176. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  177. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  178. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  179. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  180. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  181. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  182. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  183. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/timeline_paths.py +0 -0
  184. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli/tokens.py +0 -0
  185. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  186. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  187. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/requires.txt +0 -0
  188. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/src/swarph_cli.egg-info/top_level.txt +0 -0
  189. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_124_register_merge.py +0 -0
  190. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_126_pending_from_complete.py +0 -0
  191. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  192. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_138_memory_navigate.py +0 -0
  193. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_139_tool_probe.py +0 -0
  194. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_183_card_anchor.py +0 -0
  195. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_184_notify_honesty.py +0 -0
  196. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_201_win32_bash_refusal.py +0 -0
  197. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_243_onboard_per_peer_token.py +0 -0
  198. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_307_task4_ask_reply.py +0 -0
  199. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  200. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_319_refusal_exit_codes.py +0 -0
  201. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_323_mistral_backend.py +0 -0
  202. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_332_report_organize_findings.py +0 -0
  203. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_356_query_filters.py +0 -0
  204. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_397_gh_identity_router.py +0 -0
  205. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_397_router_preflight.py +0 -0
  206. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_402_one_identity_resolver.py +0 -0
  207. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_467b_onboard_operator_credential.py +0 -0
  208. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_472_register_caller_binding.py +0 -0
  209. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_523_guide.py +0 -0
  210. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_524_conftest_hermeticity.py +0 -0
  211. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_532_accept_flag.py +0 -0
  212. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_535_version_capability.py +0 -0
  213. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_546_default_gateway.py +0 -0
  214. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_547_verbs_from_registry.py +0 -0
  215. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_548_gateway_remainder.py +0 -0
  216. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_552_codegraph_gateway.py +0 -0
  217. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_555_relay_row_shape.py +0 -0
  218. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_556_relay_freshness.py +0 -0
  219. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_559_edges_truncated.py +0 -0
  220. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_564_bootstrap_first_peer.py +0 -0
  221. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  222. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_579_register_bootstrap_message.py +0 -0
  223. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_591_cli_step_graph.py +0 -0
  224. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_648_install_unit.py +0 -0
  225. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_654_reexec.py +0 -0
  226. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_656_owned_slm.py +0 -0
  227. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_684_dreaming_verb.py +0 -0
  228. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_689_pkg_version_order.py +0 -0
  229. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_715_grok_wake.py +0 -0
  230. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_716_timeline_one_path.py +0 -0
  231. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_723_grok_0251.py +0 -0
  232. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  233. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_725_stdio_reconfigure.py +0 -0
  234. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_726_grok_limit_screen.py +0 -0
  235. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_729_dm_once.py +0 -0
  236. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  237. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_735_dm_mcp_tools.py +0 -0
  238. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_740_edit_project.py +0 -0
  239. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_762_cards_edit_priority.py +0 -0
  240. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_764_enrich_collapse.py +0 -0
  241. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_801_confirm_flow_cli.py +0 -0
  242. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_802_stage_history.py +0 -0
  243. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_825_codegraph_hook_noise.py +0 -0
  244. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_825_install_posttooluse_only.py +0 -0
  245. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  246. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_829_counterfactual_column.py +0 -0
  247. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_830_hook_binding_version.py +0 -0
  248. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_84_cli_labels.py +0 -0
  249. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_866_doctrine_guide.py +0 -0
  250. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_872_identity_refuses_not_warns.py +0 -0
  251. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_874_source_text.py +0 -0
  252. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  253. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_937_dreaming_notify.py +0 -0
  254. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_938_organize_line_limit.py +0 -0
  255. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_941_bench_backends.py +0 -0
  256. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_941_harness_342.py +0 -0
  257. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_945_same_line_pairing.py +0 -0
  258. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_955_prompt_audit.py +0 -0
  259. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_957_auth_http.py +0 -0
  260. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_959_venv_interpreter.py +0 -0
  261. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_960_channel.py +0 -0
  262. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_963_plugin_manifest.py +0 -0
  263. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_964_channel_cell.py +0 -0
  264. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_965_mesh_wait.py +0 -0
  265. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_975_channel_hop.py +0 -0
  266. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_add_security_gate.py +0 -0
  267. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_add.py +0 -0
  268. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_hash.py +0 -0
  269. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_lib.py +0 -0
  270. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_mcp.py +0 -0
  271. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_skill.py +0 -0
  272. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_tool.py +0 -0
  273. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_artifact_uri.py +0 -0
  274. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_backends.py +0 -0
  275. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_claude_cli.py +0 -0
  276. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_command.py +0 -0
  277. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_pack.py +0 -0
  278. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_prices.py +0 -0
  279. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_quality.py +0 -0
  280. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_runner.py +0 -0
  281. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_bench_validate.py +0 -0
  282. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_board_card_thread.py +0 -0
  283. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_board_command.py +0 -0
  284. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_board_merge_check.py +0 -0
  285. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_board_obligation_close.py +0 -0
  286. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_brain_ask_command.py +0 -0
  287. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_brain_ask_env_diagnosis.py +0 -0
  288. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_brain_ask_identity_explicitness.py +0 -0
  289. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_brain_command.py +0 -0
  290. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_caller_meta_guard.py +0 -0
  291. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_lineage.py +0 -0
  292. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_liveness.py +0 -0
  293. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_manifest.py +0 -0
  294. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_paths.py +0 -0
  295. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_capture_security.py +0 -0
  296. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_command_dispatch.py +0 -0
  297. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_harden.py +0 -0
  298. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_loader.py +0 -0
  299. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_selfcheck.py +0 -0
  300. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cell_verify.py +0 -0
  301. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_chain_token.py +0 -0
  302. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_channel_command.py +0 -0
  303. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  304. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_channel_post.py +0 -0
  305. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_channel_wake_policy_filter.py +0 -0
  306. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_chat_command.py +0 -0
  307. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_claude_parser.py +0 -0
  308. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_claude_tmux_template.py +0 -0
  309. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codegraph_caller_scope.py +0 -0
  310. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codegraph_command.py +0 -0
  311. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codegraph_hook.py +0 -0
  312. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codegraph_mcp.py +0 -0
  313. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_codex_waker.py +0 -0
  314. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_caller_convention.py +0 -0
  315. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_command.py +0 -0
  316. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_levers.py +0 -0
  317. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_marker.py +0 -0
  318. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_compress_verify.py +0 -0
  319. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_console_encoding_e2e.py +0 -0
  320. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_content_composition.py +0 -0
  321. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_cursor_membrane.py +0 -0
  322. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_daemon_command.py +0 -0
  323. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_daemon_console_encoding.py +0 -0
  324. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_daemon_delivery.py +0 -0
  325. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_delivery_queue.py +0 -0
  326. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_display_sanitize.py +0 -0
  327. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  328. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_event_emit.py +0 -0
  329. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_feature_to_uri.py +0 -0
  330. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gateway_brain.py +0 -0
  331. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gateway_client.py +0 -0
  332. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gateway_command.py +0 -0
  333. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gateway_message_idempotency.py +0 -0
  334. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  335. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_group_command.py +0 -0
  336. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_guide_commands_parse.py +0 -0
  337. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_guide_selfcheck.py +0 -0
  338. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_heartbeat_check.py +0 -0
  339. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_highlight_command.py +0 -0
  340. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hook_output.py +0 -0
  341. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_activity_marker.py +0 -0
  342. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_add.py +0 -0
  343. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_bundle.py +0 -0
  344. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_init.py +0 -0
  345. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_legacy_generation_removal.py +0 -0
  346. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_lifecycle.py +0 -0
  347. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_merge.py +0 -0
  348. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_verify.py +0 -0
  349. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  350. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_hooks_win32_command_path.py +0 -0
  351. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_import_command.py +0 -0
  352. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_inbox_identity_provenance.py +0 -0
  353. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_init_command.py +0 -0
  354. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_init_provider_menu.py +0 -0
  355. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_codex_hooks.py +0 -0
  356. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_hook.py +0 -0
  357. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_multiplexer_command.py +0 -0
  358. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_postcompact_hook.py +0 -0
  359. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_install_wake_hook.py +0 -0
  360. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_lane_command.py +0 -0
  361. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_main.py +0 -0
  362. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_main_unknown_verb_guard.py +0 -0
  363. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mcp_missing_hint.py +0 -0
  364. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mcp_server.py +0 -0
  365. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_command.py +0 -0
  366. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_emit_snippet.py +0 -0
  367. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_gateway.py +0 -0
  368. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_sync.py +0 -0
  369. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_memory_sync_membrane.py +0 -0
  370. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_command.py +0 -0
  371. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_inbox_mark_read.py +0 -0
  372. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_inbox_sanitize.py +0 -0
  373. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_originate_thread_623.py +0 -0
  374. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_send_unknown_peer.py +0 -0
  375. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_sidecar.py +0 -0
  376. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  377. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  378. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_meta_edge_identity.py +0 -0
  379. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_channel_poll.py +0 -0
  380. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_cli.py +0 -0
  381. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_daemon_collision.py +0 -0
  382. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_self_name_verification.py +0 -0
  383. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_monitor_state_split.py +0 -0
  384. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_multiplexer.py +0 -0
  385. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_muse_membrane.py +0 -0
  386. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_muse_own_cli.py +0 -0
  387. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_obligation_close_is_surfaced.py +0 -0
  388. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_okf_links.py +0 -0
  389. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_onboard_command.py +0 -0
  390. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_opencode_membrane.py +0 -0
  391. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_opencode_plugin.py +0 -0
  392. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_orphan_daemons.py +0 -0
  393. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_packaged_artifacts_exist.py +0 -0
  394. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_packaged_skills.py +0 -0
  395. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_executor.py +0 -0
  396. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_executor_windows.py +0 -0
  397. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_reply_drain_command.py +0 -0
  398. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_reply_drain_supervisor.py +0 -0
  399. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_reply_outbox.py +0 -0
  400. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_reply_transport.py +0 -0
  401. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_service_e2e.py +0 -0
  402. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_service_host.py +0 -0
  403. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_service_payload.py +0 -0
  404. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_peer_service_reply.py +0 -0
  405. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_postcompact_recall.py +0 -0
  406. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_process_liveness.py +0 -0
  407. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_protocol_handler.py +0 -0
  408. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_providers_isolation.py +0 -0
  409. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_ratify_command.py +0 -0
  410. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_readme_claims_no_gateway_default.py +0 -0
  411. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_repl_caller_convention.py +0 -0
  412. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_schedule_command.py +0 -0
  413. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_schedule_create_contract.py +0 -0
  414. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_security.py +0 -0
  415. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_service_app.py +0 -0
  416. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_service_command.py +0 -0
  417. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_service_providers.py +0 -0
  418. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_session_bridge_inject.py +0 -0
  419. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_session_bridge_probe.py +0 -0
  420. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_session_bridge_resolve.py +0 -0
  421. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_session_path_role_gate.py +0 -0
  422. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_shipped_units_match_the_cli.py +0 -0
  423. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_smoke_chat.py +0 -0
  424. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_smoke_one_shot.py +0 -0
  425. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_smoke_phase_5_5.py +0 -0
  426. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_base_hoist.py +0 -0
  427. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_command.py +0 -0
  428. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_identity_stamp.py +0 -0
  429. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_live_pin.py +0 -0
  430. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_mitosis_lineage.py +0 -0
  431. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_tmux_session.py +0 -0
  432. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_spawn_windows_relaunch.py +0 -0
  433. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_stall_alert.py +0 -0
  434. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_subprocess_decode_encoding.py +0 -0
  435. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_timeline_command.py +0 -0
  436. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_tmux_wake_runstate_defer.py +0 -0
  437. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_tmux_wake_session_reanchor.py +0 -0
  438. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_tmux_wake_stale.py +0 -0
  439. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_tmux_wake_submit_verify.py +0 -0
  440. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_token_file_one_parser.py +0 -0
  441. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_token_precedence_explicit_first.py +0 -0
  442. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_unified_token_resolver.py +0 -0
  443. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_version_command.py +0 -0
  444. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_version_consistency.py +0 -0
  445. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_vibe_membrane.py +0 -0
  446. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog.py +0 -0
  447. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_dm_wake.py +0 -0
  448. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  449. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  450. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_emit_health.py +0 -0
  451. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_install_flags.py +0 -0
  452. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_model_rung.py +0 -0
  453. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/tests/test_watchdog_stale_peers.py +0 -0
  454. {swarph_cli-0.69.7 → swarph_cli-0.69.9}/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.7
3
+ Version: 0.69.9
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.7"
7
+ version = "0.69.9"
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.7"
19
+ __version__ = "0.69.9"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -326,6 +326,34 @@ def _read_cgroup(pid: int) -> "str | None":
326
326
  return None
327
327
 
328
328
 
329
+ def _service_leaf(text: "str | None") -> "str | None":
330
+ """The .service leaf of a cgroup, or None.
331
+
332
+ user@<uid>.service is the session manager, not a supervisor. The leaf
333
+ keeps its .service suffix so a later reader can name the unit after
334
+ this process is gone.
335
+ """
336
+ if not text:
337
+ return None
338
+ for line in text.splitlines():
339
+ leaf = line.rsplit("/", 1)[-1].strip()
340
+ if leaf.endswith(".service") and not leaf.startswith("user@"):
341
+ return leaf
342
+ return None
343
+
344
+
345
+ def _supervisor_for_pidfile(explicit: "str | None") -> "str | None":
346
+ """What start writes into monitor.pid.
347
+
348
+ An explicit claim wins. Otherwise read this process's own cgroup now,
349
+ while the pid is still ours. A later status must not open /proc/<pid>
350
+ for a dead monitor: that pid may have been reused.
351
+ """
352
+ if explicit:
353
+ return explicit
354
+ return _service_leaf(_read_cgroup(os.getpid()))
355
+
356
+
329
357
  def _derive_systemd_unit(pid: "int | None") -> "str | None":
330
358
  """The supervisor Linux already knows (#344 review): Windows has no
331
359
  pid→task map so the pidfile CLAIM is the whole answer there, but on
@@ -589,10 +617,11 @@ def _cmd_start(args: argparse.Namespace) -> int:
589
617
  )
590
618
  return 0
591
619
 
620
+ recorded = _supervisor_for_pidfile(supervisor)
592
621
  if args.once:
593
622
  mesh.write_pidfile(
594
623
  pidfile, self_name=self_name, sinks=sinks, poll_s=args.poll_s,
595
- supervisor=supervisor,
624
+ supervisor=recorded,
596
625
  )
597
626
  mesh._monitor_iteration(state)
598
627
  return 0
@@ -602,10 +631,10 @@ def _cmd_start(args: argparse.Namespace) -> int:
602
631
  print("swarph monitor: no fork() on this platform — running in the "
603
632
  "foreground", file=sys.stderr)
604
633
  return _run_pinned(state, pidfile, self_name, sinks, args.poll_s,
605
- supervisor=supervisor)
634
+ supervisor=recorded)
606
635
 
607
636
  return _start_detached(state, pidfile, self_name, sinks, args.poll_s,
608
- supervisor=supervisor)
637
+ supervisor=recorded)
609
638
 
610
639
 
611
640
  def _run_pinned(state, pidfile: Path, self_name: str, sinks: list, poll_s: int,
@@ -64,71 +64,19 @@ 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
+ # >>> #960: the heartbeat is the READER, the monitor is the WRITER. <<<
68
+ # channel-serve writes channel_heartbeat.json every 60s and only reads
69
+ # inbox.log. The pull monitor is the only writer of that log. A fresh
70
+ # heartbeat means the reader is up, not the writer. `monitor status` is
71
+ # the check for the writer. Not running: start one, even when the
72
+ # heartbeat is fresh. Running (status 0 or 1): start nothing. A second
73
+ # monitor is the duplicate. The heartbeat file is not that check.
124
74
  "$SW" monitor status --as "$SELF" ${SD[@]+"${SD[@]}"} >/dev/null 2>&1
125
75
  case $? in
126
76
  0) ;; # running, nothing pending
127
77
  1) ;; # running, DMs pending — reported below
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
78
+ *) # not running (2) or unknown. The heartbeat does not cover this.
79
+ if "$SW" monitor start --as "$SELF" --deliver pull ${SD[@]+"${SD[@]}"} >/tmp/.swarph-monitor-start.$$ 2>&1; then
132
80
  echo "[monitor] started (--deliver pull)"
133
81
  else
134
82
  # LOUD, not silent: an auto-start that fails quietly rebuilds the exact
@@ -0,0 +1,385 @@
1
+ """Card #729. Every pass, a cell with nobody reading its inbox is an outage.
2
+
3
+ One DM per outage. A live watcher clears it, so the next gap can alert again.
4
+ """
5
+ from __future__ import annotations
6
+
7
+ import argparse
8
+ import json
9
+ import os
10
+ import subprocess
11
+ import sys
12
+ import time
13
+ from pathlib import Path
14
+ from typing import Iterable
15
+
16
+ GAP_SECONDS = 60
17
+
18
+
19
+ def writer_verdict(*, reader_alive: bool, monitor_rc: int) -> str:
20
+ """The heartbeat is the reader. The pull monitor is the writer of inbox.log.
21
+
22
+ A live reader with monitor status 2 is writer-down. A live monitor with a
23
+ fresh heartbeat is healthy. This does not start a process.
24
+ """
25
+ if not reader_alive:
26
+ return "reader-down"
27
+ if monitor_rc == 2:
28
+ return "writer-down"
29
+ return "healthy"
30
+
31
+
32
+ def writer_transition(down: bool, rec: dict) -> bool:
33
+ """True only on the edge into writer-down.
34
+
35
+ A healthy pass clears the flag. A down pass does not set it: the caller
36
+ marks the alert only after the DM returns 0, so a lost send is retried.
37
+ """
38
+ if not down:
39
+ rec["writer_down"] = False
40
+ return False
41
+ return not bool(rec.get("writer_down"))
42
+
43
+
44
+ def supervisor_from_cgroup(text: str | None) -> str:
45
+ """The unit leaf of a cgroup listing, the same rule monitor status uses.
46
+
47
+ A swarph-monitor service, including the non-template names
48
+ swarph-monitor.service and swarph-monitor-<cell>.service, is the
49
+ supervisor. user@<uid>.service is the session manager, not one.
50
+ Anything else is unsupervised.
51
+ """
52
+ if not text:
53
+ return "unsupervised"
54
+ for line in text.splitlines():
55
+ leaf = line.rsplit("/", 1)[-1].strip()
56
+ if not leaf.endswith(".service") or leaf.startswith("user@"):
57
+ continue
58
+ if leaf.startswith("swarph-monitor"):
59
+ return leaf
60
+ return "unsupervised"
61
+
62
+
63
+ def writer_alert(cell: str, recorded: str | None, *, owned: bool = False) -> str:
64
+ """Name the supervisor recorded at start, or the enabled template unit."""
65
+ who = _alert_supervisor(recorded)
66
+ if who == "unsupervised" and owned:
67
+ who = f"swarph-monitor@{cell}.service"
68
+ if who == "unsupervised":
69
+ return f"{cell}: writer-down, unsupervised"
70
+ return f"{cell}: writer-down, supervised by {who}"
71
+
72
+
73
+ def systemd_owns_monitor(cell: str, run=None) -> bool:
74
+ """True when either scope has an enabled swarph-monitor@<cell> unit.
75
+
76
+ That result is part of the writer-down alert when the cgroup names no
77
+ swarph-monitor leaf. This function does not start a process.
78
+ """
79
+ unit = f"swarph-monitor@{cell}.service"
80
+ runner = run if run is not None else subprocess.run
81
+ owned = False
82
+ for argv in (
83
+ ["systemctl", "is-enabled", unit],
84
+ ["systemctl", "--user", "is-enabled", unit],
85
+ ):
86
+ try:
87
+ proc = runner(argv, capture_output=True, text=True,
88
+ encoding="utf-8", errors="replace", check=False)
89
+ except FileNotFoundError:
90
+ continue
91
+ if proc.returncode == 0:
92
+ owned = True
93
+ return owned
94
+
95
+
96
+ def enforce_writers(cells: dict[str, str], *, status, run=None,
97
+ state: dict | None = None) -> list[str]:
98
+ """Cells that just transitioned to writer-down.
99
+
100
+ Both systemd scopes are still checked. Nothing here starts a monitor.
101
+ Supervision stays with the unit. One name is returned per down edge.
102
+ """
103
+ alert = []
104
+ state = state if state is not None else {}
105
+ for name, inbox in cells.items():
106
+ if not _channel_alive(inbox):
107
+ continue
108
+ try:
109
+ rc = int(status(name))
110
+ except (TypeError, ValueError, OSError):
111
+ continue
112
+ rec_owned = systemd_owns_monitor(name, run=run)
113
+ verdict = writer_verdict(reader_alive=True, monitor_rc=rc)
114
+ rec = state.setdefault(name, {})
115
+ rec["systemd_owned"] = rec_owned
116
+ if writer_transition(verdict == "writer-down", rec):
117
+ alert.append(name)
118
+ return alert
119
+
120
+
121
+ def _channel_alive(inbox: str, now: float | None = None) -> bool:
122
+ """A channel server writes channel_heartbeat.json beside the inbox every 60 s.
123
+
124
+ `swarph channel-serve` takes no inbox argument, so no command line names the
125
+ path; a fresh heartbeat is the only evidence the channel is reading.
126
+ """
127
+ hb = Path(inbox).parent / "channel_heartbeat.json"
128
+ try:
129
+ ts = float(json.loads(hb.read_text(encoding="utf-8")).get("ts") or 0)
130
+ except (OSError, ValueError, TypeError, AttributeError):
131
+ return False
132
+ return (time.time() if now is None else now) - ts < 180
133
+
134
+
135
+ def _watching(inbox: str, cmdlines: Iterable[str]) -> bool:
136
+ """A reader is tail or dm_notify_filter with the inbox path as its own argument,
137
+ or a channel server whose heartbeat is fresh.
138
+
139
+ A process that merely mentions the path (a prompt, a log line) is not a reader.
140
+ """
141
+ if _channel_alive(inbox):
142
+ return True
143
+ for cmd in cmdlines:
144
+ parts = cmd.split()
145
+ if inbox not in parts:
146
+ continue
147
+ if any(os.path.basename(p) == "tail" for p in parts):
148
+ return True
149
+ if any(p.endswith("dm_notify_filter") or p.endswith("dm_notify_filter.py") for p in parts):
150
+ return True
151
+ return False
152
+
153
+
154
+ def _cron_watched(name: str, crontab: str) -> bool:
155
+ """An active crontab line that runs dm_wake.py for this cell is its poller."""
156
+ for line in crontab.splitlines():
157
+ body = line.strip()
158
+ if not body or body.startswith("#"):
159
+ continue
160
+ if name in body and "dm_wake.py" in body:
161
+ return True
162
+ return False
163
+
164
+
165
+ def _timer_watched(name: str, timers: Iterable[str]) -> bool:
166
+ needle = f"swarph-codex-waker@{name}.timer"
167
+ return any(needle in line for line in timers)
168
+
169
+
170
+ def scan(cells: dict[str, str], cmdlines: Iterable[str], state: dict,
171
+ now: float, *, gap: float = GAP_SECONDS,
172
+ timers: Iterable[str] = (), crontab: str = "") -> list[str]:
173
+ """Return the cells that should get the one DM for this outage."""
174
+ alert = []
175
+ timers = list(timers)
176
+ for name, inbox in cells.items():
177
+ rec = state.setdefault(name, {})
178
+ if (_watching(inbox, cmdlines) or _timer_watched(name, timers)
179
+ or _cron_watched(name, crontab)):
180
+ rec["missing_since"] = None
181
+ rec["outage"] = False
182
+ continue
183
+ since = rec.get("missing_since")
184
+ if since is None:
185
+ rec["missing_since"] = now
186
+ continue
187
+ if now - since > gap and not rec.get("outage"):
188
+ alert.append(name)
189
+ return alert
190
+
191
+
192
+ def cells_under(root: Path) -> dict[str, str]:
193
+ found = {}
194
+ if not root.is_dir():
195
+ return found
196
+ for child in sorted(root.iterdir()):
197
+ inbox = child / "mesh-sidecar" / "inbox.log"
198
+ if inbox.is_file():
199
+ found[child.name] = str(inbox)
200
+ return found
201
+
202
+
203
+ def load_state(path: Path) -> dict:
204
+ try:
205
+ return json.loads(path.read_text(encoding="utf-8"))
206
+ except (OSError, ValueError):
207
+ return {}
208
+
209
+
210
+ def save_state(path: Path, state: dict) -> None:
211
+ path.parent.mkdir(parents=True, exist_ok=True)
212
+ path.write_text(json.dumps(state), encoding="utf-8")
213
+
214
+
215
+ def _listing(argv: list[str]) -> str:
216
+ """A missing binary is an empty listing, which is also a host with no timers."""
217
+ try:
218
+ listed = subprocess.run(
219
+ argv, capture_output=True, text=True,
220
+ encoding="utf-8", errors="replace", check=False)
221
+ except FileNotFoundError:
222
+ return ""
223
+ return listed.stdout or ""
224
+
225
+
226
+ def _crontab() -> str:
227
+ return _listing(["crontab", "-l"])
228
+
229
+
230
+ def _timer_lines() -> list[str]:
231
+ return _listing(
232
+ ["systemctl", "--user", "list-timers", "--all", "--no-legend"]).splitlines()
233
+
234
+
235
+ def _alert_supervisor(recorded: str | None) -> str:
236
+ """A unit name recorded at start, or a cgroup blob from an older test.
237
+
238
+ A plain unit name is trusted: the monitor wrote it from its own cgroup
239
+ while the pid was still its own. Cgroup text is parsed the same way.
240
+ """
241
+ if not recorded or not str(recorded).strip():
242
+ return "unsupervised"
243
+ text = str(recorded).strip()
244
+ if "/" in text or "\n" in text:
245
+ return supervisor_from_cgroup(text)
246
+ if text.startswith("user@"):
247
+ return "unsupervised"
248
+ return text
249
+
250
+
251
+ def recorded_supervisor(root: Path, cell: str) -> str | None:
252
+ """The supervisor field of monitor.pid.
253
+
254
+ The pid in that file may be dead, and a dead pid can be reused by an
255
+ unrelated process. The field was written at start and still answers.
256
+ This function does not open a process listing.
257
+ """
258
+ pidfile = root / cell / "mesh-sidecar" / "monitor.pid"
259
+ try:
260
+ rec = json.loads(pidfile.read_text(encoding="utf-8"))
261
+ except (OSError, ValueError):
262
+ return None
263
+ if not isinstance(rec, dict):
264
+ return None
265
+ who = rec.get("supervisor")
266
+ if isinstance(who, str) and who.strip():
267
+ return who.strip()
268
+ return None
269
+
270
+
271
+ def mesh_send(swarph: str, to: str, sender: str, content: str,
272
+ token_file: str = "") -> int:
273
+ """Send one FYI. A self-send is refused and logged. The token is a path."""
274
+ if sender and to and sender == to:
275
+ print(f"wake_watchdog: refusing self-send to {to}", file=sys.stderr)
276
+ return 2
277
+ argv = [swarph, "mesh", "send", to, "--as", sender, "--kind", "fyi",
278
+ "--content", content]
279
+ if token_file:
280
+ argv.extend(["--token-file", token_file])
281
+ proc = subprocess.run(argv, check=False)
282
+ return proc.returncode
283
+
284
+
285
+ def _swarph_bin() -> str:
286
+ explicit = os.environ.get("SWARPH_BIN", "")
287
+ if explicit:
288
+ return explicit
289
+ home = os.path.expanduser("~/.local/bin/swarph")
290
+ if os.path.isfile(home):
291
+ return home
292
+ return "swarph"
293
+
294
+
295
+ def main(argv: list[str] | None = None) -> int:
296
+ p = argparse.ArgumentParser(prog="wake_watchdog")
297
+ p.add_argument("--dry-run", action="store_true",
298
+ help="write state and list unwatched cells; do not send")
299
+ p.add_argument("--as", dest="sender", default="",
300
+ help="cell the alert is sent as; the unit sets this")
301
+ p.add_argument("--escalate",
302
+ default=os.environ.get("WAKE_WATCHDOG_ESCALATE", "drop-on-meta-edge"),
303
+ help="awake peer who also gets the outage DM (default: drop-on-meta-edge)")
304
+ p.add_argument("--token-file", default="",
305
+ help="bearer token file for the service identity; the path is passed through, never the value")
306
+ args = p.parse_args(argv)
307
+ root = Path(os.environ.get("SWARPH_STATE_ROOT", os.path.expanduser("~/swarph_state")))
308
+ state_path = Path(os.environ.get(
309
+ "WAKE_WATCHDOG_STATE",
310
+ os.path.expanduser("~/.local/state/wake-watchdog.json")))
311
+ import time
312
+ cmdlines = _listing(["ps", "-eo", "args"]).splitlines()
313
+ timers = _timer_lines()
314
+ crontab = _crontab()
315
+ cells = cells_under(root)
316
+ state = load_state(state_path)
317
+ alert = scan(cells, cmdlines, state, time.time(), timers=timers, crontab=crontab)
318
+ sender = args.sender.strip()
319
+ escalate = (args.escalate or "").strip()
320
+ if not args.dry_run and escalate and sender and escalate == sender:
321
+ print("wake_watchdog: escalation peer equals the sender; refusing to send",
322
+ file=sys.stderr)
323
+ return 2
324
+ swarph = _swarph_bin()
325
+
326
+ def _status(name: str, _sw: str = swarph) -> int:
327
+ proc = subprocess.run(
328
+ [_sw, "monitor", "status", "--as", name],
329
+ capture_output=True, text=True, encoding="utf-8", errors="replace",
330
+ check=False)
331
+ return proc.returncode
332
+
333
+ reported = enforce_writers(cells, status=_status, state=state)
334
+ save_state(state_path, state)
335
+ token_file = (args.token_file or "").strip()
336
+ for name in reported:
337
+ print(f"writer-down {name}", flush=True)
338
+ if args.dry_run or not sender:
339
+ continue
340
+ owned = bool(state.get(name, {}).get("systemd_owned"))
341
+ sent = mesh_send(
342
+ swarph, escalate or "", sender,
343
+ writer_alert(name, recorded_supervisor(root, name), owned=owned),
344
+ "")
345
+ if sent == 0:
346
+ state.setdefault(name, {})["writer_down"] = True
347
+ save_state(state_path, state)
348
+ if args.dry_run:
349
+ for name, inbox in cells.items():
350
+ if not (_watching(inbox, cmdlines) or _timer_watched(name, timers)
351
+ or _cron_watched(name, crontab)):
352
+ print(name, flush=True)
353
+ return 0
354
+ sender = args.sender.strip()
355
+ if not sender:
356
+ print("wake_watchdog: --as is empty; not marking any cell alerted", file=sys.stderr)
357
+ return 2
358
+ swarph = _swarph_bin()
359
+ rc = 0
360
+ for name in alert:
361
+ print(f"outage {name}", flush=True)
362
+ dm_rc = mesh_send(
363
+ swarph, name, sender, "your DM wake is dead, re-arm", token_file)
364
+ dm_ok = dm_rc == 0
365
+ card_argv = [swarph, "board", "cards", "say", "729", "--as", sender,
366
+ "--to", name, "--content", f"{name}: DM wake is dead, re-arm"]
367
+ if token_file:
368
+ card_argv.extend(["--token-file", token_file])
369
+ card = subprocess.run(card_argv, check=False)
370
+ esc_ok = True
371
+ if escalate and escalate != name:
372
+ esc_rc = mesh_send(
373
+ swarph, escalate, sender, f"{name}: DM wake is dead, re-arm",
374
+ token_file)
375
+ esc_ok = esc_rc == 0
376
+ if dm_ok and card.returncode == 0 and esc_ok:
377
+ state[name]["outage"] = True
378
+ save_state(state_path, state)
379
+ else:
380
+ rc = 1
381
+ return rc
382
+
383
+
384
+ if __name__ == "__main__":
385
+ raise SystemExit(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.69.7
3
+ Version: 0.69.9
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
@@ -178,6 +178,7 @@ src/swarph_cli/systemd/swarph-peer-reply-drain@.timer
178
178
  src/swarph_cli/systemd/swarph-watchdog.default
179
179
  src/swarph_cli/systemd/swarph-watchdog.service
180
180
  src/swarph_cli/systemd/swarph-watchdog.timer
181
+ tests/test_1006_session_board.py
181
182
  tests/test_124_register_merge.py
182
183
  tests/test_126_pending_from_complete.py
183
184
  tests/test_132_ensure_monitor_state_dir.py
@@ -229,6 +230,7 @@ tests/test_729_dm_once.py
229
230
  tests/test_729_wake_watchdog.py
230
231
  tests/test_734_dreaming_enrich_degrade.py
231
232
  tests/test_735_dm_mcp_tools.py
233
+ tests/test_739_wake_writer.py
232
234
  tests/test_740_edit_project.py
233
235
  tests/test_762_cards_edit_priority.py
234
236
  tests/test_764_enrich_collapse.py