meshcode 2.11.4__tar.gz → 2.11.5__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (210) hide show
  1. {meshcode-2.11.4 → meshcode-2.11.5}/PKG-INFO +1 -1
  2. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/server.py +155 -149
  4. meshcode-2.11.5/meshcode/meshcode_mcp/sleep_signals.py +110 -0
  5. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/PKG-INFO +1 -1
  6. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/SOURCES.txt +2 -0
  7. {meshcode-2.11.4 → meshcode-2.11.5}/pyproject.toml +1 -1
  8. meshcode-2.11.5/tests/test_sleep_signals.py +160 -0
  9. {meshcode-2.11.4 → meshcode-2.11.5}/README.md +0 -0
  10. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/_stop_hook_template.py +0 -0
  11. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/ascii_art.py +0 -0
  12. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/cli.py +0 -0
  13. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/comms_v4.py +0 -0
  14. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/compat.py +0 -0
  15. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/daemon.py +0 -0
  16. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/error_hints.py +0 -0
  17. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/exceptions.py +0 -0
  18. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/invites.py +0 -0
  19. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/launcher.py +0 -0
  20. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/launcher_install.py +0 -0
  21. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/__init__.py +0 -0
  22. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/__main__.py +0 -0
  23. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/backend.py +0 -0
  24. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/realtime.py +0 -0
  25. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/test_backend.py +0 -0
  26. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  27. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  28. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/preferences.py +0 -0
  29. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/protocol_handler.py +0 -0
  30. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/protocol_v2.py +0 -0
  31. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/quickstart.py +0 -0
  32. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/run_agent.py +0 -0
  33. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/scripts/check_secrets.py +0 -0
  34. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/scripts/race_rate_harness.py +0 -0
  35. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/secrets.py +0 -0
  36. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/self_update.py +0 -0
  37. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/setup_clients.py +0 -0
  38. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/supervisor.py +0 -0
  39. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode/upload.py +0 -0
  40. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/comms_v4.py +0 -0
  41. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  42. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  43. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/cli.py +0 -0
  44. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  45. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/compat.py +0 -0
  46. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  47. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  48. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/invites.py +0 -0
  49. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  50. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  51. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  52. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  53. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  54. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  55. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  56. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  57. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  58. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  59. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  60. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  61. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  62. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  63. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  64. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  65. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  66. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  67. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/meshcode/upload.py +0 -0
  68. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  69. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_core.py +0 -0
  70. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  71. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  72. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  73. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  74. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  75. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  76. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  77. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  78. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  79. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  80. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  81. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  82. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  83. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  84. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  85. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  86. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  87. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  88. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  89. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  90. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  91. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  92. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  93. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  94. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  95. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  96. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  97. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  98. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  99. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  100. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  101. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  102. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  103. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  104. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  105. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  106. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  107. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  108. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/comms_v4.py +0 -0
  109. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  110. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  111. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/cli.py +0 -0
  112. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  113. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/compat.py +0 -0
  114. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  115. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  116. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/invites.py +0 -0
  117. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  118. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  119. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  120. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  121. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  122. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  123. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  124. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  125. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  126. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  127. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  128. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  129. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  130. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  131. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  132. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  133. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  134. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  135. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/meshcode/upload.py +0 -0
  136. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  137. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_core.py +0 -0
  138. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  139. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  140. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  141. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  142. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  143. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  144. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  145. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  146. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  147. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  148. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  149. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/comms_v4.py +0 -0
  150. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  151. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  152. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  153. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  154. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  155. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  156. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  157. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  158. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  159. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  160. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  161. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  162. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  163. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  164. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  165. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  166. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  167. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  168. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  169. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  170. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  171. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  172. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  173. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  174. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  175. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  176. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  177. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  178. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_core.py +0 -0
  179. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  180. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  181. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  182. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  183. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  184. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  185. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  186. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  187. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  188. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  189. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  190. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/dependency_links.txt +0 -0
  191. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/entry_points.txt +0 -0
  192. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/requires.txt +0 -0
  193. {meshcode-2.11.4 → meshcode-2.11.5}/meshcode.egg-info/top_level.txt +0 -0
  194. {meshcode-2.11.4 → meshcode-2.11.5}/setup.cfg +0 -0
  195. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_auto_update_hardening.py +0 -0
  196. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_core.py +0 -0
  197. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_cross_agent_messaging.py +0 -0
  198. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_esc_deaf_state.py +0 -0
  199. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_exceptions.py +0 -0
  200. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_lease_sigterm_release.py +0 -0
  201. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_mark_read_batch.py +0 -0
  202. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_migration_integrity.py +0 -0
  203. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_realtime_event_freshness.py +0 -0
  204. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_rls_cross_tenant.py +0 -0
  205. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_rpc_migrations.py +0 -0
  206. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_security_regressions.py +0 -0
  207. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_sentinel.py +0 -0
  208. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_status_enum_coverage.py +0 -0
  209. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_stay_on_loop_hook.py +0 -0
  210. {meshcode-2.11.4 → meshcode-2.11.5}/tests/test_wait_open_tasks_contradiction.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.4
3
+ Version: 2.11.5
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -1,5 +1,5 @@
1
1
  """MeshCode — Real-time communication between AI agents."""
2
- __version__ = "2.11.4"
2
+ __version__ = "2.11.5"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -499,73 +499,18 @@ def _filter_and_mark(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
499
499
  return out
500
500
 
501
501
 
502
- _SLEEP_PAYLOAD_TYPES = {"sleep", "go_to_sleep", "shutdown", "got_done", "done",
503
- "exit", "stop", "kill", "terminate"}
504
- # Multi-word imperative markers ONLY. Single Spanish verbs like "salir" /
505
- # "terminar" / "duerme" were too loose and caught idiomatic prose like
506
- # "terminaron sus tasks" or "no puedo salir" (msg cfbd36b0 2026-05-14 alexa +
507
- # commander incidents — feedback_done_signal_false_positive_idiomatic
508
- # precedent). The parser now requires a directive-shaped phrase: a verb
509
- # combined with a temporal/object qualifier ("ahora" / "todos" / "to sleep")
510
- # so describing an event in prose can't trip must_exit.
511
- _SLEEP_TEXT_MARKERS = (
512
- # English directives
513
- "go to sleep", "all sleep now", "sleep now", "got_done", "go_done",
514
- "shut down", "shutdown", "stop now", "exit now",
515
- # Spanish directives (es-MX, sammybenu's primary). All require either a
516
- # leading "a"/"todos" or a trailing "ahora" so plain conjugations
517
- # ("salir", "terminar", "duerme") in body prose don't trigger.
518
- "a dormir", "todos a dormir", "duerme ahora", "duerman ahora",
519
- "dormir ahora", "para la sesion", "exit y duerme",
502
+ # Sleep-signal parsing lives in a separate pure module so it can be tested
503
+ # without booting the full MCP server. Re-export the public surface here
504
+ # for back-compat with any in-tree caller that imports from server.
505
+ from .sleep_signals import ( # noqa: F401
506
+ _SLEEP_PAYLOAD_TYPES,
507
+ _SLEEP_TEXT_MARKERS,
508
+ _KNOWN_HUMAN_HANDLES,
509
+ _is_human_authored,
510
+ _looks_like_sleep_signal,
511
+ _split_messages,
520
512
  )
521
513
 
522
-
523
- def _looks_like_sleep_signal(m: Dict[str, Any]) -> bool:
524
- """Detect commander broadcasts / DMs that authorize the wait-loop exit.
525
-
526
- Catches three encodings (BE-S5.11): structured payload.type, top-level
527
- text marker, and broadcast-with-sleep-type. Single source of truth so
528
- every receive path (PRODUCT RULE 2 + inner _meshcode_wait_inner) routes
529
- sleep authorizations into done_signals consistently.
530
- """
531
- pl = m.get("payload") or {}
532
- if isinstance(pl, dict):
533
- if str(pl.get("type", "")).lower() in _SLEEP_PAYLOAD_TYPES:
534
- return True
535
- if str(pl.get("directive", "")).lower() in _SLEEP_PAYLOAD_TYPES:
536
- return True
537
- text = str(pl.get("text", "")).lower()
538
- if any(marker in text for marker in _SLEEP_TEXT_MARKERS):
539
- return True
540
- return False
541
-
542
-
543
- def _split_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
544
- """Split a list of normalized message dicts into messages / acks / done_signals.
545
-
546
- BE-S5.11: classify ANY message (including broadcasts) carrying a sleep
547
- intent into done_signals so meshcode_wait surfaces them with the
548
- must_exit flag. Previous logic only matched type='done' literally,
549
- which the broadcast path could never produce.
550
- """
551
- real: List[Dict[str, Any]] = []
552
- acks: List[Dict[str, Any]] = []
553
- dones: List[Dict[str, Any]] = []
554
- for m in messages:
555
- t = m.get("type", "msg")
556
- if t == "ack":
557
- acks.append(m)
558
- elif t == "done" or _looks_like_sleep_signal(m):
559
- dones.append(m)
560
- else:
561
- real.append(m)
562
- return {
563
- "messages": real,
564
- "acks": acks,
565
- "done_signals": dones,
566
- "count": len(real),
567
- }
568
-
569
514
  from . import backend as be
570
515
  from .realtime import RealtimeListener
571
516
 
@@ -1718,8 +1663,26 @@ def _orphan_watchdog_fn():
1718
1663
  # Parent exited (or boot was already orphaned). Confirm with
1719
1664
  # stdin peer probe to avoid killing intentional daemons.
1720
1665
  if _BOOT_PPID != 1 or _stdin_peer_dead():
1666
+ # 2s confirmation grace before os._exit(0). Without this,
1667
+ # a transient ppid/stdin race (FastMCP stdio fd duplication,
1668
+ # brief reparent during macOS process-group shuffles) can
1669
+ # false-positive and kill an actively-attached MCP — observed
1670
+ # mode-b of fe57b7a5 (mid-session crash after ~10min normal
1671
+ # ops, Samuel 2026-05-14T19:31Z mesh-commander session).
1672
+ # We accept a +2s detection latency for legitimate orphans;
1673
+ # the heartbeat-path orphan check at line ~1798 still catches
1674
+ # them within one heartbeat tick (5-15s).
1675
+ _orphan_watchdog_stop.wait(2)
1676
+ if _orphan_watchdog_stop.is_set():
1677
+ break
1678
+ if os.getppid() != 1 or not (_BOOT_PPID != 1 or _stdin_peer_dead()):
1679
+ log.info(
1680
+ f"orphan watchdog: transient ppid==1 cleared after "
1681
+ f"2s grace (ppid_now={os.getppid()}) — continuing"
1682
+ )
1683
+ continue
1721
1684
  log.warning(
1722
- f"orphan watchdog: parent gone "
1685
+ f"orphan watchdog: parent gone (confirmed after 2s grace) "
1723
1686
  f"(boot_ppid={_BOOT_PPID} → ppid=1, "
1724
1687
  f"stdin_peer_dead={_stdin_peer_dead()}) — "
1725
1688
  f"force-exiting MCP for {AGENT_NAME}"
@@ -1852,18 +1815,34 @@ def _heartbeat_loop_inner():
1852
1815
  and os.getppid() == 1):
1853
1816
  _orphan = (_BOOT_PPID != 1) or _stdin_peer_dead()
1854
1817
  if _orphan:
1855
- log.warning(
1856
- f"parent process exited (boot_ppid={_BOOT_PPID} "
1857
- f"current_ppid=1, stdin_peer_dead={_BOOT_PPID == 1}) "
1858
- f"— orphan MCP for {AGENT_NAME}, releasing lease "
1859
- f"and exiting"
1860
- )
1861
- try:
1862
- _release_lease()
1863
- except Exception:
1864
- pass
1865
- _heartbeat_stop.set()
1866
- os._exit(0)
1818
+ # 2s confirmation grace — same rationale as the
1819
+ # orphan_watchdog above (fe57b7a5 mode-b). Heartbeat
1820
+ # ticks every 5-15s, so adding 2s here trades minimal
1821
+ # detection latency for false-positive resistance.
1822
+ _heartbeat_stop.wait(2)
1823
+ if _heartbeat_stop.is_set():
1824
+ break
1825
+ if os.getppid() != 1 or not ((_BOOT_PPID != 1) or _stdin_peer_dead()):
1826
+ log.info(
1827
+ f"heartbeat orphan check: transient ppid==1 "
1828
+ f"cleared after 2s grace (ppid_now={os.getppid()}) "
1829
+ f"— continuing"
1830
+ )
1831
+ else:
1832
+ log.warning(
1833
+ f"parent process exited "
1834
+ f"(confirmed after 2s grace) "
1835
+ f"(boot_ppid={_BOOT_PPID} → "
1836
+ f"current_ppid=1, stdin_peer_dead={_BOOT_PPID == 1}) "
1837
+ f"— orphan MCP for {AGENT_NAME}, releasing lease "
1838
+ f"and exiting"
1839
+ )
1840
+ try:
1841
+ _release_lease()
1842
+ except Exception:
1843
+ pass
1844
+ _heartbeat_stop.set()
1845
+ os._exit(0)
1867
1846
  except Exception:
1868
1847
  pass
1869
1848
 
@@ -2064,65 +2043,67 @@ async def lifespan(_app):
2064
2043
  log.warning(f"initial heartbeat attempt {_attempt+1} failed: {e}")
2065
2044
  import time; time.sleep(2)
2066
2045
 
2067
- # Per-agent persona (BE-S4.1.sdk / mig 232): tone/voice/policy that
2068
- # shapes how the agent talks. Loaded once per session; soft-fail if the
2069
- # RPC is unavailable on older projects.
2070
- global _PERSONA
2071
- try:
2072
- _persona_resp = be.sb_rpc("mc_agent_persona", {
2073
- "p_api_key": _get_api_key(),
2074
- "p_project_id": _PROJECT_ID,
2075
- "p_agent_name": AGENT_NAME,
2076
- })
2077
- if isinstance(_persona_resp, dict) and _persona_resp.get("ok"):
2078
- _PERSONA = _persona_resp.get("persona") or {}
2079
- log.info(
2080
- f"[meshcode] persona loaded: tone={_PERSONA.get('tone','default')} "
2081
- f"language={_PERSONA.get('language','en')} "
2082
- f"reply_max_tokens={_PERSONA.get('reply_max_tokens','unset')}"
2083
- )
2084
- else:
2085
- log.debug(f"persona load returned non-ok: {_persona_resp} — continuing without")
2086
- except Exception as e:
2087
- log.debug(f"persona RPC unavailable ({e}) — continuing without")
2088
-
2089
- # Boot-context bundle (S1-2.F2 / mig 217): single RPC returns last_seen
2090
- # + recent msgs + open tasks + relevant memories + peer state. Cached on
2091
- # the module global so the first ~30s of tool calls can short-circuit.
2092
- # Soft-fail: if the RPC isn't deployed (older project) or errors, log
2093
- # warn + continue with the legacy lazy-load path.
2094
- global _BOOT_CONTEXT_BUNDLE, _BOOT_CONTEXT_AT
2095
- try:
2096
- _ctx = be.sb_rpc("mc_agent_boot_context", {
2097
- "p_api_key": _get_api_key(),
2098
- "p_project_id": _PROJECT_ID,
2099
- "p_agent_name": AGENT_NAME,
2100
- })
2101
- if isinstance(_ctx, dict) and _ctx.get("ok"):
2102
- _BOOT_CONTEXT_BUNDLE = _ctx
2103
- _BOOT_CONTEXT_AT = _time.time()
2104
- log.info(
2105
- f"[meshcode] boot_context: "
2106
- f"{len(_ctx.get('recent_msgs', []))} unread, "
2107
- f"{len(_ctx.get('open_tasks', []))} open tasks, "
2108
- f"{len(_ctx.get('relevant_memories', []))} memories surfaced"
2109
- )
2110
- else:
2111
- # Defense-in-depth: if the failure is a missing GRANT (mig
2112
- # 270 regressed or older project), the legacy lazy-load
2113
- # path already fills the gap. WARN puts a stderr line that
2114
- # some Claude Code versions surface as "MCP server failed
2115
- # at launch". Downgrade only the GRANT case; other non-ok
2116
- # responses still warn.
2117
- if _is_grant_or_permission_error(_ctx):
2118
- log.debug(f"boot_context permission_denied (legacy boot ok): {_ctx}")
2046
+ # fe57b7a5 mode-a fix (2026-05-14): mc_agent_persona + mc_agent_boot_context
2047
+ # used to run synchronously here, adding ~1-4s to lifespan pre-yield latency.
2048
+ # Combined with the module-import RPCs (3-15s), commanders with large
2049
+ # _INSTRUCTIONS payloads + many memories exceeded Claude Code's MCP
2050
+ # handshake window under congestion → "MCP failed at launch". Both RPCs
2051
+ # produce module globals (_PERSONA, _BOOT_CONTEXT_BUNDLE) consumed lazily
2052
+ # by tools that already have soft-fail fallbacks, so deferring them into
2053
+ # a daemon thread is safe — first reads within ~1s after lifespan yield
2054
+ # may see None, in which case consumers fall back to live RPC paths.
2055
+ global _PERSONA, _BOOT_CONTEXT_BUNDLE, _BOOT_CONTEXT_AT
2056
+
2057
+ def _load_persona_bg():
2058
+ global _PERSONA
2059
+ try:
2060
+ _persona_resp = be.sb_rpc("mc_agent_persona", {
2061
+ "p_api_key": _get_api_key(),
2062
+ "p_project_id": _PROJECT_ID,
2063
+ "p_agent_name": AGENT_NAME,
2064
+ })
2065
+ if isinstance(_persona_resp, dict) and _persona_resp.get("ok"):
2066
+ _PERSONA = _persona_resp.get("persona") or {}
2067
+ log.info(
2068
+ f"[meshcode] persona loaded (bg): tone={_PERSONA.get('tone','default')} "
2069
+ f"language={_PERSONA.get('language','en')} "
2070
+ f"reply_max_tokens={_PERSONA.get('reply_max_tokens','unset')}"
2071
+ )
2119
2072
  else:
2120
- log.warning(f"boot_context returned non-ok: {_ctx} — continuing legacy boot")
2121
- except Exception as e:
2122
- if _is_grant_or_permission_error(str(e)):
2123
- log.debug(f"boot_context permission_denied (legacy boot ok): {e}")
2124
- else:
2125
- log.warning(f"boot_context RPC unavailable ({e}) — continuing legacy boot")
2073
+ log.debug(f"persona load returned non-ok (bg): {_persona_resp} — continuing without")
2074
+ except Exception as e:
2075
+ log.debug(f"persona RPC unavailable (bg) ({e}) — continuing without")
2076
+
2077
+ def _load_boot_context_bg():
2078
+ global _BOOT_CONTEXT_BUNDLE, _BOOT_CONTEXT_AT
2079
+ try:
2080
+ _ctx = be.sb_rpc("mc_agent_boot_context", {
2081
+ "p_api_key": _get_api_key(),
2082
+ "p_project_id": _PROJECT_ID,
2083
+ "p_agent_name": AGENT_NAME,
2084
+ })
2085
+ if isinstance(_ctx, dict) and _ctx.get("ok"):
2086
+ _BOOT_CONTEXT_BUNDLE = _ctx
2087
+ _BOOT_CONTEXT_AT = _time.time()
2088
+ log.info(
2089
+ f"[meshcode] boot_context (bg): "
2090
+ f"{len(_ctx.get('recent_msgs', []))} unread, "
2091
+ f"{len(_ctx.get('open_tasks', []))} open tasks, "
2092
+ f"{len(_ctx.get('relevant_memories', []))} memories surfaced"
2093
+ )
2094
+ else:
2095
+ if _is_grant_or_permission_error(_ctx):
2096
+ log.debug(f"boot_context permission_denied (bg, legacy boot ok): {_ctx}")
2097
+ else:
2098
+ log.warning(f"boot_context returned non-ok (bg): {_ctx} — continuing legacy boot")
2099
+ except Exception as e:
2100
+ if _is_grant_or_permission_error(str(e)):
2101
+ log.debug(f"boot_context permission_denied (bg, legacy boot ok): {e}")
2102
+ else:
2103
+ log.warning(f"boot_context RPC unavailable (bg) ({e}) — continuing legacy boot")
2104
+
2105
+ _threading.Thread(target=_load_persona_bg, daemon=True, name="meshcode-load-persona").start()
2106
+ _threading.Thread(target=_load_boot_context_bg, daemon=True, name="meshcode-load-boot-context").start()
2126
2107
 
2127
2108
  # Heartbeat in daemon thread — independent of asyncio event loop.
2128
2109
  _heartbeat_stop.clear()
@@ -2726,21 +2707,43 @@ def meshcode_download_file(file_id: str) -> Dict[str, Any]:
2726
2707
  file_name = file_info.get("file_name", "download")
2727
2708
  signed_url = file_info.get("signed_url")
2728
2709
 
2729
- # Strategy 1: signed_url (no auth headers, no client service_role needed)
2710
+ sb_url = os.environ.get("SUPABASE_URL", be._sb_url if hasattr(be, '_sb_url') else "")
2711
+ anon_key = os.environ.get("SUPABASE_KEY", be._sb_key if hasattr(be, '_sb_key') else "")
2712
+ service_key = os.environ.get("MESHCODE_SUPABASE_SERVICE_KEY", "")
2713
+
2714
+ tried_labels: List[str] = []
2715
+ last_err: Optional[str] = None
2716
+
2717
+ # Strategy 1: signed_url (no auth headers, no client service_role needed).
2718
+ # Path B (235e85b6): server-side RPC mints the URL via backend GUCs and
2719
+ # returns it; client just GETs. Works for guest workspaces that
2720
+ # intentionally lack MESHCODE_SUPABASE_SERVICE_KEY in their .mcp.json.
2730
2721
  if signed_url:
2722
+ tried_labels.append("signed_url")
2731
2723
  try:
2732
2724
  with _req.urlopen(_req.Request(signed_url), timeout=30) as resp:
2733
2725
  content = resp.read()
2734
2726
  return _consume(content, mime_type, file_name)
2735
- except _uerr.HTTPError:
2736
- pass # fall through to legacy path
2737
- except Exception:
2738
- pass
2739
-
2740
- # Strategy 2: legacy service_role/anon/public on storage_path
2741
- sb_url = os.environ.get("SUPABASE_URL", be._sb_url if hasattr(be, '_sb_url') else "")
2742
- anon_key = os.environ.get("SUPABASE_KEY", be._sb_key if hasattr(be, '_sb_key') else "")
2743
- service_key = os.environ.get("MESHCODE_SUPABASE_SERVICE_KEY", "")
2727
+ except _uerr.HTTPError as e:
2728
+ last_err = f"HTTP {e.code} on signed_url: {e.reason}"
2729
+ except Exception as e:
2730
+ last_err = f"{type(e).__name__} on signed_url: {e}"
2731
+
2732
+ # Strategy 2 candidates: direct storage REST with available keys.
2733
+ # Guest workspaces (no MESHCODE_SUPABASE_SERVICE_KEY) cannot read a
2734
+ # private bucket via anon every Strategy-2 attempt will 400/403. Bail
2735
+ # early with a clear root-cause diagnosis instead of churning through
2736
+ # doomed attempts and surfacing a generic HTTP 400 to the user. Task
2737
+ # 8ecd56bb 2026-05-14: alexa + bob hit this path.
2738
+ if not signed_url and not service_key:
2739
+ return {
2740
+ "error": "server-side signed_url unavailable and this workspace has no service_role key",
2741
+ "error_code": "missing_server_guc",
2742
+ "tried": tried_labels,
2743
+ "storage_path": storage_path,
2744
+ "hint": "backend RPC mc_get_file_download did not return a signed_url. Likely the Supabase project is missing GUC `app.settings.service_role_key` + `app.settings.supabase_url` so the RPC body cannot mint a signed URL. Ask Samuel to apply both ALTER DATABASE settings on the meshcode Supabase project (sb_secret_... + https://gjinagyyjttyxnaoavnz.supabase.co), or distribute MESHCODE_SUPABASE_SERVICE_KEY into guest .mcp.json (security tradeoff — not recommended).",
2745
+ "diagnosis": "guest_workspace_pattern",
2746
+ }
2744
2747
 
2745
2748
  if not sb_url or not anon_key:
2746
2749
  return {"error": "storage not configured", "error_code": "config_error"}
@@ -2769,9 +2772,7 @@ def meshcode_download_file(file_id: str) -> Dict[str, Any]:
2769
2772
  attempts.append(("anon auth", auth_url, anon_key))
2770
2773
  attempts.append(("anon public", public_url, anon_key))
2771
2774
 
2772
- last_err = None
2773
2775
  content = None
2774
- tried_labels: List[str] = ["signed_url"] if signed_url else []
2775
2776
  for label, _url, _key in attempts:
2776
2777
  tried_labels.append(label)
2777
2778
  try:
@@ -2789,7 +2790,12 @@ def meshcode_download_file(file_id: str) -> Dict[str, Any]:
2789
2790
  "error_code": "download_error",
2790
2791
  "tried": tried_labels,
2791
2792
  "storage_path": storage_path,
2792
- "hint": "ask commander to apply backend GUC app.settings.service_role_key + supabase_url so the RPC can mint signed URLs" if not signed_url else None,
2793
+ "hint": (
2794
+ "every fallback failed. If 'signed_url' was tried and 400'd, the URL may be expired or "
2795
+ "signed against a stale key — re-request via meshcode_download_file. If signed_url was "
2796
+ "absent, the backend GUC `app.settings.service_role_key`/`app.settings.supabase_url` is "
2797
+ "missing on the Supabase project."
2798
+ ),
2793
2799
  }
2794
2800
 
2795
2801
  return _consume(content, mime_type, file_name)
@@ -0,0 +1,110 @@
1
+ """Sleep-authorization signal parser for the wait loop.
2
+
3
+ Pure module — no side effects, no env deps. Extracted from server.py so the
4
+ parser can be imported and tested in isolation. server.py re-exports these
5
+ names for back-compat.
6
+
7
+ Authorizes a wait-loop exit (must_exit=True) only on explicit sleep-auth
8
+ context. Two valid encodings:
9
+
10
+ (1) STRUCTURED DIRECTIVE — payload.type or payload.directive is in
11
+ _SLEEP_PAYLOAD_TYPES. Deliberately-shaped messages; an agent that
12
+ sets payload.type to a sleep verb is issuing a directive on purpose.
13
+ (2) HUMAN TEXT KEYWORD — payload.text contains a sleep marker AND the
14
+ sender is a human user.
15
+
16
+ Plain text idioms from AI-role agents are deliberately IGNORED — quoting
17
+ "a dormir" or describing "Samuel cierra la sesion" inside a debate-close
18
+ summary is not authorization. Task fabfc602 (bob@justforfun 2026-05-14).
19
+ """
20
+ from __future__ import annotations
21
+
22
+ from typing import Any, Dict, List
23
+
24
+ _SLEEP_PAYLOAD_TYPES = {"sleep", "go_to_sleep", "shutdown", "got_done", "done",
25
+ "exit", "stop", "kill", "terminate"}
26
+
27
+ # Multi-word imperative markers ONLY. Single Spanish verbs like "salir" /
28
+ # "terminar" / "duerme" were too loose and caught idiomatic prose like
29
+ # "terminaron sus tasks" or "no puedo salir" (msg cfbd36b0 2026-05-14 alexa +
30
+ # commander incidents — feedback_done_signal_false_positive_idiomatic
31
+ # precedent). The parser requires a directive-shaped phrase: a verb
32
+ # combined with a temporal/object qualifier ("ahora" / "todos" / "to sleep")
33
+ # so describing an event in prose can't trip must_exit.
34
+ _SLEEP_TEXT_MARKERS = (
35
+ # English directives
36
+ "go to sleep", "all sleep now", "sleep now", "got_done", "go_done",
37
+ "shut down", "shutdown", "stop now", "exit now",
38
+ # Spanish directives (es-MX). Each requires a leading "a"/"todos" or
39
+ # a trailing "ahora" so plain conjugations in prose don't trigger.
40
+ "a dormir", "todos a dormir", "duerme ahora", "duerman ahora",
41
+ "dormir ahora", "para la sesion", "exit y duerme",
42
+ )
43
+
44
+ # Back-compat fallback for messages predating mig 289 (payload.sender_role
45
+ # stamping). Kept in sync with _stop_hook_template._KNOWN_HUMAN_HANDLES so
46
+ # server + hook share authority on who counts as a human user.
47
+ _KNOWN_HUMAN_HANDLES = frozenset({"sammybenu", "samuel", "sam"})
48
+
49
+
50
+ def _is_human_authored(m: Dict[str, Any]) -> bool:
51
+ """True when the message carries explicit human-user authorship.
52
+
53
+ Two encodings (mig 289 + back-compat):
54
+ (a) payload.sender_role == "human_user" (or .from_role) — server-stamped
55
+ (b) sender handle in _KNOWN_HUMAN_HANDLES — pre-mig-289 fallback
56
+ """
57
+ pl = m.get("payload") or {}
58
+ if isinstance(pl, dict):
59
+ role = pl.get("sender_role") or pl.get("from_role")
60
+ if isinstance(role, str) and role.lower() == "human_user":
61
+ return True
62
+ sender = m.get("from") or ""
63
+ if isinstance(sender, str) and sender.lower() in _KNOWN_HUMAN_HANDLES:
64
+ return True
65
+ return False
66
+
67
+
68
+ def _looks_like_sleep_signal(m: Dict[str, Any]) -> bool:
69
+ """Detect mesh messages that authorize the wait-loop exit.
70
+
71
+ See module docstring for the two valid encodings and the rationale
72
+ for ignoring idiom matches from AI-role senders.
73
+ """
74
+ pl = m.get("payload") or {}
75
+ if isinstance(pl, dict):
76
+ if str(pl.get("type", "")).lower() in _SLEEP_PAYLOAD_TYPES:
77
+ return True
78
+ if str(pl.get("directive", "")).lower() in _SLEEP_PAYLOAD_TYPES:
79
+ return True
80
+ text = str(pl.get("text", "")).lower()
81
+ if text and any(marker in text for marker in _SLEEP_TEXT_MARKERS):
82
+ if _is_human_authored(m):
83
+ return True
84
+ return False
85
+
86
+
87
+ def _split_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
88
+ """Split a list of normalized message dicts into messages / acks / done_signals.
89
+
90
+ Classifies any message carrying a sleep intent into done_signals so
91
+ meshcode_wait surfaces them with must_exit=True. Idiom matches from
92
+ AI-role senders fall through to plain `messages` (no must_exit).
93
+ """
94
+ real: List[Dict[str, Any]] = []
95
+ acks: List[Dict[str, Any]] = []
96
+ dones: List[Dict[str, Any]] = []
97
+ for m in messages:
98
+ t = m.get("type", "msg")
99
+ if t == "ack":
100
+ acks.append(m)
101
+ elif t == "done" or _looks_like_sleep_signal(m):
102
+ dones.append(m)
103
+ else:
104
+ real.append(m)
105
+ return {
106
+ "messages": real,
107
+ "acks": acks,
108
+ "done_signals": dones,
109
+ "count": len(real),
110
+ }
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.4
3
+ Version: 2.11.5
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -183,6 +183,7 @@ meshcode/meshcode_mcp/__main__.py
183
183
  meshcode/meshcode_mcp/backend.py
184
184
  meshcode/meshcode_mcp/realtime.py
185
185
  meshcode/meshcode_mcp/server.py
186
+ meshcode/meshcode_mcp/sleep_signals.py
186
187
  meshcode/meshcode_mcp/test_backend.py
187
188
  meshcode/meshcode_mcp/test_realtime.py
188
189
  meshcode/meshcode_mcp/test_server_wrapper.py
@@ -201,6 +202,7 @@ tests/test_rls_cross_tenant.py
201
202
  tests/test_rpc_migrations.py
202
203
  tests/test_security_regressions.py
203
204
  tests/test_sentinel.py
205
+ tests/test_sleep_signals.py
204
206
  tests/test_status_enum_coverage.py
205
207
  tests/test_stay_on_loop_hook.py
206
208
  tests/test_wait_open_tasks_contradiction.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.11.4"
7
+ version = "2.11.5"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}