swarph-cli 0.69.0__tar.gz → 0.69.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (445) hide show
  1. {swarph_cli-0.69.0/src/swarph_cli.egg-info → swarph_cli-0.69.2}/PKG-INFO +1 -1
  2. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/pyproject.toml +1 -1
  3. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/__init__.py +1 -1
  4. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/mesh.py +197 -11
  5. {swarph_cli-0.69.0 → swarph_cli-0.69.2/src/swarph_cli.egg-info}/PKG-INFO +1 -1
  6. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_sidecar.py +1 -0
  7. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_tmux_wake_runstate_defer.py +2 -0
  8. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_tmux_wake_submit_verify.py +277 -4
  9. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/LICENSE +0 -0
  10. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/README.md +0 -0
  11. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/setup.cfg +0 -0
  12. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/__main__.py +0 -0
  13. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/__init__.py +0 -0
  14. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/backends.py +0 -0
  15. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/pack.py +0 -0
  16. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/prices.py +0 -0
  17. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/providers.py +0 -0
  18. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/quality.py +0 -0
  19. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/refresh_prices.py +0 -0
  20. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/rules.py +0 -0
  21. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/runner.py +0 -0
  22. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/bench/validate.py +0 -0
  23. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/caller.py +0 -0
  24. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/__init__.py +0 -0
  25. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/harden.py +0 -0
  26. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/lineage.py +0 -0
  27. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/liveness.py +0 -0
  28. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/manifest.py +0 -0
  29. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/paths.py +0 -0
  30. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/capture/verify.py +0 -0
  31. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/cell.py +0 -0
  32. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/chain_token.py +0 -0
  33. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/channel.py +0 -0
  34. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/__init__.py +0 -0
  35. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/_content.py +0 -0
  36. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/_display.py +0 -0
  37. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/_gateway_client.py +0 -0
  38. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/add.py +0 -0
  39. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/bench.py +0 -0
  40. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/board.py +0 -0
  41. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/board_merge_check.py +0 -0
  42. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/brain.py +0 -0
  43. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/brain_ask.py +0 -0
  44. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/cell.py +0 -0
  45. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/cell_probe.py +0 -0
  46. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/cell_selfcheck.py +0 -0
  47. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/channel.py +0 -0
  48. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/chat.py +0 -0
  49. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/codegraph.py +0 -0
  50. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/codegraph_hook.py +0 -0
  51. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/codex_hook_output.py +0 -0
  52. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/codex_waker.py +0 -0
  53. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/compress.py +0 -0
  54. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/daemon.py +0 -0
  55. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/dreaming.py +0 -0
  56. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/event.py +0 -0
  57. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/gateway.py +0 -0
  58. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/gh_route.py +0 -0
  59. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/group.py +0 -0
  60. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/guide.py +0 -0
  61. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/highlight.py +0 -0
  62. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/hook_interpreter.py +0 -0
  63. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/hook_output.py +0 -0
  64. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/hooks.py +0 -0
  65. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/import_session.py +0 -0
  66. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/init.py +0 -0
  67. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_codex_hooks.py +0 -0
  68. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_hook.py +0 -0
  69. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_multiplexer.py +0 -0
  70. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_opencode_plugin.py +0 -0
  71. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_postcompact_hook.py +0 -0
  72. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/install_wake_hook.py +0 -0
  73. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/lane.py +0 -0
  74. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/mcp_server.py +0 -0
  75. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/memory.py +0 -0
  76. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/memory_emit_hook.py +0 -0
  77. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/memory_sync.py +0 -0
  78. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/monitor.py +0 -0
  79. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/okf_links.py +0 -0
  80. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/onboard.py +0 -0
  81. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/peer_reply_drain.py +0 -0
  82. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/postcompact_hook_output.py +0 -0
  83. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/protocol_handler.py +0 -0
  84. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/ratify.py +0 -0
  85. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/schedule.py +0 -0
  86. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/security.py +0 -0
  87. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/service.py +0 -0
  88. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/spawn.py +0 -0
  89. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/timeline.py +0 -0
  90. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/version.py +0 -0
  91. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/wake_hook_output.py +0 -0
  92. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/commands/watchdog.py +0 -0
  93. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/compress/__init__.py +0 -0
  94. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/compress/levers.py +0 -0
  95. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/compress/marker.py +0 -0
  96. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/compress/verify.py +0 -0
  97. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/console_safe.py +0 -0
  98. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/delivery_queue.py +0 -0
  99. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dm_frame.py +0 -0
  100. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/__init__.py +0 -0
  101. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/candidates.py +0 -0
  102. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/clone.py +0 -0
  103. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/enrich.py +0 -0
  104. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/index_check.py +0 -0
  105. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/notify.py +0 -0
  106. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/organize.py +0 -0
  107. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/probes.py +0 -0
  108. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/report.py +0 -0
  109. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/run.py +0 -0
  110. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/slm.py +0 -0
  111. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/transcripts.py +0 -0
  112. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/dreaming/verify.py +0 -0
  113. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/__init__.py +0 -0
  114. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/feature_registry.py +0 -0
  115. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/lanes_control.py +0 -0
  116. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/schema.sql +0 -0
  117. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/server.py +0 -0
  118. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway/services_control.py +0 -0
  119. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gateway_default.py +0 -0
  120. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/gh_identity.py +0 -0
  121. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/guide/GUIDE.md +0 -0
  122. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/identity.py +0 -0
  123. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/main.py +0 -0
  124. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/multiplexer.py +0 -0
  125. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/orphan_daemons.py +0 -0
  126. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/pane_probe.py +0 -0
  127. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/parsers/__init__.py +0 -0
  128. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/parsers/claude.py +0 -0
  129. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/opencode/plugin.js +0 -0
  130. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/_shim.cmd +0 -0
  131. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/postcompact-recall-grok.sh +0 -0
  132. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/postcompact-recall.sh +0 -0
  133. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-antigravity.sh +0 -0
  134. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-claude.sh +0 -0
  135. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit-grok.sh +0 -0
  136. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-emit.sh +0 -0
  137. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/posttooluse-recall.sh +0 -0
  138. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/precompact-flag.sh +0 -0
  139. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/preinvocation-recall-antigravity.sh +0 -0
  140. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/payloads/postcompact/sessionstart-recall.sh +0 -0
  141. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_executor.py +0 -0
  142. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_executor_windows.py +0 -0
  143. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_reconciliation.py +0 -0
  144. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_reply_outbox.py +0 -0
  145. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_service_host.py +0 -0
  146. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_service_payload.py +0 -0
  147. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_service_reply.py +0 -0
  148. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/peer_staging.py +0 -0
  149. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/query_filters.py +0 -0
  150. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/__init__.py +0 -0
  151. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/dm_notify_filter.py +0 -0
  152. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/ensure_monitor.sh +0 -0
  153. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/install_codex_waker_windows.ps1 +0 -0
  154. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/install_monitor_task_windows.ps1 +0 -0
  155. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/scripts/wake_watchdog.py +0 -0
  156. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/service/__init__.py +0 -0
  157. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/service/app.py +0 -0
  158. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/service/providers.py +0 -0
  159. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/session_bridge.py +0 -0
  160. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/skills/i-have-adhd/LICENSE +0 -0
  161. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/skills/i-have-adhd/SKILL.md +0 -0
  162. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/source_text.py +0 -0
  163. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/stall_alert.py +0 -0
  164. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/codex-waker.default +0 -0
  165. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/peer-reply-drain.default +0 -0
  166. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-codex-outbox@.service +0 -0
  167. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-codex-outbox@.timer +0 -0
  168. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-codex-waker@.service +0 -0
  169. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-codex-waker@.timer +0 -0
  170. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-monitor-reexec.path +0 -0
  171. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-monitor-reexec.service +0 -0
  172. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-monitor@.service +0 -0
  173. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-peer-reply-drain@.service +0 -0
  174. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-peer-reply-drain@.timer +0 -0
  175. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-watchdog.default +0 -0
  176. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-watchdog.service +0 -0
  177. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/systemd/swarph-watchdog.timer +0 -0
  178. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/timeline_paths.py +0 -0
  179. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli/tokens.py +0 -0
  180. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/SOURCES.txt +0 -0
  181. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/dependency_links.txt +0 -0
  182. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/entry_points.txt +0 -0
  183. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/requires.txt +0 -0
  184. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/src/swarph_cli.egg-info/top_level.txt +0 -0
  185. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_124_register_merge.py +0 -0
  186. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_126_pending_from_complete.py +0 -0
  187. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_132_ensure_monitor_state_dir.py +0 -0
  188. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_138_memory_navigate.py +0 -0
  189. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_139_tool_probe.py +0 -0
  190. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_183_card_anchor.py +0 -0
  191. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_184_notify_honesty.py +0 -0
  192. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_201_win32_bash_refusal.py +0 -0
  193. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_243_onboard_per_peer_token.py +0 -0
  194. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_307_task4_ask_reply.py +0 -0
  195. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_312_codegraph_fuzzy_verb.py +0 -0
  196. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_319_refusal_exit_codes.py +0 -0
  197. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_323_mistral_backend.py +0 -0
  198. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_332_report_organize_findings.py +0 -0
  199. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_356_query_filters.py +0 -0
  200. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_397_gh_identity_router.py +0 -0
  201. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_397_router_preflight.py +0 -0
  202. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_402_one_identity_resolver.py +0 -0
  203. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_467b_onboard_operator_credential.py +0 -0
  204. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_472_register_caller_binding.py +0 -0
  205. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_523_guide.py +0 -0
  206. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_524_conftest_hermeticity.py +0 -0
  207. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_532_accept_flag.py +0 -0
  208. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_535_version_capability.py +0 -0
  209. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_546_default_gateway.py +0 -0
  210. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_547_verbs_from_registry.py +0 -0
  211. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_548_gateway_remainder.py +0 -0
  212. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_552_codegraph_gateway.py +0 -0
  213. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_555_relay_row_shape.py +0 -0
  214. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_556_relay_freshness.py +0 -0
  215. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_559_edges_truncated.py +0 -0
  216. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_564_bootstrap_first_peer.py +0 -0
  217. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_578_no_machine_specific_host_defaults.py +0 -0
  218. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_579_register_bootstrap_message.py +0 -0
  219. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_591_cli_step_graph.py +0 -0
  220. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_644_monitor_supervision.py +0 -0
  221. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_648_install_unit.py +0 -0
  222. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_654_reexec.py +0 -0
  223. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_656_owned_slm.py +0 -0
  224. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_684_dreaming_verb.py +0 -0
  225. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_689_pkg_version_order.py +0 -0
  226. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_716_timeline_one_path.py +0 -0
  227. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_724_ps1_ascii_or_bom.py +0 -0
  228. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_725_stdio_reconfigure.py +0 -0
  229. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_729_dm_once.py +0 -0
  230. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_729_wake_watchdog.py +0 -0
  231. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_734_dreaming_enrich_degrade.py +0 -0
  232. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_735_dm_mcp_tools.py +0 -0
  233. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_740_edit_project.py +0 -0
  234. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_762_cards_edit_priority.py +0 -0
  235. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_764_enrich_collapse.py +0 -0
  236. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_801_confirm_flow_cli.py +0 -0
  237. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_802_stage_history.py +0 -0
  238. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_825_codegraph_hook_noise.py +0 -0
  239. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_825_install_posttooluse_only.py +0 -0
  240. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_825_remaining_cap_shred_bind.py +0 -0
  241. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_829_counterfactual_column.py +0 -0
  242. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_830_hook_binding_version.py +0 -0
  243. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_84_cli_labels.py +0 -0
  244. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_866_doctrine_guide.py +0 -0
  245. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_872_identity_refuses_not_warns.py +0 -0
  246. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_874_source_text.py +0 -0
  247. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_888_opencode_data_dir_can_fail.py +0 -0
  248. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_937_dreaming_notify.py +0 -0
  249. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_938_organize_line_limit.py +0 -0
  250. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_941_bench_backends.py +0 -0
  251. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_941_harness_342.py +0 -0
  252. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_945_same_line_pairing.py +0 -0
  253. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_955_prompt_audit.py +0 -0
  254. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_957_auth_http.py +0 -0
  255. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_959_venv_interpreter.py +0 -0
  256. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_960_channel.py +0 -0
  257. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_963_plugin_manifest.py +0 -0
  258. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_964_channel_cell.py +0 -0
  259. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_965_mesh_wait.py +0 -0
  260. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_975_channel_hop.py +0 -0
  261. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_add_security_gate.py +0 -0
  262. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_add.py +0 -0
  263. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_hash.py +0 -0
  264. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_lib.py +0 -0
  265. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_mcp.py +0 -0
  266. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_skill.py +0 -0
  267. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_tool.py +0 -0
  268. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_artifact_uri.py +0 -0
  269. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_backends.py +0 -0
  270. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_command.py +0 -0
  271. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_pack.py +0 -0
  272. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_prices.py +0 -0
  273. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_quality.py +0 -0
  274. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_runner.py +0 -0
  275. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_bench_validate.py +0 -0
  276. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_board_card_thread.py +0 -0
  277. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_board_command.py +0 -0
  278. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_board_merge_check.py +0 -0
  279. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_board_obligation_close.py +0 -0
  280. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_brain_ask_command.py +0 -0
  281. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_brain_ask_env_diagnosis.py +0 -0
  282. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_brain_ask_identity_explicitness.py +0 -0
  283. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_brain_command.py +0 -0
  284. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_caller_meta_guard.py +0 -0
  285. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_lineage.py +0 -0
  286. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_liveness.py +0 -0
  287. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_manifest.py +0 -0
  288. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_paths.py +0 -0
  289. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_capture_security.py +0 -0
  290. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_command_dispatch.py +0 -0
  291. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_harden.py +0 -0
  292. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_loader.py +0 -0
  293. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_selfcheck.py +0 -0
  294. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cell_verify.py +0 -0
  295. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_chain_token.py +0 -0
  296. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_channel_command.py +0 -0
  297. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_channel_poll_never_breaks_dms.py +0 -0
  298. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_channel_post.py +0 -0
  299. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_channel_wake_policy_filter.py +0 -0
  300. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_chat_command.py +0 -0
  301. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_claude_parser.py +0 -0
  302. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_claude_tmux_template.py +0 -0
  303. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codegraph_caller_scope.py +0 -0
  304. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codegraph_command.py +0 -0
  305. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codegraph_hook.py +0 -0
  306. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codegraph_mcp.py +0 -0
  307. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_codex_waker.py +0 -0
  308. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_caller_convention.py +0 -0
  309. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_command.py +0 -0
  310. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_levers.py +0 -0
  311. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_marker.py +0 -0
  312. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_compress_verify.py +0 -0
  313. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_console_encoding_e2e.py +0 -0
  314. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_content_composition.py +0 -0
  315. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_cursor_membrane.py +0 -0
  316. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_daemon_command.py +0 -0
  317. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_daemon_console_encoding.py +0 -0
  318. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_daemon_delivery.py +0 -0
  319. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_delivery_queue.py +0 -0
  320. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_display_sanitize.py +0 -0
  321. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_ensure_monitor_no_peer_default.py +0 -0
  322. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_event_emit.py +0 -0
  323. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_feature_to_uri.py +0 -0
  324. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gateway_brain.py +0 -0
  325. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gateway_client.py +0 -0
  326. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gateway_command.py +0 -0
  327. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gateway_message_idempotency.py +0 -0
  328. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_gh_route_doctor_mesh_name.py +0 -0
  329. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_group_command.py +0 -0
  330. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_guide_commands_parse.py +0 -0
  331. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_guide_selfcheck.py +0 -0
  332. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_heartbeat_check.py +0 -0
  333. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_highlight_command.py +0 -0
  334. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hook_output.py +0 -0
  335. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_activity_marker.py +0 -0
  336. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_add.py +0 -0
  337. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_bundle.py +0 -0
  338. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_init.py +0 -0
  339. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_legacy_generation_removal.py +0 -0
  340. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_lifecycle.py +0 -0
  341. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_merge.py +0 -0
  342. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_verify.py +0 -0
  343. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_win32_bash_wrapper.py +0 -0
  344. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_hooks_win32_command_path.py +0 -0
  345. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_import_command.py +0 -0
  346. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_inbox_identity_provenance.py +0 -0
  347. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_init_command.py +0 -0
  348. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_init_provider_menu.py +0 -0
  349. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_codex_hooks.py +0 -0
  350. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_hook.py +0 -0
  351. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_multiplexer_command.py +0 -0
  352. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_postcompact_hook.py +0 -0
  353. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_install_wake_hook.py +0 -0
  354. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_lane_command.py +0 -0
  355. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_main.py +0 -0
  356. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_main_unknown_verb_guard.py +0 -0
  357. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mcp_missing_hint.py +0 -0
  358. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mcp_server.py +0 -0
  359. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_command.py +0 -0
  360. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_emit_snippet.py +0 -0
  361. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_gateway.py +0 -0
  362. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_sync.py +0 -0
  363. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_memory_sync_membrane.py +0 -0
  364. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_command.py +0 -0
  365. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_inbox_mark_read.py +0 -0
  366. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_inbox_sanitize.py +0 -0
  367. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_originate_thread_623.py +0 -0
  368. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_send_unknown_peer.py +0 -0
  369. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_sidecar_cursor_decoupled.py +0 -0
  370. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_mesh_sidecar_wake_survives_read.py +0 -0
  371. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_meta_edge_identity.py +0 -0
  372. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_channel_poll.py +0 -0
  373. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_cli.py +0 -0
  374. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_daemon_collision.py +0 -0
  375. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_self_name_verification.py +0 -0
  376. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_monitor_state_split.py +0 -0
  377. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_multiplexer.py +0 -0
  378. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_muse_membrane.py +0 -0
  379. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_muse_own_cli.py +0 -0
  380. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_obligation_close_is_surfaced.py +0 -0
  381. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_okf_links.py +0 -0
  382. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_onboard_command.py +0 -0
  383. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_opencode_membrane.py +0 -0
  384. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_opencode_plugin.py +0 -0
  385. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_orphan_daemons.py +0 -0
  386. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_packaged_artifacts_exist.py +0 -0
  387. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_packaged_skills.py +0 -0
  388. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_executor.py +0 -0
  389. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_executor_windows.py +0 -0
  390. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_reply_drain_command.py +0 -0
  391. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_reply_drain_supervisor.py +0 -0
  392. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_reply_outbox.py +0 -0
  393. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_reply_transport.py +0 -0
  394. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_service_e2e.py +0 -0
  395. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_service_host.py +0 -0
  396. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_service_payload.py +0 -0
  397. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_peer_service_reply.py +0 -0
  398. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_postcompact_recall.py +0 -0
  399. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_process_liveness.py +0 -0
  400. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_protocol_handler.py +0 -0
  401. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_providers_isolation.py +0 -0
  402. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_ratify_command.py +0 -0
  403. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_readme_claims_no_gateway_default.py +0 -0
  404. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_repl_caller_convention.py +0 -0
  405. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_schedule_command.py +0 -0
  406. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_schedule_create_contract.py +0 -0
  407. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_security.py +0 -0
  408. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_service_app.py +0 -0
  409. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_service_command.py +0 -0
  410. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_service_providers.py +0 -0
  411. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_session_bridge_inject.py +0 -0
  412. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_session_bridge_probe.py +0 -0
  413. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_session_bridge_resolve.py +0 -0
  414. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_session_path_role_gate.py +0 -0
  415. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_shipped_units_match_the_cli.py +0 -0
  416. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_smoke_chat.py +0 -0
  417. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_smoke_one_shot.py +0 -0
  418. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_smoke_phase_5_5.py +0 -0
  419. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_base_hoist.py +0 -0
  420. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_command.py +0 -0
  421. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_identity_stamp.py +0 -0
  422. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_live_pin.py +0 -0
  423. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_mitosis_lineage.py +0 -0
  424. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_tmux_session.py +0 -0
  425. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_spawn_windows_relaunch.py +0 -0
  426. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_stall_alert.py +0 -0
  427. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_subprocess_decode_encoding.py +0 -0
  428. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_timeline_command.py +0 -0
  429. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_tmux_wake_session_reanchor.py +0 -0
  430. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_tmux_wake_stale.py +0 -0
  431. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_token_file_one_parser.py +0 -0
  432. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_token_precedence_explicit_first.py +0 -0
  433. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_unified_token_resolver.py +0 -0
  434. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_version_command.py +0 -0
  435. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_version_consistency.py +0 -0
  436. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_vibe_membrane.py +0 -0
  437. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog.py +0 -0
  438. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_dm_wake.py +0 -0
  439. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_dm_wake_cooldown.py +0 -0
  440. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_dm_wake_wiring.py +0 -0
  441. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_emit_health.py +0 -0
  442. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_install_flags.py +0 -0
  443. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_model_rung.py +0 -0
  444. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_watchdog_stale_peers.py +0 -0
  445. {swarph_cli-0.69.0 → swarph_cli-0.69.2}/tests/test_windows_waker_installer.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.69.0
3
+ Version: 0.69.2
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "swarph-cli"
7
- version = "0.69.0"
7
+ version = "0.69.2"
8
8
  description = "The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`."
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -16,6 +16,6 @@ The architecture splits CLI from substrate so:
16
16
 
17
17
  from __future__ import annotations
18
18
 
19
- __version__ = "0.69.0"
19
+ __version__ = "0.69.2"
20
20
 
21
21
  __all__ = ["__version__"]
@@ -1474,6 +1474,11 @@ class TmuxSink(Sink):
1474
1474
  # observation and let the fresh path take over — it defers while
1475
1475
  # running (#619) and injects at the first idle poll.
1476
1476
  if led.get("wake_outstanding") and _agent_running(self.target):
1477
+ # Opencode keeps the finished ▣ header on screen, so a deferral
1478
+ # during the turn must not clear the flag: the next idle poll
1479
+ # still owes a wake. Cursor's #620 clear stays.
1480
+ if _opencode_in_progress(self.target):
1481
+ return None
1477
1482
  led["wake_outstanding"] = False
1478
1483
  if led.get("wake_outstanding"):
1479
1484
  # Lazy: watchdog imports mesh module-level, so the reverse must
@@ -1496,6 +1501,17 @@ class TmuxSink(Sink):
1496
1501
  _tmux_enter(self.target) # one verified nudge, no new text
1497
1502
  return True
1498
1503
  if composer == "clear":
1504
+ # A finished opencode turn leaves a ▣ header with a
1505
+ # trailing duration and a clear box. The standing-wake
1506
+ # claim would swallow every later DM. Inject again.
1507
+ if _opencode_turn_finished_target(self.target):
1508
+ ok = _tmux_wake(self.target)
1509
+ if ok:
1510
+ _stamp_wake(led)
1511
+ return True
1512
+ if ok is None:
1513
+ return None
1514
+ return False
1499
1515
  # #611: a clear composer proves the wake SUBMITTED only if
1500
1516
  # the pane it was injected into is still the pane we are
1501
1517
  # reading. The ledger flag survives a respawn; the wake
@@ -1979,6 +1995,19 @@ def _log_dm(state: MonitorState, dm: dict) -> None:
1979
1995
 
1980
1996
 
1981
1997
  _WAKE_PROMPT = "check mesh"
1998
+ # Opencode has no push channel (#601). The wake is one line that names the
1999
+ # pull tool and carries no DM body, so the turn cannot answer mail it was
2000
+ # not shown. Longer than _WAKE_PROMPT, so wake-text detection strips this
2001
+ # one first.
2002
+ _OPENCODE_WAKE_PROMPT = "check mesh: swarph_dm_unread"
2003
+ # A finished turn keeps its ▣ header and adds a duration.
2004
+ # Short turns read `· 3.3s` (drop-on-meta-edge, 1.18.33, card #961 post
2005
+ # 54557). A longer idle turn reads `· 3m 4s` (opencode pane, 2026-09-29
2006
+ # 07:10Z). On a wide pane the same row continues into the sidebar, so the
2007
+ # duration is not the end of the line. An in-progress header has no duration.
2008
+ _OPENCODE_DONE = re.compile(
2009
+ r"·\s*(?:\d+h\s*)?(?:\d+m\s*)?\d+(?:\.\d+)?s\b"
2010
+ )
1982
2011
  # Submit-verify bounds (#533): the settle pause lets the -l literal LAND in
1983
2012
  # the composer before Enter can submit it (the blind gesture raced this), and
1984
2013
  # the attempt bound keeps a never-submitting pane from being Enter-spammed
@@ -2072,20 +2101,168 @@ _COMPOSER_PLACEHOLDERS = ("Add a follow-up", "Ask Codex to do anything")
2072
2101
  _CURSOR_RUN_HINT = "ctrl+c to stop"
2073
2102
 
2074
2103
 
2104
+ def _only_wake_text(content: str) -> bool:
2105
+ """True when the composer holds wake text and nothing else.
2106
+
2107
+ The opencode prompt contains the cursor/claude prompt as a prefix, so
2108
+ the longer string is removed first. A stack of either prompt still
2109
+ counts as wake text (one Enter drains it). Anything left over is a
2110
+ human's line.
2111
+ """
2112
+ rest = content
2113
+ for prompt in (_OPENCODE_WAKE_PROMPT, _WAKE_PROMPT):
2114
+ rest = rest.replace(prompt, "")
2115
+ return rest.strip() == ""
2116
+
2117
+
2118
+ def _opencode_mode_row(line: str) -> bool:
2119
+ """The mode row under the input. ``--auto`` inserts a word, so the row
2120
+ is ``Build ·`` or ``Build auto ·`` (measured 1.18.33, both shapes).
2121
+ """
2122
+ s = line.strip()
2123
+ if not s.startswith("┃") or "·" not in s:
2124
+ return False
2125
+ body = s[1:].strip()
2126
+ return body.startswith("Build") or body.startswith("Plan")
2127
+
2128
+
2129
+ def _opencode_permission_row(line: str) -> bool:
2130
+ """The 1.18.33 dialog, drawn inside the box.
2131
+
2132
+ The row is ``┃ △ Permission required``. The mode row and the ``╹▀``
2133
+ border are gone on that screen, so this row is the whole signal.
2134
+ """
2135
+ s = line.strip()
2136
+ return s.startswith("┃") and "△" in s and "Permission required" in s
2137
+
2138
+
2139
+ def _is_opencode_pane(lines: list[str]) -> bool:
2140
+ """The opencode composer box, measured on 1.18.33.
2141
+
2142
+ The input row is a ``┃`` line. The mode row under it names Build or
2143
+ Plan and a ``·``. The bottom of the box is ``╹▀``. The permission
2144
+ dialog keeps the ``┃`` row and drops the mode row and the border.
2145
+ Cursor and claude do not draw any of these.
2146
+ """
2147
+ for ln in lines:
2148
+ s = ln.strip()
2149
+ if s.startswith("╹▀") or _opencode_mode_row(ln) or _opencode_permission_row(ln):
2150
+ return True
2151
+ return False
2152
+
2153
+
2154
+ def _opencode_running(lines: list[str]) -> bool:
2155
+ """True only while a turn is in progress.
2156
+
2157
+ The footer shows ``esc interrupt``, and the ▣ header has no trailing
2158
+ duration. A finished turn keeps the ▣ header and adds ``· <n>s``,
2159
+ with no interrupt hint. That pane is idle. The permission dialog is
2160
+ not a turn; it has its own branch.
2161
+ """
2162
+ if any("esc interrupt" in ln for ln in lines):
2163
+ return True
2164
+ for ln in lines:
2165
+ s = ln.strip()
2166
+ if s.startswith("▣") and not _OPENCODE_DONE.search(s):
2167
+ return True
2168
+ return False
2169
+
2170
+
2171
+ def _opencode_turn_finished(lines: list[str]) -> bool:
2172
+ """The ▣ header carried a duration and the interrupt hint is gone."""
2173
+ if _opencode_running(lines):
2174
+ return False
2175
+ return any(
2176
+ ln.strip().startswith("▣") and _OPENCODE_DONE.search(ln.strip())
2177
+ for ln in lines
2178
+ )
2179
+
2180
+
2181
+ def _opencode_in_progress(target: str) -> bool:
2182
+ """In-progress opencode turn. A deferral here keeps wake_outstanding."""
2183
+ lines = _capture_pane_lines(target)
2184
+ if not lines or not _is_opencode_pane(lines):
2185
+ return False
2186
+ return _opencode_running(lines)
2187
+
2188
+
2189
+ def _opencode_turn_finished_target(target: str) -> bool:
2190
+ lines = _capture_pane_lines(target)
2191
+ if not lines or not _is_opencode_pane(lines):
2192
+ return False
2193
+ return _opencode_turn_finished(lines)
2194
+
2195
+
2196
+ def _opencode_row_text(line: str) -> str:
2197
+ """Text on one ``┃`` row, with a right-aligned footer cut off.
2198
+
2199
+ A long cwd:branch is drawn inside the box, right-aligned, and wraps
2200
+ across as many rows as the path needs. The gap before that footer is
2201
+ a run of 4 or more spaces. Composer text is the left side of the row.
2202
+ Leading spaces stay in place for the split, then the left side is
2203
+ stripped. Skipping a fixed number of rows cannot work: the row count
2204
+ is the path length over the box width.
2205
+ """
2206
+ idx = line.find("┃")
2207
+ if idx < 0:
2208
+ return ""
2209
+ return re.split(r" {4,}", line[idx + 1:], maxsplit=1)[0].strip()
2210
+
2211
+
2212
+ def _opencode_input(lines: list[str]) -> Optional[str]:
2213
+ """Composer text inside the box, '' when that box is empty, None if no box.
2214
+
2215
+ Walk up from the mode row. Each ``┃`` row contributes only the text
2216
+ before its footer gap. The walk stops at the first line that is not a
2217
+ ``┃`` row — the top of the box — so a transcript ``┃`` row above the
2218
+ box is never composer text.
2219
+ """
2220
+ mode_at = None
2221
+ for i, ln in enumerate(lines):
2222
+ if _opencode_mode_row(ln):
2223
+ mode_at = i
2224
+ if mode_at is None:
2225
+ return None
2226
+ for ln in reversed(lines[:mode_at]):
2227
+ if not ln.strip().startswith("┃"):
2228
+ return ""
2229
+ text = _opencode_row_text(ln)
2230
+ if text:
2231
+ return text
2232
+ return ""
2233
+
2234
+
2235
+ def _opencode_composer_state(lines: list[str]) -> Optional[str]:
2236
+ if any(_opencode_permission_row(ln) for ln in lines):
2237
+ return "busy"
2238
+ text = _opencode_input(lines)
2239
+ if text is None:
2240
+ return None
2241
+ if not text or text.startswith("Ask anything"):
2242
+ return "clear"
2243
+ if _only_wake_text(text):
2244
+ return "wake"
2245
+ return "busy"
2246
+
2247
+
2075
2248
  def _agent_running(target: str) -> Optional[bool]:
2076
- """True if the pane's composer row carries cursor's run-state hint.
2077
-
2078
- The #619 deferral signal: a keystroke into a RUNNING TUI lands in the
2079
- follow-up queue, and the queue is input-gated (measured live 2026-08-26,
2080
- twice: queued wakes fired only when the human next typed — 10:00 and
2081
- 10:09Z). On an unattended cell a queued wake never fires, so no wake
2082
- keystroke may be sent while this returns True. None = pane or composer
2083
- unreadable; callers treat unknown as NOT-running (the composer state was
2084
- already established by then — this guard only vets the timing).
2249
+ """True while a keystroke would miss the idle composer.
2250
+
2251
+ Cursor: the composer row carries 'ctrl+c to stop' (measured live on
2252
+ cursor-lin 2026-08-24). A keystroke into that TUI lands in the
2253
+ follow-up queue, and the queue is input-gated (measured live
2254
+ 2026-08-26, twice: queued wakes fired only when the human next typed).
2255
+ Opencode: ``esc interrupt`` or a ▣ header with no trailing duration.
2256
+ A finished ``· <n>s`` header is idle. None =
2257
+ pane or composer unreadable; callers treat unknown as NOT-running
2258
+ (the composer state was already established by then — this guard
2259
+ only vets the timing).
2085
2260
  """
2086
2261
  lines = _capture_pane_lines(target)
2087
2262
  if lines is None:
2088
2263
  return None
2264
+ if _is_opencode_pane(lines):
2265
+ return _opencode_running(lines)
2089
2266
  composer = _composer_line(lines)
2090
2267
  if composer is None:
2091
2268
  return None
@@ -2112,6 +2289,8 @@ def _composer_state(target: str) -> Optional[str]:
2112
2289
  lines = _capture_pane_lines(target)
2113
2290
  if lines is None:
2114
2291
  return None
2292
+ if _is_opencode_pane(lines):
2293
+ return _opencode_composer_state(lines)
2115
2294
  composer = _composer_line(lines)
2116
2295
  if composer is None:
2117
2296
  return None
@@ -2126,7 +2305,7 @@ def _composer_state(target: str) -> Optional[str]:
2126
2305
  # A human who types exactly 'check mesh' by hand is indistinguishable
2127
2306
  # from our own unsubmitted wake — and submitting it runs the same
2128
2307
  # command, so the collision is harmless in that direction only.
2129
- if content.replace(_WAKE_PROMPT, "").strip() == "":
2308
+ if _only_wake_text(content):
2130
2309
  return "wake"
2131
2310
  return "busy"
2132
2311
 
@@ -2199,8 +2378,11 @@ def _tmux_wake(target: str) -> Optional[bool]:
2199
2378
  wake stays owed and the ledger retries the whole wake later.
2200
2379
  """
2201
2380
  try:
2381
+ seen = _capture_pane_lines(target)
2382
+ prompt = (_OPENCODE_WAKE_PROMPT
2383
+ if seen and _is_opencode_pane(seen) else _WAKE_PROMPT)
2202
2384
  subprocess.run(
2203
- ["tmux", "send-keys", "-t", target, "-l", _WAKE_PROMPT],
2385
+ ["tmux", "send-keys", "-t", target, "-l", prompt],
2204
2386
  check=True,
2205
2387
  stdout=subprocess.DEVNULL,
2206
2388
  stderr=subprocess.PIPE,
@@ -2234,6 +2416,10 @@ def _tmux_wake(target: str) -> Optional[bool]:
2234
2416
  # sink DEFER, keeping the cursor back so a later poll
2235
2417
  # retries the wake when the composer clears.
2236
2418
  return None
2419
+ # "wake" while a turn is already on screen: another Enter stacks
2420
+ # a follow-up. Defer; the sink does not advance.
2421
+ if _agent_running(target):
2422
+ return None
2237
2423
  # "wake": still sitting unsubmitted — loop another Enter.
2238
2424
  print(
2239
2425
  f"[monitor] tmux wake still unsubmitted after "
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: swarph-cli
3
- Version: 0.69.0
3
+ Version: 0.69.2
4
4
  Summary: The `swarph` binary — a multi-LLM CLI and mesh-gateway client + bundled server (`swarph gateway`): one-shot prompts across providers, interactive `chat`, multi-provider `spawn` (claude/codex/antigravity via a ProviderMembrane with subprocess billing-scrub), `mesh` send/inbox/register, `brain-ask` over the swarph-brain (gbrain) semantic memory, git-backed `assisted_memory`, per-invocation `gh` identity routing, a silent DM-wake hook, session `import`, and a stranded-session `watchdog`.
5
5
  Author: Pierre Samson, Claude Opus
6
6
  License: MIT
@@ -60,6 +60,7 @@ def test_tmux_wake_sends_literal_prompt_then_submits_and_verifies(monkeypatch):
60
60
  assert mesh._tmux_wake("gpt-lc:0.0") is True
61
61
  assert seen == ["gpt-lc:0.0"] # verified through the composer gate
62
62
  assert [command for command, _ in calls] == [
63
+ ["tmux", "capture-pane", "-p", "-t", "gpt-lc:0.0"],
63
64
  ["tmux", "send-keys", "-t", "gpt-lc:0.0", "-l", "check mesh"],
64
65
  ["tmux", "send-keys", "-t", "gpt-lc:0.0", "Enter"],
65
66
  ]
@@ -44,6 +44,8 @@ def _rig(monkeypatch, *, composer="clear", unread=3, session_created=None,
44
44
  monkeypatch.setattr(mesh, "_composer_state", lambda t: composer)
45
45
  monkeypatch.setattr(mesh, "_tmux_session_created", lambda t: session_created)
46
46
  monkeypatch.setattr(mesh, "_agent_running", lambda t: running)
47
+ monkeypatch.setattr(mesh, "_opencode_in_progress", lambda t: False)
48
+ monkeypatch.setattr(mesh, "_opencode_turn_finished_target", lambda t: False)
47
49
  monkeypatch.setattr(watchdog, "_gateway_unread_count",
48
50
  lambda *a, **k: unread)
49
51
 
@@ -53,7 +53,9 @@ def test_second_enter_fires_only_when_the_first_did_not_submit(tmux):
53
53
  """The Codex shape, now verified instead of blind: first Enter leaves the
54
54
  prompt in the composer, second clears it."""
55
55
  calls, state = tmux
56
- state["captures"] = ["> check mesh", "> "]
56
+ # First frame is the pre-inject look (prompt choice). The pending frame
57
+ # is what the first Enter left behind.
58
+ state["captures"] = ["> ", "> check mesh", "> "]
57
59
  assert mesh._tmux_wake("pane") is True
58
60
  assert enters(calls) == 2
59
61
 
@@ -62,7 +64,7 @@ def test_concatenated_backlog_is_detected_and_drained(tmux):
62
64
  """The observed defect shape: two wakes stacked unsubmitted. The substring
63
65
  check catches the concatenation and the retry Enter drains it."""
64
66
  calls, state = tmux
65
- state["captures"] = ["> check meshcheck mesh", "> "]
67
+ state["captures"] = ["> ", "> check meshcheck mesh", "> "]
66
68
  assert mesh._tmux_wake("pane") is True
67
69
  assert enters(calls) == 2
68
70
 
@@ -101,7 +103,7 @@ def test_capture_failure_mid_loop_stops_the_loop(tmux, monkeypatch):
101
103
  state["captures"] = ["> check mesh"] # first capture: still pending
102
104
 
103
105
  def flaky(argv, **kw):
104
- if argv[1] == "capture-pane" and state["i"] > 0:
106
+ if argv[1] == "capture-pane" and state["i"] > 1:
105
107
  raise OSError("tmux socket vanished")
106
108
  if argv[1] == "capture-pane":
107
109
  i = min(state["i"], len(state["captures"]) - 1)
@@ -161,7 +163,7 @@ def test_codex_composer_marker_counts_as_recognizable(tmux):
161
163
 
162
164
  def test_cursor_composer_holding_the_wake_is_pending(tmux):
163
165
  calls, state = tmux
164
- state["captures"] = ["→ check mesh", "→ Add a follow-up"]
166
+ state["captures"] = ["→ Add a follow-up", "→ check mesh", "→ Add a follow-up"]
165
167
  assert mesh._tmux_wake("pane") is True
166
168
  assert enters(calls) == 2
167
169
 
@@ -199,6 +201,8 @@ def gate(monkeypatch):
199
201
  lambda t: calls.__setitem__("enter", calls["enter"] + 1) or True)
200
202
  monkeypatch.setattr(mesh, "_wake_still_pending", lambda t: box["pending"])
201
203
  monkeypatch.setattr(mesh, "_composer_state", lambda t: box["composer"])
204
+ monkeypatch.setattr(mesh, "_opencode_in_progress", lambda t: False)
205
+ monkeypatch.setattr(mesh, "_opencode_turn_finished_target", lambda t: False)
202
206
  import swarph_cli.commands.watchdog as wd
203
207
  monkeypatch.setattr(wd, "_gateway_unread_count",
204
208
  lambda g, p, t: box["unread"])
@@ -540,3 +544,272 @@ def test_engine_defers_on_adopted_mid_settle_with_real_tmux_sink(tmp_path, tmux,
540
544
  out = capsys.readouterr()
541
545
  assert "DEFERRED" in out.out
542
546
  assert "DELIVERY FAILED" not in out.err
547
+
548
+
549
+ # ── opencode 1.18.33, measured on a sacrificial pane (card #961) ──────────
550
+ #
551
+ # The composer is a ┃ box, not a > / › / → line. A ▣ spinner sits between
552
+ # the transcript and the box; lines above it are history. `esc interrupt`
553
+ # and `Permission required` mean a keystroke is the wrong key.
554
+
555
+ _OPENCODE_IDLE = """\
556
+ ┃
557
+ ┃ Ask anything… "What is the tech stack of this project?"
558
+ ┃
559
+ ┃ Build · DeepSeek V4 Pro (New) OpenCode Go
560
+ ╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
561
+ tab agents
562
+ ctrl+p commands
563
+ """
564
+
565
+ _OPENCODE_HOLDING = """\
566
+ ┃
567
+ ┃ check mesh: swarph_dm_unread
568
+ ┃
569
+ ┃ Build · DeepSeek V4 Pro (New) OpenCode Go
570
+ ╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
571
+ """
572
+
573
+ _OPENCODE_RUNNING = """\
574
+ ┃ check mesh: swarph_dm_unread
575
+ ┃ Click to expand
576
+ ▣ Build · DeepSeek V4 Pro (New)
577
+ ┃
578
+ ┃
579
+ ┃ Build · DeepSeek V4 Pro (New) OpenCode Go
580
+ esc interrupt
581
+ """
582
+
583
+ # 1.18.33 draws the dialog inside the box. The mode row and the bottom
584
+ # border are not on this screen (drop-on-meta-edge, card #961 post 54557).
585
+ _OPENCODE_PERMISSION = """\
586
+ ┃
587
+ ┃ △ Permission required
588
+ ┃
589
+ Allow once
590
+ Allow always
591
+ Reject
592
+ """
593
+
594
+ _OPENCODE_FINISHED = """\
595
+ ▣ Build · DeepSeek V4 Pro (New) · 3.3s
596
+ ┃
597
+ ┃ Ask anything… "What is the tech stack of this project?"
598
+ ┃
599
+ ┃ Build · DeepSeek V4 Pro (New) OpenCode Go
600
+ ╹▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
601
+ tab agents ctrl+p commands
602
+ """
603
+
604
+
605
+ def test_live_idle_footer_is_clear_and_not_running(tmux):
606
+ """The 07:10Z pane: composer empty, transcript full of ┃ rows, footer
607
+ cwd on the ┃ row above the mode row, finished header `· 3m 4s`.
608
+ 0.69.1 returns that path as composer text and treats the header as
609
+ still running."""
610
+ from pathlib import Path
611
+ fixture = (Path(__file__).resolve().parent / "fixtures"
612
+ / "opencode-pane-idle-footer-0710.txt")
613
+ text = fixture.read_text(encoding="utf-8")
614
+ lines = text.splitlines()
615
+ assert any("Found the real blocker" in ln and ln.strip().startswith("┃")
616
+ for ln in lines)
617
+ _calls, state = tmux
618
+ state["captures"] = [text]
619
+ assert mesh._opencode_input(lines) == ""
620
+ assert mesh._composer_state("sac") == "clear"
621
+ assert mesh._agent_running("sac") is False
622
+
623
+
624
+ def test_drop_six_captures_split_the_footer():
625
+ """A wrapped cwd is a run of spaces, not a fixed number of rows.
626
+ Measured by drop-on-meta-edge on sacW 220x55 (card #961, #612)."""
627
+ from pathlib import Path
628
+ fix = Path(__file__).resolve().parent / "fixtures"
629
+ wide = (fix / "opencode-961" / "W2_turn2.txt").read_text(encoding="utf-8")
630
+ narrow = (fix / "opencode-961" / "G3_turn2.txt").read_text(encoding="utf-8")
631
+ live = (fix / "opencode-pane-idle-footer-0710.txt").read_text(encoding="utf-8")
632
+ assert mesh._opencode_input(wide.splitlines()) == ""
633
+ assert mesh._opencode_input(live.splitlines()) == ""
634
+ assert mesh._opencode_input(narrow.splitlines()) == ""
635
+ typed_narrow = "\n".join([
636
+ " ┃",
637
+ " ┃ hello from a human",
638
+ " ┃",
639
+ " ┃ Build · DeepSeek V4 Pro (New) OpenCode Go",
640
+ ])
641
+ assert mesh._opencode_input(typed_narrow.splitlines()) == "hello from a human"
642
+ overlay = (
643
+ " ┃ hello from a human" + (" " * 150)
644
+ + "meta-edge/d7cba227-9da7-4d92-92da-"
645
+ )
646
+ typed_wide = "\n".join([
647
+ " ┃",
648
+ overlay,
649
+ " ┃ Build · DeepSeek V4 Pro (New) OpenCode Go" + (" " * 40)
650
+ + "feat/sacrificial-612",
651
+ ])
652
+ assert mesh._opencode_input(typed_wide.splitlines()) == "hello from a human"
653
+ holding = "\n".join([
654
+ "┃",
655
+ "┃ check mesh: swarph_dm_unread",
656
+ "┃",
657
+ "┃ Build · DeepSeek V4 Pro (New) OpenCode Go",
658
+ ])
659
+ assert mesh._opencode_input(holding.splitlines()) == "check mesh: swarph_dm_unread"
660
+ # A wide pane keeps the sidebar on the ▣ row, past the duration.
661
+ sidebar = "▣ Build · DeepSeek V4.1 Flash · 5.1s" + (" " * 40) + "LSPs are disabled"
662
+ assert mesh._OPENCODE_DONE.search(sidebar)
663
+ assert mesh._opencode_running([sidebar]) is False
664
+ assert mesh._opencode_running(["▣ Build · DeepSeek V4 Pro (New)"]) is True
665
+
666
+
667
+ def test_wide_empty_box_records_the_wake_outstanding(tmux):
668
+ """W-D1: the footer showed up after Enter and the old read called the
669
+ box busy, so the injected wake was reported as a human's line and
670
+ wake_outstanding stayed unset."""
671
+ from pathlib import Path
672
+ wide = (Path(__file__).resolve().parent / "fixtures" / "opencode-961"
673
+ / "W2_turn2.txt").read_text(encoding="utf-8")
674
+ calls, state = tmux
675
+ state["captures"] = [wide]
676
+ st = _State()
677
+ assert mesh.TmuxSink("sacW").deliver(st, [], 1) is True
678
+ assert st.ledger("x")["wake_outstanding"] is True
679
+ literals = [c for c in calls if "-l" in c]
680
+ assert literals == [[
681
+ "tmux", "send-keys", "-t", "sacW", "-l", mesh._OPENCODE_WAKE_PROMPT]]
682
+ assert enters(calls) == 1
683
+
684
+
685
+ def test_opencode_idle_box_is_a_clear_composer(tmux):
686
+ calls, state = tmux
687
+ state["captures"] = [_OPENCODE_IDLE]
688
+ assert mesh._composer_state("sac") == "clear"
689
+ assert mesh._agent_running("sac") is False
690
+ # `--auto` rewrites the mode row to "Build auto ·" (1.18.33).
691
+ state["captures"] = [_OPENCODE_IDLE.replace("Build ·", "Build auto ·")]
692
+ state["i"] = 0
693
+ assert mesh._composer_state("sac") == "clear"
694
+
695
+
696
+ def test_opencode_unsubmitted_pointer_is_wake_not_unknown(tmux):
697
+ """Main reads this pane as no composer at all, so the sink fails closed
698
+ and the text stays typed. The box is a composer, and this line is wake."""
699
+ calls, state = tmux
700
+ state["captures"] = [_OPENCODE_HOLDING]
701
+ assert mesh._composer_state("sac") == "wake"
702
+
703
+
704
+ def test_opencode_human_text_in_the_box_is_busy(tmux):
705
+ calls, state = tmux
706
+ state["captures"] = [_OPENCODE_HOLDING.replace(
707
+ "check mesh: swarph_dm_unread", "look at the diff")]
708
+ assert mesh._composer_state("sac") == "busy"
709
+
710
+
711
+ def test_opencode_transcript_above_the_spinner_is_not_the_composer(tmux):
712
+ """The submitted prompt stays on screen above ▣. Reading it as the
713
+ composer would send another Enter into the running turn."""
714
+ calls, state = tmux
715
+ state["captures"] = [_OPENCODE_RUNNING]
716
+ assert mesh._composer_state("sac") == "clear"
717
+ assert mesh._agent_running("sac") is True
718
+
719
+
720
+ def test_opencode_permission_modal_is_not_typed_into(tmux):
721
+ """The dialog is a ┃ row. deliver returns None from the busy branch.
722
+ False would be the unrecognised-pane fallthrough, which also sends no
723
+ keys and is the wrong outcome."""
724
+ calls, state = tmux
725
+ assert "┃ △ Permission required" in _OPENCODE_PERMISSION
726
+ assert "╹▀" not in _OPENCODE_PERMISSION
727
+ assert "Build ·" not in _OPENCODE_PERMISSION
728
+ state["captures"] = [_OPENCODE_PERMISSION]
729
+ assert mesh._composer_state("sac") == "busy"
730
+ sink = mesh.TmuxSink("sac")
731
+ assert sink.deliver(_State(), [], 1) is None
732
+ assert enters(calls) == 0
733
+ assert not any("-l" in c for c in calls)
734
+
735
+
736
+ def test_opencode_finished_header_is_not_a_running_turn(tmux):
737
+ calls, state = tmux
738
+ state["captures"] = [_OPENCODE_FINISHED]
739
+ assert mesh._agent_running("sac") is False
740
+ assert mesh._opencode_turn_finished_target("sac") is True
741
+ assert mesh._composer_state("sac") == "clear"
742
+
743
+
744
+ def test_opencode_deferral_keeps_wake_outstanding(tmux):
745
+ calls, state = tmux
746
+ state["captures"] = [_OPENCODE_RUNNING]
747
+ st = _State()
748
+ st.ledger("x")["wake_outstanding"] = True
749
+ assert mesh.TmuxSink("sac").deliver(st, [], 2) is None
750
+ assert st.ledger("x")["wake_outstanding"] is True
751
+ assert enters(calls) == 0
752
+ assert not any("-l" in c for c in calls)
753
+
754
+
755
+ def test_opencode_finished_turn_earns_another_wake(tmux, monkeypatch):
756
+ """Inside the trust window a standing wake would report delivery with
757
+ no keystroke. A finished header means the cell is idle again."""
758
+ import time
759
+ import swarph_cli.commands.watchdog as wd
760
+ monkeypatch.setattr(wd, "_gateway_unread_count", lambda *a, **k: 1)
761
+ calls, state = tmux
762
+ state["captures"] = [_OPENCODE_FINISHED]
763
+ st = _State()
764
+ st.ledger("x")["wake_outstanding"] = True
765
+ st.ledger("x")["last_wake_injected_at"] = time.time()
766
+ assert mesh.TmuxSink("sac").deliver(st, [], 3) is True
767
+ literals = [c for c in calls if "-l" in c]
768
+ assert literals == [[
769
+ "tmux", "send-keys", "-t", "sac", "-l", mesh._OPENCODE_WAKE_PROMPT]]
770
+ assert enters(calls) == 1
771
+ assert st.ledger("x")["wake_outstanding"] is True
772
+
773
+
774
+ def test_opencode_running_turn_gets_no_second_prompt(tmux):
775
+ calls, state = tmux
776
+ state["captures"] = [_OPENCODE_RUNNING]
777
+ sink = mesh.TmuxSink("sac")
778
+ assert sink.deliver(_State(), [], 1) is None
779
+ assert enters(calls) == 0
780
+ assert not any("-l" in c for c in calls)
781
+
782
+
783
+ def test_opencode_idle_injects_the_pointer_then_one_enter(tmux):
784
+ calls, state = tmux
785
+ state["captures"] = [_OPENCODE_IDLE, _OPENCODE_RUNNING]
786
+ assert mesh._tmux_wake("sac") is True
787
+ literals = [c for c in calls if "-l" in c]
788
+ assert literals == [[
789
+ "tmux", "send-keys", "-t", "sac", "-l", mesh._OPENCODE_WAKE_PROMPT]]
790
+ assert enters(calls) == 1
791
+
792
+
793
+ def test_opencode_holding_wake_is_nudged_not_retyped(tmux):
794
+ calls, state = tmux
795
+ state["captures"] = [_OPENCODE_HOLDING]
796
+ sink = mesh.TmuxSink("sac")
797
+ assert sink.deliver(_State(), [], 1) is True
798
+ assert enters(calls) == 1
799
+ assert not any("-l" in c for c in calls)
800
+
801
+
802
+ def test_opencode_dropin_names_the_tmux_sink_and_is_not_the_template():
803
+ """install-unit renders swarph-monitor@.service only. This file is the
804
+ review copy of the opencode drop-in; nothing in this test installs it."""
805
+ from pathlib import Path
806
+ root = Path(__file__).resolve().parents[1]
807
+ dropin = root / "deploy" / "monitor" / "swarph-monitor@opencode.conf"
808
+ text = dropin.read_text(encoding="utf-8")
809
+ assert "ExecStart=" in text
810
+ assert "--deliver tmux:opencode" in text
811
+ assert "--deliver pull" in text
812
+ assert "--as opencode" in text
813
+ template = (root / "src" / "swarph_cli" / "systemd"
814
+ / "swarph-monitor@.service").read_text(encoding="utf-8")
815
+ assert "tmux:opencode" not in template
File without changes
File without changes
File without changes