swarph-cli 0.69.6__tar.gz → 0.69.8__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 (452) hide show
  1. {swarph_cli-0.69.6/src/swarph_cli.egg-info → swarph_cli-0.69.8}/PKG-INFO +1 -1
  2. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/pyproject.toml +1 -1
  3. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/mesh.py +30 -14
  5. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/ensure_monitor.sh +9 -61
  6. {swarph_cli-0.69.6 → swarph_cli-0.69.8/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  7. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/SOURCES.txt +2 -0
  8. swarph_cli-0.69.8/tests/test_1006_session_board.py +291 -0
  9. swarph_cli-0.69.8/tests/test_726_grok_limit_screen.py +60 -0
  10. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_960_ensure_monitor_heartbeat.py +47 -16
  11. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/LICENSE +0 -0
  12. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/README.md +0 -0
  13. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/setup.cfg +0 -0
  14. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/__main__.py +0 -0
  15. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/__init__.py +0 -0
  16. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/backends.py +0 -0
  17. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/claude_cli.py +0 -0
  18. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/pack.py +0 -0
  19. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/prices.py +0 -0
  20. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/providers.py +0 -0
  21. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/quality.py +0 -0
  22. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/refresh_prices.py +0 -0
  23. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/rules.py +0 -0
  24. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/runner.py +0 -0
  25. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/bench/validate.py +0 -0
  26. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/caller.py +0 -0
  27. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/__init__.py +0 -0
  28. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/harden.py +0 -0
  29. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/lineage.py +0 -0
  30. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/liveness.py +0 -0
  31. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/manifest.py +0 -0
  32. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/paths.py +0 -0
  33. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/capture/verify.py +0 -0
  34. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/cell.py +0 -0
  35. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/chain_token.py +0 -0
  36. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/channel.py +0 -0
  37. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/__init__.py +0 -0
  38. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/_content.py +0 -0
  39. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/_display.py +0 -0
  40. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/_gateway_client.py +0 -0
  41. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/add.py +0 -0
  42. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/bench.py +0 -0
  43. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/board.py +0 -0
  44. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/board_merge_check.py +0 -0
  45. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/brain.py +0 -0
  46. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/brain_ask.py +0 -0
  47. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/cell.py +0 -0
  48. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/cell_probe.py +0 -0
  49. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  50. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/channel.py +0 -0
  51. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/chat.py +0 -0
  52. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/codegraph.py +0 -0
  53. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  54. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  55. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/codex_waker.py +0 -0
  56. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/compress.py +0 -0
  57. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/daemon.py +0 -0
  58. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/dreaming.py +0 -0
  59. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/event.py +0 -0
  60. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/gateway.py +0 -0
  61. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/gh_route.py +0 -0
  62. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/group.py +0 -0
  63. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/guide.py +0 -0
  64. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/highlight.py +0 -0
  65. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  66. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/hook_output.py +0 -0
  67. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/hooks.py +0 -0
  68. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/import_session.py +0 -0
  69. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/init.py +0 -0
  70. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  71. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_hook.py +0 -0
  72. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  73. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  74. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  75. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  76. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/lane.py +0 -0
  77. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/mcp_server.py +0 -0
  78. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/memory.py +0 -0
  79. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  80. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/memory_sync.py +0 -0
  81. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/monitor.py +0 -0
  82. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/okf_links.py +0 -0
  83. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/onboard.py +0 -0
  84. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  85. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  86. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/protocol_handler.py +0 -0
  87. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/ratify.py +0 -0
  88. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/schedule.py +0 -0
  89. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/security.py +0 -0
  90. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/service.py +0 -0
  91. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/spawn.py +0 -0
  92. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/timeline.py +0 -0
  93. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/version.py +0 -0
  94. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  95. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/commands/watchdog.py +0 -0
  96. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/compress/__init__.py +0 -0
  97. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/compress/levers.py +0 -0
  98. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/compress/marker.py +0 -0
  99. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/compress/verify.py +0 -0
  100. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/console_safe.py +0 -0
  101. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/delivery_queue.py +0 -0
  102. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dm_frame.py +0 -0
  103. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/__init__.py +0 -0
  104. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/candidates.py +0 -0
  105. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/clone.py +0 -0
  106. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/enrich.py +0 -0
  107. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/index_check.py +0 -0
  108. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/notify.py +0 -0
  109. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/organize.py +0 -0
  110. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/probes.py +0 -0
  111. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/report.py +0 -0
  112. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/run.py +0 -0
  113. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/slm.py +0 -0
  114. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/transcripts.py +0 -0
  115. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/dreaming/verify.py +0 -0
  116. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/__init__.py +0 -0
  117. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/feature_registry.py +0 -0
  118. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/lanes_control.py +0 -0
  119. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/schema.sql +0 -0
  120. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/server.py +0 -0
  121. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway/services_control.py +0 -0
  122. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gateway_default.py +0 -0
  123. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/gh_identity.py +0 -0
  124. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/guide/GUIDE.md +0 -0
  125. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/identity.py +0 -0
  126. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/main.py +0 -0
  127. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/multiplexer.py +0 -0
  128. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/orphan_daemons.py +0 -0
  129. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/pane_probe.py +0 -0
  130. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/parsers/__init__.py +0 -0
  131. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/parsers/claude.py +0 -0
  132. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  133. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  134. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  135. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  136. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  137. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  138. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  139. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  140. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  141. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  142. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  143. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  144. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_executor.py +0 -0
  145. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_executor_windows.py +0 -0
  146. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_reconciliation.py +0 -0
  147. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_reply_outbox.py +0 -0
  148. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_service_host.py +0 -0
  149. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_service_payload.py +0 -0
  150. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_service_reply.py +0 -0
  151. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/peer_staging.py +0 -0
  152. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/query_filters.py +0 -0
  153. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/__init__.py +0 -0
  154. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  155. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  156. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  157. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  158. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/service/__init__.py +0 -0
  159. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/service/app.py +0 -0
  160. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/service/providers.py +0 -0
  161. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/session_bridge.py +0 -0
  162. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  163. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  164. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/source_text.py +0 -0
  165. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/stall_alert.py +0 -0
  166. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/codex-waker.default +0 -0
  167. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  168. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  169. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  170. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  171. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  172. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  173. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  174. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  175. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  176. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  177. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  178. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  179. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  180. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/timeline_paths.py +0 -0
  181. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli/tokens.py +0 -0
  182. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  183. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  184. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/requires.txt +0 -0
  185. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/src/swarph_cli.egg-info/top_level.txt +0 -0
  186. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_124_register_merge.py +0 -0
  187. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_126_pending_from_complete.py +0 -0
  188. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  189. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_138_memory_navigate.py +0 -0
  190. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_139_tool_probe.py +0 -0
  191. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_183_card_anchor.py +0 -0
  192. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_184_notify_honesty.py +0 -0
  193. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_201_win32_bash_refusal.py +0 -0
  194. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_243_onboard_per_peer_token.py +0 -0
  195. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_307_task4_ask_reply.py +0 -0
  196. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  197. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_319_refusal_exit_codes.py +0 -0
  198. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_323_mistral_backend.py +0 -0
  199. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_332_report_organize_findings.py +0 -0
  200. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_356_query_filters.py +0 -0
  201. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_397_gh_identity_router.py +0 -0
  202. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_397_router_preflight.py +0 -0
  203. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_402_one_identity_resolver.py +0 -0
  204. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_467b_onboard_operator_credential.py +0 -0
  205. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_472_register_caller_binding.py +0 -0
  206. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_523_guide.py +0 -0
  207. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_524_conftest_hermeticity.py +0 -0
  208. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_532_accept_flag.py +0 -0
  209. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_535_version_capability.py +0 -0
  210. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_546_default_gateway.py +0 -0
  211. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_547_verbs_from_registry.py +0 -0
  212. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_548_gateway_remainder.py +0 -0
  213. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_552_codegraph_gateway.py +0 -0
  214. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_555_relay_row_shape.py +0 -0
  215. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_556_relay_freshness.py +0 -0
  216. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_559_edges_truncated.py +0 -0
  217. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_564_bootstrap_first_peer.py +0 -0
  218. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  219. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_579_register_bootstrap_message.py +0 -0
  220. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_591_cli_step_graph.py +0 -0
  221. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_644_monitor_supervision.py +0 -0
  222. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_648_install_unit.py +0 -0
  223. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_654_reexec.py +0 -0
  224. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_656_owned_slm.py +0 -0
  225. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_684_dreaming_verb.py +0 -0
  226. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_689_pkg_version_order.py +0 -0
  227. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_715_grok_wake.py +0 -0
  228. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_716_timeline_one_path.py +0 -0
  229. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_723_grok_0251.py +0 -0
  230. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  231. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_725_stdio_reconfigure.py +0 -0
  232. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_729_dm_once.py +0 -0
  233. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_729_wake_watchdog.py +0 -0
  234. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  235. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_735_dm_mcp_tools.py +0 -0
  236. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_740_edit_project.py +0 -0
  237. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_762_cards_edit_priority.py +0 -0
  238. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_764_enrich_collapse.py +0 -0
  239. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_801_confirm_flow_cli.py +0 -0
  240. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_802_stage_history.py +0 -0
  241. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_825_codegraph_hook_noise.py +0 -0
  242. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_825_install_posttooluse_only.py +0 -0
  243. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  244. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_829_counterfactual_column.py +0 -0
  245. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_830_hook_binding_version.py +0 -0
  246. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_84_cli_labels.py +0 -0
  247. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_866_doctrine_guide.py +0 -0
  248. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_872_identity_refuses_not_warns.py +0 -0
  249. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_874_source_text.py +0 -0
  250. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  251. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_937_dreaming_notify.py +0 -0
  252. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_938_organize_line_limit.py +0 -0
  253. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_941_bench_backends.py +0 -0
  254. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_941_harness_342.py +0 -0
  255. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_945_same_line_pairing.py +0 -0
  256. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_955_prompt_audit.py +0 -0
  257. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_957_auth_http.py +0 -0
  258. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_959_venv_interpreter.py +0 -0
  259. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_960_channel.py +0 -0
  260. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_963_plugin_manifest.py +0 -0
  261. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_964_channel_cell.py +0 -0
  262. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_965_mesh_wait.py +0 -0
  263. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_975_channel_hop.py +0 -0
  264. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_add_security_gate.py +0 -0
  265. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_add.py +0 -0
  266. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_hash.py +0 -0
  267. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_lib.py +0 -0
  268. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_mcp.py +0 -0
  269. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_skill.py +0 -0
  270. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_tool.py +0 -0
  271. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_artifact_uri.py +0 -0
  272. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_backends.py +0 -0
  273. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_claude_cli.py +0 -0
  274. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_command.py +0 -0
  275. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_pack.py +0 -0
  276. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_prices.py +0 -0
  277. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_quality.py +0 -0
  278. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_runner.py +0 -0
  279. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_bench_validate.py +0 -0
  280. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_board_card_thread.py +0 -0
  281. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_board_command.py +0 -0
  282. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_board_merge_check.py +0 -0
  283. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_board_obligation_close.py +0 -0
  284. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_brain_ask_command.py +0 -0
  285. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_brain_ask_env_diagnosis.py +0 -0
  286. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_brain_ask_identity_explicitness.py +0 -0
  287. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_brain_command.py +0 -0
  288. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_caller_meta_guard.py +0 -0
  289. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_lineage.py +0 -0
  290. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_liveness.py +0 -0
  291. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_manifest.py +0 -0
  292. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_paths.py +0 -0
  293. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_capture_security.py +0 -0
  294. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_command_dispatch.py +0 -0
  295. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_harden.py +0 -0
  296. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_loader.py +0 -0
  297. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_selfcheck.py +0 -0
  298. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cell_verify.py +0 -0
  299. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_chain_token.py +0 -0
  300. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_channel_command.py +0 -0
  301. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  302. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_channel_post.py +0 -0
  303. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_channel_wake_policy_filter.py +0 -0
  304. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_chat_command.py +0 -0
  305. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_claude_parser.py +0 -0
  306. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_claude_tmux_template.py +0 -0
  307. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codegraph_caller_scope.py +0 -0
  308. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codegraph_command.py +0 -0
  309. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codegraph_hook.py +0 -0
  310. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codegraph_mcp.py +0 -0
  311. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_codex_waker.py +0 -0
  312. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_caller_convention.py +0 -0
  313. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_command.py +0 -0
  314. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_levers.py +0 -0
  315. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_marker.py +0 -0
  316. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_compress_verify.py +0 -0
  317. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_console_encoding_e2e.py +0 -0
  318. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_content_composition.py +0 -0
  319. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_cursor_membrane.py +0 -0
  320. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_daemon_command.py +0 -0
  321. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_daemon_console_encoding.py +0 -0
  322. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_daemon_delivery.py +0 -0
  323. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_delivery_queue.py +0 -0
  324. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_display_sanitize.py +0 -0
  325. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  326. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_event_emit.py +0 -0
  327. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_feature_to_uri.py +0 -0
  328. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gateway_brain.py +0 -0
  329. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gateway_client.py +0 -0
  330. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gateway_command.py +0 -0
  331. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gateway_message_idempotency.py +0 -0
  332. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  333. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_group_command.py +0 -0
  334. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_guide_commands_parse.py +0 -0
  335. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_guide_selfcheck.py +0 -0
  336. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_heartbeat_check.py +0 -0
  337. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_highlight_command.py +0 -0
  338. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hook_output.py +0 -0
  339. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_activity_marker.py +0 -0
  340. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_add.py +0 -0
  341. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_bundle.py +0 -0
  342. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_init.py +0 -0
  343. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_legacy_generation_removal.py +0 -0
  344. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_lifecycle.py +0 -0
  345. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_merge.py +0 -0
  346. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_verify.py +0 -0
  347. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  348. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_hooks_win32_command_path.py +0 -0
  349. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_import_command.py +0 -0
  350. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_inbox_identity_provenance.py +0 -0
  351. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_init_command.py +0 -0
  352. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_init_provider_menu.py +0 -0
  353. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_codex_hooks.py +0 -0
  354. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_hook.py +0 -0
  355. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_multiplexer_command.py +0 -0
  356. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_postcompact_hook.py +0 -0
  357. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_install_wake_hook.py +0 -0
  358. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_lane_command.py +0 -0
  359. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_main.py +0 -0
  360. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_main_unknown_verb_guard.py +0 -0
  361. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mcp_missing_hint.py +0 -0
  362. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mcp_server.py +0 -0
  363. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_command.py +0 -0
  364. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_emit_snippet.py +0 -0
  365. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_gateway.py +0 -0
  366. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_sync.py +0 -0
  367. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_memory_sync_membrane.py +0 -0
  368. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_command.py +0 -0
  369. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_inbox_mark_read.py +0 -0
  370. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_inbox_sanitize.py +0 -0
  371. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_originate_thread_623.py +0 -0
  372. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_send_unknown_peer.py +0 -0
  373. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_sidecar.py +0 -0
  374. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  375. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  376. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_meta_edge_identity.py +0 -0
  377. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_channel_poll.py +0 -0
  378. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_cli.py +0 -0
  379. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_daemon_collision.py +0 -0
  380. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_self_name_verification.py +0 -0
  381. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_monitor_state_split.py +0 -0
  382. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_multiplexer.py +0 -0
  383. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_muse_membrane.py +0 -0
  384. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_muse_own_cli.py +0 -0
  385. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_obligation_close_is_surfaced.py +0 -0
  386. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_okf_links.py +0 -0
  387. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_onboard_command.py +0 -0
  388. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_opencode_membrane.py +0 -0
  389. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_opencode_plugin.py +0 -0
  390. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_orphan_daemons.py +0 -0
  391. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_packaged_artifacts_exist.py +0 -0
  392. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_packaged_skills.py +0 -0
  393. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_executor.py +0 -0
  394. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_executor_windows.py +0 -0
  395. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_reply_drain_command.py +0 -0
  396. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_reply_drain_supervisor.py +0 -0
  397. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_reply_outbox.py +0 -0
  398. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_reply_transport.py +0 -0
  399. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_service_e2e.py +0 -0
  400. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_service_host.py +0 -0
  401. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_service_payload.py +0 -0
  402. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_peer_service_reply.py +0 -0
  403. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_postcompact_recall.py +0 -0
  404. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_process_liveness.py +0 -0
  405. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_protocol_handler.py +0 -0
  406. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_providers_isolation.py +0 -0
  407. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_ratify_command.py +0 -0
  408. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_readme_claims_no_gateway_default.py +0 -0
  409. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_repl_caller_convention.py +0 -0
  410. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_schedule_command.py +0 -0
  411. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_schedule_create_contract.py +0 -0
  412. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_security.py +0 -0
  413. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_service_app.py +0 -0
  414. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_service_command.py +0 -0
  415. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_service_providers.py +0 -0
  416. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_session_bridge_inject.py +0 -0
  417. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_session_bridge_probe.py +0 -0
  418. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_session_bridge_resolve.py +0 -0
  419. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_session_path_role_gate.py +0 -0
  420. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_shipped_units_match_the_cli.py +0 -0
  421. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_smoke_chat.py +0 -0
  422. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_smoke_one_shot.py +0 -0
  423. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_smoke_phase_5_5.py +0 -0
  424. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_base_hoist.py +0 -0
  425. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_command.py +0 -0
  426. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_identity_stamp.py +0 -0
  427. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_live_pin.py +0 -0
  428. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_mitosis_lineage.py +0 -0
  429. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_tmux_session.py +0 -0
  430. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_spawn_windows_relaunch.py +0 -0
  431. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_stall_alert.py +0 -0
  432. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_subprocess_decode_encoding.py +0 -0
  433. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_timeline_command.py +0 -0
  434. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_tmux_wake_runstate_defer.py +0 -0
  435. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_tmux_wake_session_reanchor.py +0 -0
  436. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_tmux_wake_stale.py +0 -0
  437. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_tmux_wake_submit_verify.py +0 -0
  438. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_token_file_one_parser.py +0 -0
  439. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_token_precedence_explicit_first.py +0 -0
  440. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_unified_token_resolver.py +0 -0
  441. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_version_command.py +0 -0
  442. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_version_consistency.py +0 -0
  443. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_vibe_membrane.py +0 -0
  444. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog.py +0 -0
  445. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_dm_wake.py +0 -0
  446. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  447. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  448. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_emit_health.py +0 -0
  449. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_install_flags.py +0 -0
  450. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_model_rung.py +0 -0
  451. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/tests/test_watchdog_stale_peers.py +0 -0
  452. {swarph_cli-0.69.6 → swarph_cli-0.69.8}/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.6
3
+ Version: 0.69.8
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.6"
7
+ version = "0.69.8"
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.6"
19
+ __version__ = "0.69.8"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -2306,23 +2306,39 @@ def _grok_input(lines: list[str]) -> Optional[str]:
2306
2306
  return after.strip()
2307
2307
 
2308
2308
 
2309
- def _grok_block_reason(lines: list[str]) -> Optional[str]:
2310
- """A rate or usage limit is not an idle composer and not a dead pane.
2309
+ # One grok screen, measured on the live 0.2.51 pane (52 rows). Lines above
2310
+ # this window are scrollback. A transcript that merely quotes the limit
2311
+ # strings is not the limit screen.
2312
+ _GROK_SCREEN_ROWS = 52
2313
+
2311
2314
 
2312
- 0.2.51 draws ``Retry failed: You've hit the rate limit for your plan``
2313
- under an empty ``│ ❯`` box, with no spinner. 1.0.44 draws a ``┃`` modal
2314
- ``You hit your free usage limit`` and drops the Grok footer entirely.
2315
- Either screen used to look clear (inject again) or unreadable
2316
- (DELIVERY FAILED). Return a stable reason token, or None.
2315
+ def _grok_block_reason(lines: list[str]) -> Optional[str]:
2316
+ """A rate or usage limit screen is not an idle composer and not a dead pane.
2317
+
2318
+ Match the screen by where it is drawn, not by a quote anywhere in the
2319
+ capture. 1.0.44 draws a ``┃`` modal ``You hit your free usage limit``
2320
+ (no Grok footer). 0.2.51 draws a status line that starts ``Retry failed:``
2321
+ on the current screen, under the composer. A cursor transcript that
2322
+ quotes those sentences, and a grok scrollback that still contains them
2323
+ above an idle screen, are not that screen.
2317
2324
  """
2318
- blob = "\n".join(lines).lower()
2319
- if "you hit your free usage limit" in blob:
2325
+ screen = lines[-_GROK_SCREEN_ROWS:]
2326
+ # Opencode draws its transcript in ┃ rows, so a quoted sentence there is
2327
+ # not the grok modal. The modal identifies itself (Upgrade to SuperGrok)
2328
+ # or the pane is already grok.
2329
+ modal = "\n".join(ln for ln in screen if "┃" in ln).lower()
2330
+ if ("you hit your free usage limit" in modal
2331
+ and (_is_grok_pane(screen) or "upgrade to supergrok" in modal)):
2320
2332
  return "grok-usage-limit"
2321
- if "rate limit" in blob:
2322
- return "grok-rate-limit"
2323
- if "usage limit" in blob:
2324
- return "grok-usage-limit"
2325
- if "retry failed" in blob:
2333
+ if not _is_grok_pane(screen):
2334
+ return None
2335
+ for ln in screen:
2336
+ stripped = ln.strip()
2337
+ low = stripped.lower()
2338
+ if not stripped.startswith("Retry failed:"):
2339
+ continue
2340
+ if "rate limit" in low:
2341
+ return "grok-rate-limit"
2326
2342
  return "grok-retry-failed"
2327
2343
  return None
2328
2344
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.69.6
3
+ Version: 0.69.8
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
@@ -224,6 +225,7 @@ tests/test_716_timeline_one_path.py
224
225
  tests/test_723_grok_0251.py
225
226
  tests/test_724_ps1_ascii_or_bom.py
226
227
  tests/test_725_stdio_reconfigure.py
228
+ tests/test_726_grok_limit_screen.py
227
229
  tests/test_729_dm_once.py
228
230
  tests/test_729_wake_watchdog.py
229
231
  tests/test_734_dreaming_enrich_degrade.py
@@ -0,0 +1,291 @@
1
+ """card #1006 — CLI session board. The desktop tool names must not appear."""
2
+ import json
3
+ import subprocess
4
+ import sys
5
+ from pathlib import Path
6
+
7
+ ROOT = Path(__file__).resolve().parents[1]
8
+ SKILL = ROOT / "plugins" / "swarph" / "skills" / "lej-session-board"
9
+ FIX = ROOT / "tests" / "fixtures" / "session-board"
10
+ SCRIPT = SKILL / "scripts" / "build_board.py"
11
+
12
+ # Names that exist only on Claude Desktop. Referencing one is a fail.
13
+ DESKTOP_TOOLS = (
14
+ "mcp__ccd_session_mgmt__",
15
+ "list_sessions",
16
+ "list_events",
17
+ "archive_session",
18
+ "set_pinned",
19
+ "get_session",
20
+ )
21
+
22
+
23
+ def _skill_texts():
24
+ files = [SKILL / "SKILL.md", SKILL / "references" / "coordination-rule.md", SCRIPT]
25
+ return {p.relative_to(SKILL).as_posix(): p.read_text(encoding="utf-8") for p in files}
26
+
27
+
28
+ def desktop_hits(text: str) -> list[str]:
29
+ return [name for name in DESKTOP_TOOLS if name in text]
30
+
31
+
32
+ def test_desktop_tool_name_is_not_referenced():
33
+ for rel, text in _skill_texts().items():
34
+ assert desktop_hits(text) == [], rel
35
+
36
+
37
+ def test_the_scanner_fails_when_a_desktop_tool_name_is_added():
38
+ poisoned = "call mcp__ccd_session_mgmt__list_sessions here"
39
+ assert "mcp__ccd_session_mgmt__" in desktop_hits(poisoned)
40
+
41
+
42
+ def test_license_and_attribution_kept():
43
+ lic = (SKILL / "LICENSE").read_text(encoding="utf-8")
44
+ assert "MIT License" in lic
45
+ assert "Jonathan Edwards" in lic
46
+ readme = (SKILL / "README.md").read_text(encoding="utf-8")
47
+ assert "Jonathan Edwards" in readme
48
+ assert "MIT" in readme
49
+
50
+
51
+ def test_coordination_rule_uses_listagents_and_sendmessage_by_name():
52
+ text = (SKILL / "references" / "coordination-rule.md").read_text(encoding="utf-8")
53
+ assert "ListAgents" in text
54
+ assert "SendMessage" in text
55
+ assert "by the name" in text
56
+ assert "approves building, not deploying" in text
57
+ assert desktop_hits(text) == []
58
+
59
+
60
+ def _run_board():
61
+ proc = subprocess.run(
62
+ [sys.executable, str(SCRIPT),
63
+ "--listing", str(FIX / "listagents-lab.txt"),
64
+ "--rows", str(FIX / "commander-170.json")],
65
+ check=True, capture_output=True, text=True,
66
+ )
67
+ return json.loads(proc.stdout)
68
+
69
+
70
+ def _validate(board: dict):
71
+ """The fields references/data-schema.md says the page reads."""
72
+ schema = (SKILL / "references" / "data-schema.md").read_text(encoding="utf-8")
73
+ for field in ("updated", "projectFolder", "manager", "sessions", "groups"):
74
+ assert field in schema
75
+ assert field in board
76
+ assert isinstance(board["sessions"], list) and board["sessions"]
77
+ for sess in board["sessions"]:
78
+ for field in ("id", "name", "state", "tone", "where", "questions"):
79
+ assert field in sess
80
+ assert sess["tone"] in ("needs", "done", "")
81
+ assert 2 <= len(sess["where"]) <= 4
82
+ for q in sess["questions"]:
83
+ for field in ("id", "to", "title", "options", "to_node", "in_session"):
84
+ assert field in q
85
+ assert field in schema
86
+ assert "card" in schema and "obligation" in schema
87
+ assert q["to_node"] == q["to"]
88
+ assert isinstance(q["in_session"], bool)
89
+ assert 2 <= len(q["options"]) <= 3
90
+ assert sum(1 for opt in q["options"] if opt.get("rec")) == 1
91
+ for opt in q["options"]:
92
+ assert opt["label"] and opt["text"]
93
+ for group in board["groups"]:
94
+ assert "title" in group and "items" in group
95
+
96
+
97
+ def test_skill_maps_a_name_through_the_sessions_file_and_lists_shell():
98
+ text = (SKILL / "SKILL.md").read_text(encoding="utf-8")
99
+ step = text.split("## 1. Take the roster", 1)[1].split("## 2.", 1)[0]
100
+ assert "~/.claude/sessions/<pid>.json" in step
101
+ assert "name" in step and "sessionId" in step and "cwd" in step
102
+ assert "`shell`" in step
103
+ assert "in progress" in step.lower() or "In progress" in step
104
+
105
+
106
+ def test_shell_status_is_in_progress():
107
+ sys.path.insert(0, str(SCRIPT.parent))
108
+ import build_board
109
+ listing = "This session is lab-ovh [1e23f6]\npeer [abc123] · interactive · shell · running a command\n"
110
+ board = build_board.build(listing, [])
111
+ sess = next(s for s in board["sessions"] if s["name"] == "peer")
112
+ assert sess["bucket"] == "in_progress"
113
+ assert sess["tone"] == ""
114
+
115
+
116
+ def test_schema_documents_the_four_new_question_fields():
117
+ schema = (SKILL / "references" / "data-schema.md").read_text(encoding="utf-8")
118
+ for field in ("to_node", "card", "obligation", "in_session"):
119
+ assert f"`{field}`" in schema
120
+
121
+
122
+ def test_skill_builds_schema_valid_json_without_desktop_tools():
123
+ board = _run_board()
124
+ _validate(board)
125
+ assert board["manager"] == "lab-ovh"
126
+
127
+
128
+ def test_lab_roster_sorts_offline_remote_control_into_stale():
129
+ listing = (FIX / "listagents-lab.txt").read_text(encoding="utf-8")
130
+ board = _run_board()
131
+ by_id = {s["id"]: s for s in board["sessions"]}
132
+ # every offline Remote Control row from the real listing is stale
133
+ seen = 0
134
+ for line in listing.splitlines():
135
+ if "Remote Control" in line and "offline" in line:
136
+ sid = line.split("[", 1)[1].split("]", 1)[0]
137
+ assert by_id[sid]["bucket"] == "stale"
138
+ assert by_id[sid]["tone"] == "done"
139
+ seen += 1
140
+ assert seen >= 40
141
+
142
+
143
+ def test_droplet_section_carries_the_commander_row_with_a_recommendation():
144
+ board = _run_board()
145
+ droplet = next(s for s in board["sessions"] if s["name"] == "droplet")
146
+ assert droplet["bucket"] == "needs_followup"
147
+ assert droplet["tone"] == "needs"
148
+ q = droplet["questions"][0]
149
+ assert "687" in q["title"] or "rebalancer" in q["title"]
150
+ rec = [opt for opt in q["options"] if opt["rec"]]
151
+ assert len(rec) == 1
152
+ assert rec[0]["label"] == "Retire"
153
+
154
+
155
+ def test_filling_the_template_does_not_change_the_markup():
156
+ sys.path.insert(0, str(SCRIPT.parent))
157
+ import build_board
158
+ template = (SKILL / "assets" / "board-template.html").read_text(encoding="utf-8")
159
+ board = _run_board()
160
+ # the page does not read the sort bucket
161
+ public = json.loads(json.dumps(board))
162
+ for sess in public["sessions"]:
163
+ sess.pop("bucket", None)
164
+ filled = build_board.fill_template(template, public)
165
+ start = template.find('<script type="application/json" id="board-data">')
166
+ end = template.find("</script>", start)
167
+ assert template[:start] == filled[:start]
168
+ assert template[end:] == filled[filled.find("</script>", start):]
169
+ data = json.loads(filled[filled.find(">", start) + 1:filled.find("</script>", start)])
170
+ _validate(data)
171
+
172
+
173
+ def test_envelope_header_and_new_fields():
174
+ sys.path.insert(0, str(SCRIPT.parent))
175
+ import build_board
176
+ board = _run_board()
177
+ body = build_board.envelope(board)
178
+ header, payload = body.split("\n", 1)
179
+ assert header == "SWARPH-BOARD v1"
180
+ parsed = json.loads(payload)
181
+ _validate(parsed)
182
+ droplet = next(s for s in parsed["sessions"] if s["name"] == "droplet")
183
+ q = droplet["questions"][0]
184
+ assert q["to_node"] == "droplet"
185
+ assert q["card"] == 687
186
+ assert q["obligation"] == 170
187
+ assert q["in_session"] is False
188
+ assert "bucket" not in droplet
189
+
190
+
191
+ def test_unchanged_board_sends_nothing():
192
+ sys.path.insert(0, str(SCRIPT.parent))
193
+ import build_board
194
+ board = _run_board()
195
+ sent = []
196
+ assert build_board.publish_if_changed(board, build_board.envelope(board), sent.append) is False
197
+ assert sent == []
198
+ # a clock-only difference is not a change
199
+ later = json.loads(json.dumps(board))
200
+ later["updated"] = "2099-01-01 00:00 UTC"
201
+ assert build_board.publish_if_changed(later, build_board.envelope(board), sent.append) is False
202
+ assert sent == []
203
+
204
+
205
+ def test_a_changed_board_sends_the_envelope_once():
206
+ sys.path.insert(0, str(SCRIPT.parent))
207
+ import build_board
208
+ board = _run_board()
209
+ changed = json.loads(json.dumps(board))
210
+ droplet = next(s for s in changed["sessions"] if s["name"] == "droplet")
211
+ droplet["questions"][0]["title"] = "A different question"
212
+ sent = []
213
+ assert build_board.publish_if_changed(changed, build_board.envelope(board), sent.append) is True
214
+ assert len(sent) == 1
215
+ assert sent[0].startswith("SWARPH-BOARD v1\n")
216
+ assert "A different question" in sent[0]
217
+
218
+
219
+ def test_deploy_accept_sets_in_session_and_every_question_has_to_node():
220
+ sys.path.insert(0, str(SCRIPT.parent))
221
+ import build_board
222
+ listing = (FIX / "listagents-lab.txt").read_text(encoding="utf-8")
223
+ rows = json.loads((FIX / "commander-170.json").read_text(encoding="utf-8"))
224
+ rows.append({
225
+ "cell": "droplet",
226
+ "card_id": 9001,
227
+ "obligation_id": 9001,
228
+ "title": "Production deploy of the board tab",
229
+ "accept": "The commander types the production deploy go in that session. This row is a hard gate.",
230
+ "options": [
231
+ {"label": "Go", "text": "go", "rec": True},
232
+ {"label": "Hold", "text": "hold", "rec": False},
233
+ ],
234
+ })
235
+ board = build_board.build(listing, rows)
236
+ questions = []
237
+ for sess in board["sessions"]:
238
+ questions.extend(sess["questions"])
239
+ assert questions
240
+ assert all(q.get("to_node") for q in questions)
241
+ deploy = next(q for q in questions if q["title"] == "Production deploy of the board tab")
242
+ retire = next(q for q in questions if q["obligation"] == 170)
243
+ assert deploy["in_session"] is True
244
+ assert deploy["to_node"] == "droplet"
245
+ assert deploy["card"] == 9001
246
+ assert retire["in_session"] is False
247
+
248
+
249
+ def test_an_answer_dm_drops_that_question():
250
+ sys.path.insert(0, str(SCRIPT.parent))
251
+ import build_board
252
+ rows = json.loads((FIX / "commander-170.json").read_text(encoding="utf-8"))
253
+ title = rows[0]["title"]
254
+ kept = build_board.drop_answered(
255
+ rows, [("commander", f"Re: {title}\nRetire.\n")], commander="commander")
256
+ assert kept == []
257
+
258
+
259
+ def test_only_the_commander_can_drop_a_question():
260
+ """A Re: line from any other cell must leave the question up.
261
+
262
+ On 61c5f49 drop_answered took bare bodies and dropped the row for any sender.
263
+ """
264
+ sys.path.insert(0, str(SCRIPT.parent))
265
+ import build_board
266
+ rows = json.loads((FIX / "commander-170.json").read_text(encoding="utf-8"))
267
+ title = rows[0]["title"]
268
+ body = f"Re: {title}\nRetire.\n"
269
+ kept = build_board.drop_answered(rows, [("lab-ovh", body)], commander="commander")
270
+ assert len(kept) == 1
271
+ assert kept[0]["title"] == title
272
+ cleared = build_board.drop_answered(rows, [("commander", body)], commander="commander")
273
+ assert cleared == []
274
+
275
+
276
+ def test_redeploy_is_in_session_and_deployment_notes_are_not():
277
+ sys.path.insert(0, str(SCRIPT.parent))
278
+ import build_board
279
+ assert build_board._in_session({"accept": "redeploy the gateway"}) is True
280
+ assert build_board._in_session({"accept": "deployment notes for the board"}) is False
281
+ assert build_board._in_session({"accept": "production deploy go"}) is True
282
+
283
+
284
+ def test_skill_step_4_names_the_sender_and_the_trigger_words():
285
+ text = (SKILL / "SKILL.md").read_text(encoding="utf-8")
286
+ step = text.split("## 4. Answers", 1)[1].split("## ", 1)[0]
287
+ assert "from_node" in step
288
+ assert "commander" in step
289
+ assert "any other cell" in step
290
+ for word in ("deploy", "redeploy", "hard gate"):
291
+ assert word in step
@@ -0,0 +1,60 @@
1
+ """card #961 — the limit check must be the limit screen, not a quote.
2
+
3
+ These assertions fail on 1d55233: that head treats any line containing the
4
+ limit sentences as a grok block, including a cursor transcript and grok
5
+ scrollback above an idle screen.
6
+ """
7
+ from pathlib import Path
8
+
9
+ from swarph_cli.commands import mesh
10
+
11
+ FIX723 = Path(__file__).resolve().parents[1] / "tests" / "fixtures" / "grok-723"
12
+ FIX726 = Path(__file__).resolve().parents[1] / "tests" / "fixtures" / "grok-726"
13
+
14
+
15
+ def lines(root, name):
16
+ return (root / name).read_text(encoding="utf-8").splitlines()
17
+
18
+
19
+ def test_cursor_transcript_quoting_both_limits_is_not_blocked():
20
+ pane = lines(FIX726, "cursor-transcript.txt")
21
+ text = "\n".join(pane)
22
+ assert "rate limit" in text
23
+ assert "You hit your free usage limit" in text
24
+ assert "Models matching" in text
25
+ assert mesh._is_grok_pane(pane) is False
26
+ assert mesh._grok_block_reason(pane) is None
27
+
28
+
29
+ def test_opencode_transcript_quoting_the_usage_limit_is_not_blocked():
30
+ src = Path(__file__).resolve().parents[1] / "tests" / "fixtures" / "opencode-pane-idle-footer-0710.txt"
31
+ pane = src.read_text(encoding="utf-8").splitlines()
32
+ # Inside the current screen, on a ┃ transcript row, not a grok modal.
33
+ pane.insert(len(pane) - 8, " ┃ You hit your free usage limit")
34
+ assert mesh._is_opencode_pane(pane) is True
35
+ assert mesh._is_grok_pane(pane) is False
36
+ assert mesh._grok_block_reason(pane) is None
37
+
38
+
39
+ def test_grok_rate_limit_and_usage_modal_still_defer():
40
+ rate = lines(FIX723, "rate-limit.txt")
41
+ usage = lines(FIX723, "usage-limit.txt")
42
+ assert mesh._grok_block_reason(rate) == "grok-rate-limit"
43
+ assert mesh._grok_turn_finished(rate) is False
44
+ assert mesh._is_grok_pane(usage) is False
45
+ assert mesh._grok_block_reason(usage) == "grok-usage-limit"
46
+
47
+
48
+ def test_old_scrollback_limit_on_an_idle_grok_screen_reads_idle():
49
+ idle = lines(FIX723, "idle.txt")
50
+ scroll = [
51
+ " ❯ an earlier turn",
52
+ " Retry failed: You’ve hit the rate limit for your plan.",
53
+ " You hit your free usage limit",
54
+ ]
55
+ # Push the quote above one screen. The idle composer stays.
56
+ pane = (scroll * 20) + idle
57
+ assert mesh._is_grok_pane(pane)
58
+ assert mesh._grok_block_reason(pane) is None
59
+ assert mesh._grok_composer_state(pane) == "clear"
60
+ assert mesh._grok_running(pane) is False
@@ -1,9 +1,8 @@
1
- """#960: a fresh channel heartbeat is a reader. Do not start a monitor beside it.
1
+ """#960: a fresh channel heartbeat is the reader, not the writer.
2
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.
3
+ channel-serve writes channel_heartbeat.json and only reads inbox.log. The
4
+ pull monitor is the only writer. A fresh heartbeat must not stop that
5
+ monitor from starting. A live monitor (status 0) still starts nothing.
7
6
  """
8
7
  from __future__ import annotations
9
8
 
@@ -61,11 +60,14 @@ def _run(tmp_path: Path, *, age: float | None):
61
60
  return proc, calls
62
61
 
63
62
 
64
- def test_fresh_heartbeat_does_not_start(tmp_path):
63
+ def test_fresh_heartbeat_with_no_monitor_starts(tmp_path):
64
+ """The shim exits 2, so no monitor is running. A 10s-old heartbeat
65
+ still has to start the pull monitor. On main this asserts the opposite.
66
+ """
65
67
  proc, calls = _run(tmp_path, age=10)
66
68
  assert proc.returncode == 0
67
- assert "monitor start" not in calls
68
- assert "under 180s" in proc.stdout
69
+ assert "monitor start" in calls
70
+ assert "under 180s" not in proc.stdout
69
71
 
70
72
 
71
73
  def test_stale_heartbeat_still_starts(tmp_path):
@@ -107,10 +109,10 @@ def test_a_future_timestamp_an_hour_ahead_starts(tmp_path):
107
109
  assert "monitor start" in calls
108
110
 
109
111
 
110
- def test_a_timestamp_within_skew_stays_fresh(tmp_path):
112
+ def test_a_timestamp_within_skew_still_starts_when_no_monitor(tmp_path):
111
113
  proc, calls = _run_payload(tmp_path, {"ts": time.time() + 10})
112
114
  assert proc.returncode == 0
113
- assert "monitor start" not in calls
115
+ assert "monitor start" in calls
114
116
 
115
117
 
116
118
  def test_a_fresh_heartbeat_with_a_dead_pid_starts(tmp_path):
@@ -120,12 +122,8 @@ def test_a_fresh_heartbeat_with_a_dead_pid_starts(tmp_path):
120
122
  assert "monitor start" in calls
121
123
 
122
124
 
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
- """
125
+ def test_a_fresh_heartbeat_with_a_live_reader_pid_still_starts(tmp_path):
126
+ """A live channel-serve pid is the reader. It does not stand in for the monitor."""
129
127
  child = None
130
128
  if os.path.isdir("/proc/self"):
131
129
  child = subprocess.Popen(
@@ -140,7 +138,40 @@ def test_a_fresh_heartbeat_with_a_live_pid_skips(tmp_path):
140
138
  child.kill()
141
139
  child.wait(timeout=5)
142
140
  assert proc.returncode == 0
141
+ assert "monitor start" in calls
142
+
143
+
144
+ def test_fresh_heartbeat_and_a_live_monitor_starts_nothing(tmp_path):
145
+ """Status 0 is the writer. A fresh heartbeat beside it must not start a second one."""
146
+ bindir = tmp_path / "bin"
147
+ bindir.mkdir()
148
+ shim = bindir / "swarph"
149
+ shim.write_text(
150
+ "#!/usr/bin/env bash\n"
151
+ "printf '%s\\n' \"$*\" >> \"$FAKE_LOG\"\n"
152
+ "case \"$*\" in\n"
153
+ " *'monitor status'*) exit 0 ;;\n"
154
+ " *) exit 2 ;;\n"
155
+ "esac\n",
156
+ encoding="utf-8",
157
+ )
158
+ shim.chmod(0o755)
159
+ log = tmp_path / "calls.log"
160
+ side = tmp_path / "state" / "mesh-sidecar"
161
+ side.mkdir(parents=True)
162
+ (side / "channel_heartbeat.json").write_text(
163
+ json.dumps({"ts": time.time()}), encoding="utf-8")
164
+ env = dict(os.environ)
165
+ env["PATH"] = f"{bindir}{os.pathsep}{env.get('PATH', '')}"
166
+ env["SWARPH_SELF"] = "cursor-lin"
167
+ env["SWARPH_STATE_DIR"] = str(tmp_path / "state")
168
+ env["FAKE_LOG"] = str(log)
169
+ proc = subprocess.run(
170
+ [_bash(), str(SCRIPT)], env=env, capture_output=True, text=True, timeout=60)
171
+ calls = log.read_text(encoding="utf-8") if log.exists() else ""
172
+ assert proc.returncode == 0
143
173
  assert "monitor start" not in calls
174
+ assert "monitor status" in calls
144
175
 
145
176
 
146
177
  def test_pid_zero_starts(tmp_path):
File without changes
File without changes
File without changes