swarph-cli 0.72.32__tar.gz → 0.72.34__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 (541) hide show
  1. {swarph_cli-0.72.32/src/swarph_cli.egg-info → swarph_cli-0.72.34}/PKG-INFO +1 -1
  2. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/pyproject.toml +1 -1
  3. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/mcp_server.py +83 -9
  5. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/index_check.py +28 -4
  6. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/notify.py +53 -2
  7. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/triage/backend.py +6 -2
  8. {swarph_cli-0.72.32 → swarph_cli-0.72.34/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  9. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli.egg-info/SOURCES.txt +3 -0
  10. swarph_cli-0.72.34/tests/test_1269_triage_label.py +25 -0
  11. swarph_cli-0.72.34/tests/test_1278_dreaming_reach.py +70 -0
  12. swarph_cli-0.72.34/tests/test_1286_dm_unread_cursor.py +192 -0
  13. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_735_dm_mcp_tools.py +4 -1
  14. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/LICENSE +0 -0
  15. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/README.md +0 -0
  16. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/setup.cfg +0 -0
  17. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/__main__.py +0 -0
  18. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/__init__.py +0 -0
  19. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/backends.py +0 -0
  20. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/claude_cli.py +0 -0
  21. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/codex_cli.py +0 -0
  22. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/pack.py +0 -0
  23. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/prices.py +0 -0
  24. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/providers.py +0 -0
  25. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/quality.py +0 -0
  26. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/refresh_prices.py +0 -0
  27. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/rules.py +0 -0
  28. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/runner.py +0 -0
  29. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/signal_score.py +0 -0
  30. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/bench/validate.py +0 -0
  31. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/caller.py +0 -0
  32. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/capture/__init__.py +0 -0
  33. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/capture/harden.py +0 -0
  34. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/capture/lineage.py +0 -0
  35. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/capture/liveness.py +0 -0
  36. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/capture/manifest.py +0 -0
  37. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/capture/paths.py +0 -0
  38. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/capture/verify.py +0 -0
  39. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/cell.py +0 -0
  40. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/chain_token.py +0 -0
  41. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/channel.py +0 -0
  42. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/__init__.py +0 -0
  43. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/_content.py +0 -0
  44. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/_display.py +0 -0
  45. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/_gateway_client.py +0 -0
  46. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/add.py +0 -0
  47. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/bench.py +0 -0
  48. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/board.py +0 -0
  49. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/board_merge_check.py +0 -0
  50. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/brain.py +0 -0
  51. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/brain_ask.py +0 -0
  52. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/cell.py +0 -0
  53. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/cell_probe.py +0 -0
  54. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  55. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/channel.py +0 -0
  56. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/chat.py +0 -0
  57. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/codegraph.py +0 -0
  58. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  59. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  60. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/codex_waker.py +0 -0
  61. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/compress.py +0 -0
  62. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/daemon.py +0 -0
  63. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/dreaming.py +0 -0
  64. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/event.py +0 -0
  65. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/gateway.py +0 -0
  66. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/gh_route.py +0 -0
  67. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/group.py +0 -0
  68. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/guide.py +0 -0
  69. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/highlight.py +0 -0
  70. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  71. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/hook_output.py +0 -0
  72. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/hooks.py +0 -0
  73. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/import_session.py +0 -0
  74. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/init.py +0 -0
  75. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  76. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/install_hook.py +0 -0
  77. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  78. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  79. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  80. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  81. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/lane.py +0 -0
  82. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/memory.py +0 -0
  83. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  84. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/memory_sync.py +0 -0
  85. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/mesh.py +0 -0
  86. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/monitor.py +0 -0
  87. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/okf_links.py +0 -0
  88. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/onboard.py +0 -0
  89. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  90. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  91. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/protocol_handler.py +0 -0
  92. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/ratify.py +0 -0
  93. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/roster.py +0 -0
  94. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/rowclear_hook.py +0 -0
  95. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/schedule.py +0 -0
  96. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/security.py +0 -0
  97. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/service.py +0 -0
  98. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/spawn.py +0 -0
  99. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/sweep.py +0 -0
  100. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/timeline.py +0 -0
  101. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/triage.py +0 -0
  102. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/version.py +0 -0
  103. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  104. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/commands/watchdog.py +0 -0
  105. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/compress/__init__.py +0 -0
  106. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/compress/levers.py +0 -0
  107. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/compress/marker.py +0 -0
  108. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/compress/verify.py +0 -0
  109. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/console_safe.py +0 -0
  110. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/delivery_queue.py +0 -0
  111. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dm_frame.py +0 -0
  112. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/__init__.py +0 -0
  113. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/candidates.py +0 -0
  114. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/clone.py +0 -0
  115. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/enrich.py +0 -0
  116. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/organize.py +0 -0
  117. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/probes.py +0 -0
  118. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/report.py +0 -0
  119. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/run.py +0 -0
  120. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/slm.py +0 -0
  121. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/transcripts.py +0 -0
  122. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/dreaming/verify.py +0 -0
  123. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/duplicate_session.py +0 -0
  124. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/gateway/__init__.py +0 -0
  125. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/gateway/feature_registry.py +0 -0
  126. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/gateway/lanes_control.py +0 -0
  127. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/gateway/schema.sql +0 -0
  128. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/gateway/server.py +0 -0
  129. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/gateway/services_control.py +0 -0
  130. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/gateway_default.py +0 -0
  131. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/gh_identity.py +0 -0
  132. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/guide/GUIDE.md +0 -0
  133. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/identity.py +0 -0
  134. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/launchd/swarph-codex-outbox.plist +0 -0
  135. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/launchd/swarph-codex-outbox.timer.plist +0 -0
  136. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/launchd/swarph-monitor.plist +0 -0
  137. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/launchd/swarph-watchdog.plist +0 -0
  138. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/launchd/swarph-watchdog.timer.plist +0 -0
  139. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/launchd_render.py +0 -0
  140. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/main.py +0 -0
  141. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/multiplexer.py +0 -0
  142. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/operator/__init__.py +0 -0
  143. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/operator/__main__.py +0 -0
  144. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/operator/api.py +0 -0
  145. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/operator/cli.py +0 -0
  146. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/operator/config.py +0 -0
  147. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/operator/fmt.py +0 -0
  148. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/orphan_daemons.py +0 -0
  149. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/pane_probe.py +0 -0
  150. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/parsers/__init__.py +0 -0
  151. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/parsers/claude.py +0 -0
  152. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  153. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  154. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  155. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  156. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  157. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  158. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  159. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  160. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  161. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  162. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  163. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  164. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/peer_executor.py +0 -0
  165. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/peer_executor_windows.py +0 -0
  166. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/peer_reconciliation.py +0 -0
  167. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/peer_reply_outbox.py +0 -0
  168. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/peer_service_host.py +0 -0
  169. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/peer_service_payload.py +0 -0
  170. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/peer_service_reply.py +0 -0
  171. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/peer_staging.py +0 -0
  172. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/query_filters.py +0 -0
  173. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/quota_sync.py +0 -0
  174. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/rowclear.py +0 -0
  175. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/scripts/__init__.py +0 -0
  176. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  177. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  178. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  179. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  180. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  181. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/scrub.py +0 -0
  182. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/service/__init__.py +0 -0
  183. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/service/app.py +0 -0
  184. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/service/providers.py +0 -0
  185. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/session_bridge.py +0 -0
  186. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  187. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  188. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/source_text.py +0 -0
  189. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/stall_alert.py +0 -0
  190. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/codex-waker.default +0 -0
  191. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  192. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  193. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  194. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  195. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  196. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  197. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  198. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  199. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  200. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  201. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-stuck-sweep.service +0 -0
  202. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-stuck-sweep.timer +0 -0
  203. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-triage.service +0 -0
  204. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-triage.timer +0 -0
  205. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  206. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  207. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  208. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/the_ten.py +0 -0
  209. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/timeline_paths.py +0 -0
  210. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/tokens.py +0 -0
  211. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/triage/__init__.py +0 -0
  212. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/triage/job.py +0 -0
  213. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/triage/owners.json +0 -0
  214. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/triage/router.py +0 -0
  215. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/triage/rows.py +0 -0
  216. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli/triage/sensor.py +0 -0
  217. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  218. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  219. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli.egg-info/requires.txt +0 -0
  220. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/src/swarph_cli.egg-info/top_level.txt +0 -0
  221. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1006_board_publisher.py +0 -0
  222. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1006_session_board.py +0 -0
  223. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1010_strand_only.py +0 -0
  224. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1013_spawn_channel.py +0 -0
  225. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1017_muse_tui.py +0 -0
  226. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1034_copy_mode.py +0 -0
  227. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1050_surrogate_wake.py +0 -0
  228. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1052_opencode_deaf.py +0 -0
  229. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1059_watchdog_unread_env.py +0 -0
  230. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1061_roster_cli.py +0 -0
  231. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1064_stuck_sweep.py +0 -0
  232. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1065_release.py +0 -0
  233. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1066_quiet_wakes.py +0 -0
  234. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1068_structured_accept_cli.py +0 -0
  235. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1071_quota_sync.py +0 -0
  236. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1073_triage.py +0 -0
  237. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1073_triage_board.py +0 -0
  238. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1073_wheel_install.py +0 -0
  239. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1075_task_cli.py +0 -0
  240. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1083_openai_decisions.py +0 -0
  241. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1144_cell_yaml_github.py +0 -0
  242. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1197_commander_taps.py +0 -0
  243. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1203_swarph_me_refusal.py +0 -0
  244. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1206_offer_question.py +0 -0
  245. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1214_egress.py +0 -0
  246. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1219_egress_probe.py +0 -0
  247. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1221_quota_windows.py +0 -0
  248. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1222_maybe_start_cap.py +0 -0
  249. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1228_quota_reach.py +0 -0
  250. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1243_the_ten.py +0 -0
  251. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1248_ship_price_table.py +0 -0
  252. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_124_register_merge.py +0 -0
  253. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1250_price_cache.py +0 -0
  254. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1256_commander_wake.py +0 -0
  255. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1261_mcp_install.py +0 -0
  256. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1262_wake_followups.py +0 -0
  257. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_1265_wake_note.py +0 -0
  258. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_126_pending_from_complete.py +0 -0
  259. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  260. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_138_memory_navigate.py +0 -0
  261. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_139_tool_probe.py +0 -0
  262. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_183_card_anchor.py +0 -0
  263. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_184_notify_honesty.py +0 -0
  264. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_201_win32_bash_refusal.py +0 -0
  265. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_243_onboard_per_peer_token.py +0 -0
  266. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_307_task4_ask_reply.py +0 -0
  267. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  268. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_319_refusal_exit_codes.py +0 -0
  269. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_323_mistral_backend.py +0 -0
  270. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_332_report_organize_findings.py +0 -0
  271. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_356_query_filters.py +0 -0
  272. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_397_gh_identity_router.py +0 -0
  273. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_397_router_preflight.py +0 -0
  274. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_402_one_identity_resolver.py +0 -0
  275. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_432_rowclear.py +0 -0
  276. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_467b_onboard_operator_credential.py +0 -0
  277. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_472_register_caller_binding.py +0 -0
  278. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_484_codex_queue_sink.py +0 -0
  279. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_513_duplicate_session.py +0 -0
  280. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_523_guide.py +0 -0
  281. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_524_conftest_hermeticity.py +0 -0
  282. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_532_accept_flag.py +0 -0
  283. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_535_version_capability.py +0 -0
  284. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_538_signal_bench.py +0 -0
  285. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_546_default_gateway.py +0 -0
  286. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_547_verbs_from_registry.py +0 -0
  287. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_548_gateway_remainder.py +0 -0
  288. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_552_codegraph_gateway.py +0 -0
  289. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_555_relay_row_shape.py +0 -0
  290. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_556_relay_freshness.py +0 -0
  291. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_559_edges_truncated.py +0 -0
  292. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_564_bootstrap_first_peer.py +0 -0
  293. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  294. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_579_register_bootstrap_message.py +0 -0
  295. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_591_cli_step_graph.py +0 -0
  296. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_644_monitor_supervision.py +0 -0
  297. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_648_install_unit.py +0 -0
  298. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_654_reexec.py +0 -0
  299. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_656_owned_slm.py +0 -0
  300. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_684_dreaming_verb.py +0 -0
  301. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_689_pkg_version_order.py +0 -0
  302. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_715_grok_wake.py +0 -0
  303. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_716_timeline_one_path.py +0 -0
  304. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_723_grok_0251.py +0 -0
  305. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  306. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_725_stdio_reconfigure.py +0 -0
  307. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_726_grok_limit_screen.py +0 -0
  308. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_729_arm_check_delivery_gap.py +0 -0
  309. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_729_dm_once.py +0 -0
  310. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_729_wake_watchdog.py +0 -0
  311. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  312. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_739_wake_writer.py +0 -0
  313. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_740_edit_project.py +0 -0
  314. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_762_cards_edit_priority.py +0 -0
  315. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_764_enrich_collapse.py +0 -0
  316. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_801_confirm_flow_cli.py +0 -0
  317. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_802_stage_history.py +0 -0
  318. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_825_codegraph_hook_noise.py +0 -0
  319. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_825_install_posttooluse_only.py +0 -0
  320. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  321. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_829_counterfactual_column.py +0 -0
  322. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_830_hook_binding_version.py +0 -0
  323. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_84_cli_labels.py +0 -0
  324. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_866_doctrine_guide.py +0 -0
  325. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_872_identity_refuses_not_warns.py +0 -0
  326. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_874_source_text.py +0 -0
  327. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  328. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_931_board_search.py +0 -0
  329. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_935_deadline_clock.py +0 -0
  330. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_937_dreaming_notify.py +0 -0
  331. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_938_organize_line_limit.py +0 -0
  332. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_941_bench_backends.py +0 -0
  333. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_941_harness_342.py +0 -0
  334. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_945_same_line_pairing.py +0 -0
  335. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_955_prompt_audit.py +0 -0
  336. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_957_auth_http.py +0 -0
  337. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_959_venv_interpreter.py +0 -0
  338. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_960_channel.py +0 -0
  339. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_960_ensure_monitor_heartbeat.py +0 -0
  340. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_961_cursor_grok_footer.py +0 -0
  341. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_961_loud_stop.py +0 -0
  342. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_963_plugin_manifest.py +0 -0
  343. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_964_channel_cell.py +0 -0
  344. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_965_mesh_wait.py +0 -0
  345. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_975_channel_hop.py +0 -0
  346. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_add_security_gate.py +0 -0
  347. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_artifact_add.py +0 -0
  348. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_artifact_hash.py +0 -0
  349. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_artifact_lib.py +0 -0
  350. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_artifact_mcp.py +0 -0
  351. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_artifact_skill.py +0 -0
  352. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_artifact_tool.py +0 -0
  353. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_artifact_uri.py +0 -0
  354. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_bench_backends.py +0 -0
  355. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_bench_claude_cli.py +0 -0
  356. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_bench_codex_cli.py +0 -0
  357. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_bench_command.py +0 -0
  358. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_bench_pack.py +0 -0
  359. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_bench_prices.py +0 -0
  360. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_bench_quality.py +0 -0
  361. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_bench_runner.py +0 -0
  362. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_bench_validate.py +0 -0
  363. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_board_card_thread.py +0 -0
  364. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_board_command.py +0 -0
  365. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_board_merge_check.py +0 -0
  366. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_board_obligation_close.py +0 -0
  367. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_brain_ask_command.py +0 -0
  368. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_brain_ask_env_diagnosis.py +0 -0
  369. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_brain_ask_identity_explicitness.py +0 -0
  370. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_brain_command.py +0 -0
  371. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_caller_meta_guard.py +0 -0
  372. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_capture_lineage.py +0 -0
  373. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_capture_liveness.py +0 -0
  374. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_capture_manifest.py +0 -0
  375. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_capture_paths.py +0 -0
  376. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_capture_security.py +0 -0
  377. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_cell_command_dispatch.py +0 -0
  378. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_cell_harden.py +0 -0
  379. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_cell_loader.py +0 -0
  380. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_cell_selfcheck.py +0 -0
  381. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_cell_verify.py +0 -0
  382. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_chain_token.py +0 -0
  383. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_channel_command.py +0 -0
  384. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  385. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_channel_post.py +0 -0
  386. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_channel_wake_policy_filter.py +0 -0
  387. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_chat_command.py +0 -0
  388. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_claude_parser.py +0 -0
  389. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_claude_tmux_template.py +0 -0
  390. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_codegraph_caller_scope.py +0 -0
  391. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_codegraph_command.py +0 -0
  392. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_codegraph_hook.py +0 -0
  393. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_codegraph_mcp.py +0 -0
  394. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_codex_waker.py +0 -0
  395. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_compress_caller_convention.py +0 -0
  396. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_compress_command.py +0 -0
  397. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_compress_levers.py +0 -0
  398. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_compress_marker.py +0 -0
  399. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_compress_verify.py +0 -0
  400. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_console_encoding_e2e.py +0 -0
  401. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_content_composition.py +0 -0
  402. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_copilot_membrane.py +0 -0
  403. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_copilot_pane_reader.py +0 -0
  404. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_cursor_membrane.py +0 -0
  405. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_daemon_command.py +0 -0
  406. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_daemon_console_encoding.py +0 -0
  407. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_daemon_delivery.py +0 -0
  408. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_delivery_queue.py +0 -0
  409. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_display_sanitize.py +0 -0
  410. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  411. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_event_emit.py +0 -0
  412. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_feature_to_uri.py +0 -0
  413. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_gateway_brain.py +0 -0
  414. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_gateway_client.py +0 -0
  415. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_gateway_command.py +0 -0
  416. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_gateway_message_idempotency.py +0 -0
  417. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  418. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_group_command.py +0 -0
  419. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_guide_commands_parse.py +0 -0
  420. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_guide_selfcheck.py +0 -0
  421. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_heartbeat_check.py +0 -0
  422. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_highlight_command.py +0 -0
  423. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hook_output.py +0 -0
  424. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_activity_marker.py +0 -0
  425. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_add.py +0 -0
  426. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_bundle.py +0 -0
  427. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_init.py +0 -0
  428. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_legacy_generation_removal.py +0 -0
  429. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_lifecycle.py +0 -0
  430. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_merge.py +0 -0
  431. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_verify.py +0 -0
  432. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  433. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_hooks_win32_command_path.py +0 -0
  434. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_import_command.py +0 -0
  435. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_inbox_identity_provenance.py +0 -0
  436. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_init_command.py +0 -0
  437. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_init_provider_menu.py +0 -0
  438. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_install_codex_hooks.py +0 -0
  439. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_install_hook.py +0 -0
  440. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_install_multiplexer_command.py +0 -0
  441. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_install_postcompact_hook.py +0 -0
  442. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_install_wake_hook.py +0 -0
  443. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_lane_command.py +0 -0
  444. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_launchd_units.py +0 -0
  445. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_main.py +0 -0
  446. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_main_unknown_verb_guard.py +0 -0
  447. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mcp_missing_hint.py +0 -0
  448. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mcp_server.py +0 -0
  449. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_memory_command.py +0 -0
  450. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_memory_emit_snippet.py +0 -0
  451. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_memory_gateway.py +0 -0
  452. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_memory_sync.py +0 -0
  453. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_memory_sync_membrane.py +0 -0
  454. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mesh_command.py +0 -0
  455. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mesh_inbox_mark_read.py +0 -0
  456. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mesh_inbox_sanitize.py +0 -0
  457. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mesh_originate_thread_623.py +0 -0
  458. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mesh_send_unknown_peer.py +0 -0
  459. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mesh_sidecar.py +0 -0
  460. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  461. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mesh_sidecar_muse_sink.py +0 -0
  462. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  463. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_meta_edge_identity.py +0 -0
  464. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_monitor_channel_poll.py +0 -0
  465. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_monitor_cli.py +0 -0
  466. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_monitor_daemon_collision.py +0 -0
  467. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_monitor_self_name_verification.py +0 -0
  468. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_monitor_state_split.py +0 -0
  469. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_multiplexer.py +0 -0
  470. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_muse_membrane.py +0 -0
  471. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_muse_own_cli.py +0 -0
  472. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_obligation_close_is_surfaced.py +0 -0
  473. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_okf_links.py +0 -0
  474. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_onboard_command.py +0 -0
  475. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_opencode_membrane.py +0 -0
  476. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_opencode_plugin.py +0 -0
  477. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_operator_me.py +0 -0
  478. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_orphan_daemons.py +0 -0
  479. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_packaged_artifacts_exist.py +0 -0
  480. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_packaged_skills.py +0 -0
  481. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_executor.py +0 -0
  482. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_executor_windows.py +0 -0
  483. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_reply_drain_command.py +0 -0
  484. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_reply_drain_supervisor.py +0 -0
  485. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_reply_outbox.py +0 -0
  486. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_reply_transport.py +0 -0
  487. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_service_e2e.py +0 -0
  488. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_service_host.py +0 -0
  489. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_service_payload.py +0 -0
  490. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_peer_service_reply.py +0 -0
  491. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_postcompact_recall.py +0 -0
  492. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_process_liveness.py +0 -0
  493. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_protocol_handler.py +0 -0
  494. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_providers_isolation.py +0 -0
  495. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_ratify_command.py +0 -0
  496. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_readme_claims_no_gateway_default.py +0 -0
  497. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_repl_caller_convention.py +0 -0
  498. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_schedule_command.py +0 -0
  499. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_schedule_create_contract.py +0 -0
  500. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_scrub.py +0 -0
  501. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_security.py +0 -0
  502. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_service_app.py +0 -0
  503. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_service_command.py +0 -0
  504. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_service_providers.py +0 -0
  505. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_session_bridge_inject.py +0 -0
  506. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_session_bridge_probe.py +0 -0
  507. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_session_bridge_resolve.py +0 -0
  508. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_session_path_role_gate.py +0 -0
  509. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_shipped_units_match_the_cli.py +0 -0
  510. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_smoke_chat.py +0 -0
  511. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_smoke_one_shot.py +0 -0
  512. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_smoke_phase_5_5.py +0 -0
  513. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_spawn_base_hoist.py +0 -0
  514. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_spawn_command.py +0 -0
  515. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_spawn_identity_stamp.py +0 -0
  516. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_spawn_live_pin.py +0 -0
  517. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_spawn_mitosis_lineage.py +0 -0
  518. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_spawn_tmux_session.py +0 -0
  519. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_spawn_windows_relaunch.py +0 -0
  520. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_stall_alert.py +0 -0
  521. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_subprocess_decode_encoding.py +0 -0
  522. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_timeline_command.py +0 -0
  523. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_tmux_wake_runstate_defer.py +0 -0
  524. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_tmux_wake_session_reanchor.py +0 -0
  525. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_tmux_wake_stale.py +0 -0
  526. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_tmux_wake_submit_verify.py +0 -0
  527. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_token_file_one_parser.py +0 -0
  528. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_token_precedence_explicit_first.py +0 -0
  529. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_unified_token_resolver.py +0 -0
  530. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_version_command.py +0 -0
  531. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_version_consistency.py +0 -0
  532. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_vibe_membrane.py +0 -0
  533. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_watchdog.py +0 -0
  534. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_watchdog_dm_wake.py +0 -0
  535. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  536. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  537. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_watchdog_emit_health.py +0 -0
  538. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_watchdog_install_flags.py +0 -0
  539. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_watchdog_model_rung.py +0 -0
  540. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/tests/test_watchdog_stale_peers.py +0 -0
  541. {swarph_cli-0.72.32 → swarph_cli-0.72.34}/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.72.32
3
+ Version: 0.72.34
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.72.32"
7
+ version = "0.72.34"
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.72.32"
19
+ __version__ = "0.72.34"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -33,9 +33,11 @@ without the SDK installed.
33
33
  from __future__ import annotations
34
34
 
35
35
  import argparse
36
+ import datetime
36
37
  import importlib.metadata
37
38
  import json
38
39
  import os
40
+ import time
39
41
  import urllib.error
40
42
  import urllib.request
41
43
 
@@ -301,17 +303,82 @@ def _dm_error(exc_or_text) -> dict:
301
303
  return {"error": str(exc_or_text), "messages": [], "n": 0}
302
304
 
303
305
 
306
+ # One poll records the cursor at the newest id it saw. The earlier DMs in that
307
+ # poll are behind the cursor and were not delivered. They stay visible while
308
+ # they are younger than this (card #1286). Older than this, they are hidden
309
+ # and counted.
310
+ _DM_GRACE_S = 24 * 60 * 60
311
+
312
+
313
+ def _created_epoch(message: dict) -> float | None:
314
+ raw = message.get("created_at")
315
+ if raw is None or raw == "":
316
+ return None
317
+ if isinstance(raw, bool):
318
+ return None
319
+ if isinstance(raw, (int, float)):
320
+ return float(raw)
321
+ text = str(raw).strip()
322
+ if text.endswith("Z"):
323
+ text = text[:-1] + "+00:00"
324
+ try:
325
+ return datetime.datetime.fromisoformat(text).timestamp()
326
+ except ValueError:
327
+ return None
328
+
329
+
330
+ def _younger_than_grace(message: dict, now: float) -> bool:
331
+ ts = _created_epoch(message)
332
+ if ts is None:
333
+ return False
334
+ return (now - ts) < _DM_GRACE_S
335
+
336
+
337
+ def _observation_cursor(who: str) -> int:
338
+ """The monitor's last_msg_id for this cell. Missing or unreadable is 0.
339
+
340
+ Peek only: nothing here writes cursor.json or marks a message read.
341
+ """
342
+ path = _mesh._default_sidecar_state_dir(who) / "cursor.json"
343
+ try:
344
+ return int(_mesh._read_cursor(path).get("last_msg_id") or 0)
345
+ except (TypeError, ValueError):
346
+ return 0
347
+
348
+
304
349
  def _dm_unread(self_name: str | None = None) -> dict:
305
- """ALL unread DMs for self, full bodies, one call. Peek semantics: marks nothing
306
- read. Above DM_CEILING the oldest DM_CEILING are returned in full and
307
- `not_returned` says how many remain — never a shortened body, never silence."""
350
+ """Unread DMs the monitor has not yet delivered, full bodies, one call.
351
+
352
+ `read_at` is still the reply-ack (#735). The observation cursor advances
353
+ before delivery, so a DM with `id == last_msg_id` was observed and must
354
+ be returned (card #1286). One poll records the cursor at the newest id,
355
+ so the earlier DMs in that batch are behind it. A DM younger than 24 h
356
+ (`created_at`) is never hidden for that reason. A DM strictly behind the
357
+ cursor and not younger than 24 h stays hidden, and `hidden_stale` counts
358
+ those read_at-null DMs. Peek semantics: marks nothing read and does not
359
+ advance the cursor. Above DM_CEILING the oldest DM_CEILING are returned
360
+ in full and `not_returned` says how many remain.
361
+ """
308
362
  try:
309
363
  who, gw, tok = _dm_session(self_name)
310
364
  st, d = _mesh._http_get_json(
311
365
  f"{gw}/messages?to={who}&unread_only=true&limit={DM_GATEWAY_WINDOW}", tok)
312
366
  if not (200 <= st < 300):
313
367
  return _dm_error(f"gateway {st}: {d.get('detail', d) if isinstance(d, dict) else d}")
314
- msgs = sorted(d.get("messages", []), key=lambda m: int(m.get("id", 0)))
368
+ raw = sorted(d.get("messages", []), key=lambda m: int(m.get("id") or 0))
369
+ last_id = _observation_cursor(who)
370
+ now = time.time()
371
+ msgs = []
372
+ hidden_stale = 0
373
+ for message in raw:
374
+ if message.get("from_node") == who:
375
+ continue
376
+ mid = int(message.get("id") or 0)
377
+ if mid >= last_id or _younger_than_grace(message, now):
378
+ msgs.append(message)
379
+ continue
380
+ if message.get("read_at") in (None, ""):
381
+ hidden_stale += 1
315
382
  keep = msgs[:DM_CEILING]
316
383
  return {
317
384
  "self": who,
@@ -319,7 +386,8 @@ def _dm_unread(self_name: str | None = None) -> dict:
319
386
  "n": len(keep),
320
387
  "unread_seen": len(msgs),
321
388
  "not_returned": len(msgs) - len(keep),
322
- "window_full": len(msgs) >= DM_GATEWAY_WINDOW,
389
+ "hidden_stale": hidden_stale,
390
+ "window_full": len(raw) >= DM_GATEWAY_WINDOW,
323
391
  }
324
392
  except Exception as exc: # loud, never an empty inbox
325
393
  return _dm_error(f"{type(exc).__name__}: {exc}")
@@ -473,10 +541,16 @@ try:
473
541
 
474
542
  @mcp.tool()
475
543
  def swarph_dm_unread() -> dict:
476
- """ALL unread DMs for this cell, FULL bodies, ONE call (card #735). Peek semantics:
477
- nothing is marked read. No paging, no cursor. Replying with swarph_dm_reply is the
478
- ack; anything unanswered keeps appearing here. `not_returned` > 0 means more than
479
- the ceiling were unread and the oldest were returned in full."""
544
+ """Unread DMs for this cell, FULL bodies, ONE call (card #735, card #1286).
545
+
546
+ Peek semantics: nothing is marked read and the monitor cursor is not moved.
547
+ The DM whose id equals the cursor was observed and not yet delivered, so
548
+ it is returned. A DM strictly behind the cursor is still returned when
549
+ its created_at is younger than 24 h. Older than that, it stays hidden
550
+ and hidden_stale counts it. No paging parameter.
551
+ Replying with swarph_dm_reply is the ack.
552
+ `not_returned` > 0 means more than the ceiling were unread and the oldest
553
+ were returned in full."""
480
554
  return _dm_unread()
481
555
 
482
556
  @mcp.tool()
@@ -41,6 +41,25 @@ MEM = pathlib.Path(
41
41
  next((a.split("=", 1)[1] for a in sys.argv if a.startswith("--mem-dir=")),
42
42
  str(pathlib.Path.home() / ".claude/projects/-home-ubuntu/memory")))
43
43
  INDEXES = ("MEMORY.md", "MEMORY_FULL.md")
44
+
45
+
46
+ def index_names(mem: pathlib.Path) -> list[str]:
47
+ """Files whose pointers count: every MEMORY*.md and index_*.md.
48
+
49
+ MEMORY.md and MEMORY_FULL.md stay required, so a missing one is still
50
+ MISSING INDEX. Pointers also live in index_*.md after the index-of-indexes
51
+ restructure. A file named only there is not an orphan (card #1278).
52
+ """
53
+ found: list[str] = []
54
+ if mem.is_dir():
55
+ found = [
56
+ p.name for p in sorted(mem.glob("*.md"))
57
+ if p.name.startswith("MEMORY") or p.name.startswith("index_")
58
+ ]
59
+ for name in INDEXES:
60
+ if name not in found:
61
+ found.append(name)
62
+ return found
44
63
  BUDGET = 24985 # bytes, MEMORY.md only — stated in the file's own header comment
45
64
  # THE THIRD STATE. Without WARN this check reported "clean" at 24,972 bytes —
46
65
  # 13 bytes of headroom — which is the exact condition ORGANIZE exists to
@@ -189,9 +208,10 @@ def main() -> int:
189
208
  print(f"memory-index-check: {MEM} is not a directory", file=sys.stderr)
190
209
  return 1
191
210
 
192
- files = {p.name for p in MEM.glob("*.md")} - set(INDEXES)
211
+ indexes = index_names(MEM)
212
+ files = {p.name for p in MEM.glob("*.md")} - set(indexes)
193
213
  linked: set[str] = set()
194
- for i in INDEXES:
214
+ for i in indexes:
195
215
  linked |= links_in(i)
196
216
 
197
217
  findings: list[str] = []
@@ -203,7 +223,7 @@ def main() -> int:
203
223
  if len(orphans) > 20:
204
224
  findings.append(f" … and {len(orphans) - 20} more")
205
225
 
206
- for idx in INDEXES:
226
+ for idx in indexes:
207
227
  p = MEM / idx
208
228
  if not p.exists():
209
229
  findings.append(f"MISSING INDEX: {idx}")
@@ -213,7 +233,11 @@ def main() -> int:
213
233
  if line.startswith("- ") and TRUNC.search(line):
214
234
  findings.append(f"TRUNCATED: {idx}:{n} ends mid-link — ...{line[-46:]}")
215
235
 
216
- dangling = sorted(linked - files)
236
+ # An index file is not a missing memory. MEMORY.md (the index of indexes)
237
+ # links at index_*.md, and those names were removed from `files` above, so
238
+ # linked - files reported every one of them DANGLING (card #1278 rework).
239
+ # Subtract the index names too: dangling is a pointer to no existing .md.
240
+ dangling = sorted(linked - files - set(indexes))
217
241
  if dangling:
218
242
  findings.append(f"DANGLING: {len(dangling)} pointer(s) to a file that does not exist")
219
243
  findings += [f" {d}" for d in dangling[:20]]
@@ -141,13 +141,64 @@ def apply(out: Path, verdicts, organized, cell: str, sender) -> bool:
141
141
  return True
142
142
 
143
143
 
144
+ def _post_to_card(card: str, content: str) -> None:
145
+ """Put the finding on a card. A DM to the sender is refused (#185 / #409).
146
+
147
+ Card #864: a finding goes on a card. Card #1278: the dreaming unit is
148
+ lab-ovh notifying lab-ovh, so the send never arrives. Default card is 656,
149
+ whose rows are closed; a holder who posts to a card thread closes their
150
+ oldest open row on it, so do not point this at a card the sender holds.
151
+ """
152
+ from swarph_cli.commands.board import (
153
+ _card_say_payload,
154
+ _http_get_json,
155
+ _post_json,
156
+ _project_owner_orchestrator,
157
+ _thread_recipient,
158
+ )
159
+ from swarph_cli.commands.mesh import _resolve_self_name, _resolve_token
160
+ from swarph_cli.gateway_default import env_gateway
161
+
162
+ gw = env_gateway()
163
+ self_name = _resolve_self_name(None)
164
+ token = _resolve_token(self_name, None)
165
+ status, card_row = _http_get_json(f"{gw}/board/cards/{card}", token)
166
+ if status != 200 or not isinstance(card_row, dict):
167
+ raise NotifySendError(f"cannot read card #{card}: {card_row}")
168
+ thread_uuid = card_row.get("thread_uuid")
169
+ if not thread_uuid:
170
+ raise NotifySendError(f"card #{card} has no bound thread")
171
+ owner = None
172
+ if not card_row.get("assignee"):
173
+ owner = _project_owner_orchestrator(gw, token, card_row.get("project_id"))
174
+ try:
175
+ to_node, _reason = _thread_recipient(card_row, None, owner_orchestrator=owner)
176
+ except RuntimeError as exc:
177
+ raise NotifySendError(str(exc)) from exc
178
+ status, payload = _post_json(
179
+ f"{gw}/messages",
180
+ _card_say_payload(self_name, to_node, "status", content, thread_uuid),
181
+ token,
182
+ )
183
+ if status < 200 or status >= 300:
184
+ raise NotifySendError(f"card #{card} post failed: {status} {payload}")
185
+
186
+
144
187
  def mesh_sender(cell: str, content: str) -> None:
145
- """Same path as ``swarph mesh send``: one status DM, no second transport."""
188
+ """Same path as ``swarph mesh send``, unless that send would be a self-wake.
189
+
190
+ SWARPH_SELF equal to the notify cell is the dreaming unit. Mesh send
191
+ refuses it and the run exits 4. Post the same body to the dreaming card.
192
+ """
146
193
  import argparse
147
194
 
148
- from swarph_cli.commands.mesh import _run_send
195
+ from swarph_cli.commands.mesh import _resolve_self_name, _run_send
149
196
  from swarph_cli.gateway_default import env_gateway
150
197
 
198
+ self_name = _resolve_self_name(None)
199
+ if cell == self_name:
200
+ _post_to_card("656", content)
201
+ return
151
202
  ns = argparse.Namespace(
152
203
  to=cell,
153
204
  kind="status",
@@ -34,7 +34,8 @@ class MemoryBackend:
34
34
  f"create '{title}': project_id is required — the gateway "
35
35
  f"refuses a card create without one (BoardCardCreate)")
36
36
  row = {"id": self.next_id, "title": title, "owner": owner,
37
- "project_id": self.project_id, "status": "open"}
37
+ "project_id": self.project_id, "status": "open",
38
+ "labels": ["triage-row"]}
38
39
  self.next_id += 1
39
40
  self.rows[title] = row
40
41
  self.posts[title] = []
@@ -119,7 +120,10 @@ class BoardBackend:
119
120
  "body": f"Daily triage row for {owner} (card #1073). "
120
121
  "Items are appended by the triage job; "
121
122
  "owners work them off the row.",
122
- "assignee": owner}
123
+ "assignee": owner,
124
+ # card #1269: the label is how offer_dispatch leaves the
125
+ # minted rows unoffered. Rows stay requested.
126
+ "labels": ["triage-row"]}
123
127
  try:
124
128
  status, body = self._http_post(
125
129
  f"{self.gateway}/board/cards", payload, self.token)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.72.32
3
+ Version: 0.72.34
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
@@ -249,7 +249,10 @@ tests/test_1256_commander_wake.py
249
249
  tests/test_1261_mcp_install.py
250
250
  tests/test_1262_wake_followups.py
251
251
  tests/test_1265_wake_note.py
252
+ tests/test_1269_triage_label.py
252
253
  tests/test_126_pending_from_complete.py
254
+ tests/test_1278_dreaming_reach.py
255
+ tests/test_1286_dm_unread_cursor.py
253
256
  tests/test_132_ensure_monitor_state_dir.py
254
257
  tests/test_138_memory_navigate.py
255
258
  tests/test_139_tool_probe.py
@@ -0,0 +1,25 @@
1
+ """Card #1269. A new triage container is created with the triage-row
2
+ label. Fails at dba1ebc: the create payload has no labels.
3
+ """
4
+ from swarph_cli.triage.backend import BoardBackend
5
+
6
+
7
+ class _Http:
8
+ def __init__(self):
9
+ self.posts = []
10
+
11
+ def post(self, url, payload, token):
12
+ self.posts.append((url, payload))
13
+ return 200, {"id": 77}
14
+
15
+ def get(self, url, token):
16
+ raise AssertionError(url)
17
+
18
+
19
+ def test_create_labels_the_container_triage_row():
20
+ http = _Http()
21
+ backend = BoardBackend(
22
+ "http://gw:9", "tok", "lab-ovh", http.get, http.post, project_id=9)
23
+ backend.create_row("droplet triage", "droplet")
24
+ _url, payload = http.posts[-1]
25
+ assert payload.get("labels") == ["triage-row"]
@@ -0,0 +1,70 @@
1
+ """Card #1278: a self-notify must be delivered, and index_*.md pointers count."""
2
+ from __future__ import annotations
3
+
4
+ import subprocess
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ from swarph_cli.dreaming import run as dreaming_run
9
+
10
+
11
+ def _run_check(mem: Path) -> tuple[int, str]:
12
+ script = Path(__file__).resolve().parents[1] / "src" / "swarph_cli" / "dreaming" / "index_check.py"
13
+ r = subprocess.run(
14
+ [sys.executable, str(script), "--mem-dir=%s" % mem, "--quiet"],
15
+ capture_output=True, text=True, encoding="utf-8", errors="replace")
16
+ return r.returncode, (r.stdout or "") + (r.stderr or "")
17
+
18
+
19
+ def test_1278_pointer_only_in_index_md_is_not_an_orphan(tmp_path):
20
+ mem = tmp_path / "mem"
21
+ mem.mkdir()
22
+ (mem / "MEMORY.md").write_text(
23
+ "- [a](a.md)\n- [topics](index_topics.md)\n", encoding="utf-8")
24
+ (mem / "MEMORY_FULL.md").write_text("- [a](a.md)\n", encoding="utf-8")
25
+ (mem / "index_topics.md").write_text("- [b](b.md)\n", encoding="utf-8")
26
+ (mem / "a.md").write_text("# a\n", encoding="utf-8")
27
+ (mem / "b.md").write_text("# b\n", encoding="utf-8")
28
+ rc, out = _run_check(mem)
29
+ assert "ORPHANS" not in out, out
30
+ assert "DANGLING" not in out, out
31
+ assert rc == 0, out
32
+
33
+
34
+ def test_1278_self_notify_is_delivered_not_exit_4(tmp_path, monkeypatch):
35
+ monkeypatch.setenv("SWARPH_SELF", "lab-ovh")
36
+ posted = []
37
+
38
+ def clone(corpus, out):
39
+ Path(out).mkdir(parents=True, exist_ok=True)
40
+ return {}
41
+
42
+ monkeypatch.setattr(dreaming_run, "clone_corpus", clone)
43
+ monkeypatch.setattr(
44
+ dreaming_run, "verify",
45
+ lambda out, manifest: [{"verdict": "agree", "file": "a.md", "line": 1, "kind": "x"}],
46
+ )
47
+ monkeypatch.setattr(
48
+ dreaming_run, "organize",
49
+ lambda out: {"findings": [], "index_bytes_before": 0,
50
+ "index_bytes_after": 0, "trimmed": []},
51
+ )
52
+ monkeypatch.setattr(dreaming_run, "render", lambda *a, **k: "# dreaming report\n")
53
+ monkeypatch.setattr(
54
+ "swarph_cli.dreaming.notify._post_to_card",
55
+ lambda card, body: posted.append((str(card), body)),
56
+ raising=False,
57
+ )
58
+ monkeypatch.setattr("swarph_cli.commands.mesh._run_send", lambda ns: 1)
59
+
60
+ corpus = tmp_path / "corpus"
61
+ corpus.mkdir()
62
+ rc = dreaming_run.main([
63
+ "--corpus", str(corpus),
64
+ "--out", str(tmp_path / "runs" / "run-1"),
65
+ "--no-enrich",
66
+ "--notify", "lab-ovh",
67
+ ])
68
+ assert rc != 4
69
+ assert posted and posted[0][0] == "656"
70
+ assert "dreaming" in posted[0][1] or posted[0][1]
@@ -0,0 +1,192 @@
1
+ """Card #1286: swarph_dm_unread agrees with the monitor cursor.
2
+
3
+ A DM with read_at NULL that the cursor has already passed is not unread.
4
+ The call is a peek: cursor.json is not rewritten and nothing is marked read.
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import datetime
9
+ import json
10
+ import time
11
+
12
+ from swarph_cli.commands import mesh
13
+ from swarph_cli.commands import mcp_server
14
+ from swarph_cli.dm_frame import frame_text
15
+
16
+
17
+ def test_1286_stale_unread_behind_the_cursor_is_not_new(monkeypatch, tmp_path):
18
+ monkeypatch.setattr(
19
+ mcp_server, "_dm_session", lambda self_name=None: ("lab-ovh", "http://gw", "tok"),
20
+ )
21
+ state = tmp_path / "mesh-sidecar"
22
+ state.mkdir()
23
+ # Path.home() ignores HOME on Windows, so point the sidecar helper itself.
24
+ monkeypatch.setattr(mcp_server._mesh, "_default_sidecar_state_dir", lambda who: state)
25
+ cursor = state / "cursor.json"
26
+ cursor.write_text(json.dumps({"last_msg_id": 200}), encoding="utf-8")
27
+ before = cursor.read_bytes()
28
+ rows = [
29
+ {"id": 100, "from_node": "cursor-lin", "to_node": "lab-ovh", "kind": "status",
30
+ "content": "STALE1286", "read_at": None},
31
+ {"id": 200, "from_node": "cursor-lin", "to_node": "lab-ovh", "kind": "status",
32
+ "content": "BOUNDARY1286", "read_at": None},
33
+ {"id": 300, "from_node": "cursor-lin", "to_node": "lab-ovh", "kind": "status",
34
+ "content": "NEW1286 full body", "read_at": None},
35
+ {"id": 400, "from_node": "lab-ovh", "to_node": "cursor-lin", "kind": "status",
36
+ "content": "SELF1286", "read_at": None},
37
+ ]
38
+
39
+ def fake_get(url, token, **kw):
40
+ assert "unread_only=true" in url
41
+ return 200, {"messages": list(reversed(rows)), "n": len(rows)}
42
+
43
+ monkeypatch.setattr(mcp_server._mesh, "_http_get_json", fake_get)
44
+ out = mcp_server._dm_unread()
45
+ # 200 equals the cursor: observed, not yet delivered, so it is returned.
46
+ # 100 is strictly behind and stays hidden.
47
+ assert [m["id"] for m in out["messages"]] == [200, 300]
48
+ assert out["messages"][1]["content"] == frame_text(rows[2])
49
+ assert "NEW1286 full body" in out["messages"][1]["content"]
50
+ assert "BOUNDARY1286" in out["messages"][0]["content"]
51
+ assert "STALE1286" not in json.dumps(out)
52
+ assert "SELF1286" not in json.dumps(out)
53
+ assert out["n"] == 2 and out["not_returned"] == 0
54
+ # 100 is behind the cursor and has no created_at, so it is not within 24 h.
55
+ assert out.get("hidden_stale") == 1
56
+ assert cursor.read_bytes() == before
57
+
58
+
59
+ def test_1286_monitor_iteration_then_unread_returns_the_woken_dm(monkeypatch, tmp_path):
60
+ """One real monitor iteration observes a new DM, then unread still returns it.
61
+
62
+ The cursor advances before delivery, so the woken id equals last_msg_id.
63
+ On 74347164 unread uses a strict `>` and the DM is gone.
64
+ """
65
+ state_dir = tmp_path / "mesh-sidecar"
66
+ state_dir.mkdir()
67
+ (state_dir / "cursor.json").write_text(
68
+ json.dumps({"last_msg_id": 100}), encoding="utf-8")
69
+ monkeypatch.setattr(mcp_server._mesh, "_default_sidecar_state_dir", lambda who: state_dir)
70
+ monkeypatch.setattr(
71
+ mcp_server, "_dm_session", lambda self_name=None: ("lab-ovh", "http://gw", "tok"),
72
+ )
73
+ woken = {
74
+ "id": 501, "from_node": "cursor-lin", "to_node": "lab-ovh", "kind": "question",
75
+ "content": "WOKEN1286 full body", "read_at": None,
76
+ }
77
+ stale = {
78
+ "id": 50, "from_node": "cursor-lin", "to_node": "lab-ovh", "kind": "status",
79
+ "content": "BEHIND1286", "read_at": None,
80
+ }
81
+
82
+ def fake_get(url, token, **kw):
83
+ return 200, {"messages": [stale, woken]}
84
+
85
+ monkeypatch.setattr(mesh, "_http_get_json", fake_get)
86
+ state = mesh.MonitorState(
87
+ self_name="lab-ovh",
88
+ state_dir=state_dir,
89
+ gateway="http://gw",
90
+ token="tok",
91
+ sinks=[mesh.parse_sink("none")],
92
+ )
93
+ mesh._monitor_iteration(state, poll_channels=False)
94
+ out = mcp_server._dm_unread()
95
+ ids = [m["id"] for m in out["messages"]]
96
+ assert ids == [501]
97
+ assert "WOKEN1286 full body" in out["messages"][0]["content"]
98
+ assert "BEHIND1286" not in json.dumps(out)
99
+ assert out.get("hidden_stale") == 1
100
+
101
+
102
+ def _iso(offset_s: float) -> str:
103
+ stamp = datetime.datetime.fromtimestamp(time.time() + offset_s, datetime.timezone.utc)
104
+ return stamp.isoformat()
105
+
106
+
107
+ def _row(mid: int, content: str, created_at: str) -> dict:
108
+ return {
109
+ "id": mid, "from_node": "cursor-lin", "to_node": "lab-ovh", "kind": "question",
110
+ "content": content, "read_at": None, "created_at": created_at,
111
+ }
112
+
113
+
114
+ def _monitor(monkeypatch, tmp_path, rows, *, polls: int = 1, windows=None):
115
+ state_dir = tmp_path / "mesh-sidecar"
116
+ state_dir.mkdir()
117
+ (state_dir / "cursor.json").write_text(
118
+ json.dumps({"last_msg_id": 100}), encoding="utf-8")
119
+ monkeypatch.setattr(mcp_server._mesh, "_default_sidecar_state_dir", lambda who: state_dir)
120
+ monkeypatch.setattr(
121
+ mcp_server, "_dm_session", lambda self_name=None: ("lab-ovh", "http://gw", "tok"),
122
+ )
123
+ seen = {"n": 0}
124
+
125
+ def fake_get(url, token, **kw):
126
+ if "unread_only" in url:
127
+ return 200, {"messages": list(rows)}
128
+ if windows is None:
129
+ return 200, {"messages": list(rows)}
130
+ index = min(seen["n"], len(windows) - 1)
131
+ seen["n"] += 1
132
+ wanted = windows[index]
133
+ return 200, {"messages": [row for row in rows if row["id"] in wanted]}
134
+
135
+ monkeypatch.setattr(mesh, "_http_get_json", fake_get)
136
+ state = mesh.MonitorState(
137
+ self_name="lab-ovh",
138
+ state_dir=state_dir,
139
+ gateway="http://gw",
140
+ token="tok",
141
+ sinks=[mesh.parse_sink("none")],
142
+ )
143
+ for _ in range(polls):
144
+ mesh._monitor_iteration(state, poll_channels=False)
145
+ return mcp_server._dm_unread(), state
146
+
147
+
148
+ def test_1286_one_poll_observing_three_returns_all_three(monkeypatch, tmp_path):
149
+ """One poll records the cursor at the newest id. The two earlier DMs are young."""
150
+ young = _iso(0)
151
+ rows = [
152
+ _row(501, "ONE1286", young),
153
+ _row(502, "TWO1286", young),
154
+ _row(503, "THREE1286", young),
155
+ ]
156
+ out, state = _monitor(monkeypatch, tmp_path, rows)
157
+ assert state.observed["last_msg_id"] == 503
158
+ assert [m["id"] for m in out["messages"]] == [501, 502, 503]
159
+ assert out.get("hidden_stale") == 0
160
+
161
+
162
+ def test_1286_three_polls_then_unread_returns_all_three(monkeypatch, tmp_path):
163
+ """A busy host calls the tool after three polls, one new DM each."""
164
+ young = _iso(-60)
165
+ rows = [
166
+ _row(501, "POLL1286a", young),
167
+ _row(502, "POLL1286b", young),
168
+ _row(503, "POLL1286c", young),
169
+ ]
170
+ out, state = _monitor(
171
+ monkeypatch, tmp_path, rows, polls=3, windows=([501], [501, 502], [501, 502, 503]),
172
+ )
173
+ assert state.observed["last_msg_id"] == 503
174
+ assert [m["id"] for m in out["messages"]] == [501, 502, 503]
175
+ assert "POLL1286a" in out["messages"][0]["content"]
176
+ assert out.get("hidden_stale") == 0
177
+
178
+
179
+ def test_1286_old_dm_behind_the_cursor_is_hidden_and_counted(monkeypatch, tmp_path):
180
+ young = _iso(0)
181
+ old = _iso(-(25 * 60 * 60))
182
+ rows = [
183
+ _row(50, "OLD1286", old),
184
+ _row(80, "YOUNGBEHIND1286", young),
185
+ _row(501, "FRESH1286", young),
186
+ ]
187
+ out, state = _monitor(monkeypatch, tmp_path, rows)
188
+ assert state.observed["last_msg_id"] == 501
189
+ assert "OLD1286" not in json.dumps(out)
190
+ assert out.get("hidden_stale") == 1
191
+ assert [m["id"] for m in out["messages"]] == [80, 501]
192
+ assert "YOUNGBEHIND1286" in out["messages"][0]["content"]
@@ -44,7 +44,10 @@ FIXTURE = [
44
44
 
45
45
 
46
46
  @pytest.fixture(autouse=True)
47
- def _session(monkeypatch: pytest.MonkeyPatch):
47
+ def _session(monkeypatch: pytest.MonkeyPatch, tmp_path):
48
+ # Card #1286 reads the monitor cursor. These tests are about the unread
49
+ # payload, so they observe from a private home whose cursor is absent.
50
+ monkeypatch.setenv("HOME", str(tmp_path))
48
51
  monkeypatch.setattr(mcp_server, "_dm_session", lambda self_name=None: ("lab-ovh", "http://gw", "tok"))
49
52
  if MUTATE == "truncate":
50
53
  real = mcp_server._lean
File without changes
File without changes
File without changes