meshcode 2.10.91__tar.gz → 2.10.94__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 (201) hide show
  1. {meshcode-2.10.91 → meshcode-2.10.94}/PKG-INFO +6 -1
  2. {meshcode-2.10.91 → meshcode-2.10.94}/README.md +5 -0
  3. meshcode-2.10.94/meshcode/__init__.py +82 -0
  4. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/meshcode_mcp/server.py +273 -125
  5. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode.egg-info/PKG-INFO +6 -1
  6. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode.egg-info/SOURCES.txt +1 -0
  7. {meshcode-2.10.91 → meshcode-2.10.94}/pyproject.toml +1 -1
  8. meshcode-2.10.94/tests/test_auto_update_hardening.py +238 -0
  9. meshcode-2.10.91/meshcode/__init__.py +0 -82
  10. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/ascii_art.py +0 -0
  11. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/cli.py +0 -0
  12. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/comms_v4.py +0 -0
  13. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/compat.py +0 -0
  14. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/error_hints.py +0 -0
  15. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/exceptions.py +0 -0
  16. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/invites.py +0 -0
  17. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/launcher.py +0 -0
  18. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/launcher_install.py +0 -0
  19. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/meshcode_mcp/__init__.py +0 -0
  20. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/meshcode_mcp/__main__.py +0 -0
  21. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/meshcode_mcp/backend.py +0 -0
  22. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/meshcode_mcp/realtime.py +0 -0
  23. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/meshcode_mcp/test_backend.py +0 -0
  24. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  25. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  26. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/preferences.py +0 -0
  27. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/protocol_v2.py +0 -0
  28. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/quickstart.py +0 -0
  29. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/run_agent.py +0 -0
  30. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/secrets.py +0 -0
  31. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/self_update.py +0 -0
  32. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/setup_clients.py +0 -0
  33. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/supervisor.py +0 -0
  34. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode/upload.py +0 -0
  35. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/comms_v4.py +0 -0
  36. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  37. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  38. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/cli.py +0 -0
  39. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  40. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/compat.py +0 -0
  41. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  42. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  43. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/invites.py +0 -0
  44. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  45. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  46. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  47. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  48. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  49. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  50. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  51. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  52. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  53. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  54. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  55. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  56. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  57. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  58. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  59. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  60. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  61. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  62. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/meshcode/upload.py +0 -0
  63. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  64. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_core.py +0 -0
  65. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  66. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  67. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  68. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  69. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  70. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  71. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  72. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  73. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  74. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  75. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  76. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  77. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  78. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  79. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  80. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  81. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  82. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  83. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  84. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  85. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  86. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  87. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  88. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  89. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  90. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  91. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  92. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  93. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  94. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  95. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  96. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  97. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  98. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  99. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  100. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  101. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  102. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  103. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/comms_v4.py +0 -0
  104. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  105. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  106. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/cli.py +0 -0
  107. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  108. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/compat.py +0 -0
  109. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  110. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  111. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/invites.py +0 -0
  112. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  113. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  114. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  115. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  116. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  117. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  118. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  119. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  120. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  121. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  122. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  123. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  124. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  125. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  126. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  127. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  128. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  129. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  130. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/meshcode/upload.py +0 -0
  131. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  132. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_core.py +0 -0
  133. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  134. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  135. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  136. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  137. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  138. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  139. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  140. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  141. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  142. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  143. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  144. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/comms_v4.py +0 -0
  145. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  146. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  147. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  148. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  149. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  150. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  151. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  152. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  153. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  154. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  155. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  156. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  157. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  158. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  159. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  160. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  161. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  162. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  163. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  164. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  165. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  166. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  167. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  168. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  169. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  170. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  171. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  172. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  173. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_core.py +0 -0
  174. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  175. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  176. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  177. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  178. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  179. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  180. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  181. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  182. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  183. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  184. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  185. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode.egg-info/dependency_links.txt +0 -0
  186. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode.egg-info/entry_points.txt +0 -0
  187. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode.egg-info/requires.txt +0 -0
  188. {meshcode-2.10.91 → meshcode-2.10.94}/meshcode.egg-info/top_level.txt +0 -0
  189. {meshcode-2.10.91 → meshcode-2.10.94}/setup.cfg +0 -0
  190. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_core.py +0 -0
  191. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_cross_agent_messaging.py +0 -0
  192. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_esc_deaf_state.py +0 -0
  193. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_exceptions.py +0 -0
  194. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_mark_read_batch.py +0 -0
  195. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_migration_integrity.py +0 -0
  196. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_realtime_event_freshness.py +0 -0
  197. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_rls_cross_tenant.py +0 -0
  198. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_rpc_migrations.py +0 -0
  199. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_security_regressions.py +0 -0
  200. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_sentinel.py +0 -0
  201. {meshcode-2.10.91 → meshcode-2.10.94}/tests/test_status_enum_coverage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.91
3
+ Version: 2.10.94
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -422,6 +422,11 @@ $env:MESHCODE_PROJECT_ID="your-project-uuid" # Windows PowerShell
422
422
 
423
423
  You can also run `meshcode doctor` (v2.10.41+) to diagnose stale paths, missing dependencies, and config issues across all your workspaces.
424
424
 
425
+ **9b. Windows: first relaunch shows the old version after a PyPI upgrade**
426
+ Auto-update on Unix uses `os.execv` to swap the running Python image in-place — one launch, latest code (v2.10.93+). On Windows this isn't possible: an `os.execv` replacement breaks Claude Code's stdio pipe to the MCP child (the new process has a different PID). Meshcode therefore installs the new version but defers the swap to the **next** launch on Windows. Expect: `[meshcode] note: Windows defers auto-update to next launch ...` on stderr, then close and re-open Claude Code once to get the new code. To force-load now, run `pip install --upgrade meshcode` manually before launching Claude Code.
427
+
428
+ If the dashboard tags an agent as having `boot_version_drift`, that means pip wrote the new version to disk but our running process is still the old one — typically benign on Windows (next launch fixes it) and worth investigating on Unix (path-precedence regression).
429
+
425
430
  **10. `MCP server failed to start` in the Claude Code `/mcp` panel**
426
431
  Run `claude --debug` to see the underlying error. Nine times out of ten it's a stale or missing key — run `meshcode login mc_xxx` again.
427
432
 
@@ -396,6 +396,11 @@ $env:MESHCODE_PROJECT_ID="your-project-uuid" # Windows PowerShell
396
396
 
397
397
  You can also run `meshcode doctor` (v2.10.41+) to diagnose stale paths, missing dependencies, and config issues across all your workspaces.
398
398
 
399
+ **9b. Windows: first relaunch shows the old version after a PyPI upgrade**
400
+ Auto-update on Unix uses `os.execv` to swap the running Python image in-place — one launch, latest code (v2.10.93+). On Windows this isn't possible: an `os.execv` replacement breaks Claude Code's stdio pipe to the MCP child (the new process has a different PID). Meshcode therefore installs the new version but defers the swap to the **next** launch on Windows. Expect: `[meshcode] note: Windows defers auto-update to next launch ...` on stderr, then close and re-open Claude Code once to get the new code. To force-load now, run `pip install --upgrade meshcode` manually before launching Claude Code.
401
+
402
+ If the dashboard tags an agent as having `boot_version_drift`, that means pip wrote the new version to disk but our running process is still the old one — typically benign on Windows (next launch fixes it) and worth investigating on Unix (path-precedence regression).
403
+
399
404
  **10. `MCP server failed to start` in the Claude Code `/mcp` panel**
400
405
  Run `claude --debug` to see the underlying error. Nine times out of ten it's a stale or missing key — run `meshcode login mc_xxx` again.
401
406
 
@@ -0,0 +1,82 @@
1
+ """MeshCode — Real-time communication between AI agents."""
2
+ __version__ = "2.10.94"
3
+
4
+ # Exception hierarchy — eagerly imported (lightweight, no deps)
5
+ from meshcode.exceptions import ( # noqa: F401
6
+ MeshCodeError,
7
+ AuthError,
8
+ RPCError,
9
+ MeshCodeTimeoutError,
10
+ MeshCodeConnectionError,
11
+ )
12
+
13
+ # Public API — lazy imports to avoid heavy deps at import time
14
+ def __getattr__(name):
15
+ if name == "backend":
16
+ from meshcode.meshcode_mcp import backend
17
+ return backend
18
+ if name in _BACKEND_EXPORTS:
19
+ from meshcode.meshcode_mcp import backend
20
+ return getattr(backend, name)
21
+ if name in _SECRETS_EXPORTS:
22
+ from meshcode import secrets
23
+ return getattr(secrets, name)
24
+ raise AttributeError(f"module 'meshcode' has no attribute {name!r}")
25
+
26
+
27
+ # Backend: core messaging & agent management
28
+ _BACKEND_EXPORTS = {
29
+ "send_message",
30
+ "read_inbox",
31
+ "count_pending",
32
+ "get_board",
33
+ "heartbeat",
34
+ "set_status",
35
+ "register_agent",
36
+ "get_project_id",
37
+ "sb_rpc",
38
+ "task_create",
39
+ "task_list",
40
+ "encrypt_payload",
41
+ "decrypt_payload",
42
+ }
43
+
44
+ # Secrets: credential management
45
+ _SECRETS_EXPORTS = {
46
+ "get_api_key",
47
+ "set_api_key",
48
+ "list_profiles",
49
+ }
50
+
51
+ __all__ = [
52
+ "__version__",
53
+ "backend",
54
+ # Exceptions
55
+ "MeshCodeError",
56
+ "AuthError",
57
+ "RPCError",
58
+ "MeshCodeTimeoutError",
59
+ "MeshCodeConnectionError",
60
+ # Messaging
61
+ "send_message",
62
+ "read_inbox",
63
+ "count_pending",
64
+ # Agent management
65
+ "register_agent",
66
+ "get_project_id",
67
+ "get_board",
68
+ "heartbeat",
69
+ "set_status",
70
+ # Tasks
71
+ "task_create",
72
+ "task_list",
73
+ # Low-level
74
+ "sb_rpc",
75
+ # Encryption
76
+ "encrypt_payload",
77
+ "decrypt_payload",
78
+ # Credentials
79
+ "get_api_key",
80
+ "set_api_key",
81
+ "list_profiles",
82
+ ]
@@ -333,8 +333,50 @@ def _filter_and_mark(messages: List[Dict[str, Any]]) -> List[Dict[str, Any]]:
333
333
  return out
334
334
 
335
335
 
336
+ _SLEEP_PAYLOAD_TYPES = {"sleep", "go_to_sleep", "shutdown", "got_done", "done",
337
+ "exit", "stop", "kill", "terminate"}
338
+ # Spanish + English markers (es-MX hot path per SDK-S6.1). Matched as
339
+ # substrings, lowercased before compare. Keep exclusive enough to not
340
+ # false-positive on casual chatter (e.g. "no quiero dormir" still matches —
341
+ # accepted tradeoff for user-control reliability).
342
+ _SLEEP_TEXT_MARKERS = (
343
+ # English
344
+ "go to sleep", "all sleep now", "sleep now", "got_done", "go_done",
345
+ "shut down", "shutdown", "stop now", "exit now",
346
+ # Spanish (es-MX, sammybenu's primary)
347
+ "a dormir", "todos a dormir", "duerme", "duerman", "dormir ahora",
348
+ "salir", "terminar", "para la sesion", "exit y duerme",
349
+ )
350
+
351
+
352
+ def _looks_like_sleep_signal(m: Dict[str, Any]) -> bool:
353
+ """Detect commander broadcasts / DMs that authorize the wait-loop exit.
354
+
355
+ Catches three encodings (BE-S5.11): structured payload.type, top-level
356
+ text marker, and broadcast-with-sleep-type. Single source of truth so
357
+ every receive path (PRODUCT RULE 2 + inner _meshcode_wait_inner) routes
358
+ sleep authorizations into done_signals consistently.
359
+ """
360
+ pl = m.get("payload") or {}
361
+ if isinstance(pl, dict):
362
+ if str(pl.get("type", "")).lower() in _SLEEP_PAYLOAD_TYPES:
363
+ return True
364
+ if str(pl.get("directive", "")).lower() in _SLEEP_PAYLOAD_TYPES:
365
+ return True
366
+ text = str(pl.get("text", "")).lower()
367
+ if any(marker in text for marker in _SLEEP_TEXT_MARKERS):
368
+ return True
369
+ return False
370
+
371
+
336
372
  def _split_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
337
- """Split a list of normalized message dicts into messages / acks / done_signals."""
373
+ """Split a list of normalized message dicts into messages / acks / done_signals.
374
+
375
+ BE-S5.11: classify ANY message (including broadcasts) carrying a sleep
376
+ intent into done_signals so meshcode_wait surfaces them with the
377
+ must_exit flag. Previous logic only matched type='done' literally,
378
+ which the broadcast path could never produce.
379
+ """
338
380
  real: List[Dict[str, Any]] = []
339
381
  acks: List[Dict[str, Any]] = []
340
382
  dones: List[Dict[str, Any]] = []
@@ -342,7 +384,7 @@ def _split_messages(messages: List[Dict[str, Any]]) -> Dict[str, Any]:
342
384
  t = m.get("type", "msg")
343
385
  if t == "ack":
344
386
  acks.append(m)
345
- elif t == "done":
387
+ elif t == "done" or _looks_like_sleep_signal(m):
346
388
  dones.append(m)
347
389
  else:
348
390
  real.append(m)
@@ -812,6 +854,42 @@ _INSTANCE_ID = f"mcp-{_uuid.uuid4().hex[:12]}"
812
854
  # launch via launchd/systemd) the orphan check is a no-op and we never
813
855
  # false-terminate a legitimately-detached process.
814
856
  _BOOT_PPID = os.getppid()
857
+
858
+
859
+ def _stdin_peer_dead() -> bool:
860
+ """Non-destructively check whether stdin's peer has closed.
861
+
862
+ Used by the orphan-MCP guard when PPID == 1 to distinguish the
863
+ parent-died-during-import race (peer dead → exit) from a legitimate
864
+ detached daemon launch (peer alive → tolerate).
865
+
866
+ Uses MSG_PEEK so any pending JSON-RPC bytes stay in the kernel buffer
867
+ for FastMCP's stdio reader. socket.detach() releases the fd without
868
+ closing it. Returns False on any error so we never falsely kill an
869
+ otherwise-healthy process.
870
+ """
871
+ try:
872
+ import socket as _sock
873
+ s = _sock.socket(fileno=0)
874
+ try:
875
+ s.setblocking(False)
876
+ try:
877
+ data = s.recv(1, _sock.MSG_PEEK)
878
+ # Empty bytes from a non-blocking recv == EOF == peer closed.
879
+ return data == b""
880
+ except BlockingIOError:
881
+ # Data not yet available but socket is healthy.
882
+ return False
883
+ except OSError:
884
+ # ENOTCONN / ENOTSOCK on a real pipe/tty → not a dead unix
885
+ # socket; treat as alive.
886
+ return False
887
+ finally:
888
+ s.detach()
889
+ except Exception:
890
+ return False
891
+
892
+
815
893
  # Boot-context bundle (S1-2.F2): mc_agent_boot_context result cached at
816
894
  # lifespan startup so the first 30s of tool calls can short-circuit
817
895
  # repeated queries. Tools that read this fall back to live RPCs after the
@@ -1137,64 +1215,15 @@ def _build_instructions() -> str:
1137
1215
 
1138
1216
  base = f"""You are agent "{AGENT_NAME}" in meshwork "{PROJECT_NAME}".{role_block}{launch_block}
1139
1217
 
1140
- {_db_loop}PERMANENT LOOP (MANDATORY — #1 RULE):
1141
- You MUST stay in meshcode_wait() forever. After EVERY action, call meshcode_wait().
1142
- NEVER exit. NEVER stop. NEVER say "standing by" without calling meshcode_wait().
1143
- The ONLY exits: user says "stop", got_done signal, or fatal error.
1144
- Timeout? Call meshcode_wait() again with 2x timeout (cap 1800s).
1145
-
1146
- LOOP: act → meshcode_send if needed → meshcode_wait() → repeat.
1147
-
1148
- RULES:
1149
- - Use MCP tools only (never CLI commands in bash).
1150
- - Tasks > messages. Claim tasks via meshcode_tasks/task_claim/task_complete.
1151
- - Messages <100 tokens. Long content → create task.
1152
- - No empty acks. JSON reports only.
1153
- - Threading: pass in_reply_to.
1154
- - sensitive=True for secrets/PII.
1155
-
1156
- SESSION START (do these IMMEDIATELY — don't wait for user input):
1157
- The moment you see these instructions — whether the user said "boot", "hi",
1158
- nothing at all, or anything else — run the full protocol. Never reply with
1159
- only a memory summary and then idle. If your client defers MCP tool schemas
1160
- (shows tool names without parameters), your FIRST action is to load them:
1218
+ {_db_loop}LOOP (#1 rule): act → meshcode_wait() → repeat. Never exit/stop without calling meshcode_wait(). EXIT IMMEDIATELY when wait returns must_exit=True (set status=sleeping then end session). Other exits: user says stop, fatal error. Timeout → re-call with 2x (cap 1800s).
1219
+
1220
+ RULES: MCP tools only. Tasks > messages. Messages <100 tokens (long → task). No empty acks, JSON only. Thread via in_reply_to. sensitive=True for secrets.
1221
+
1222
+ SESSION START (run NOW, don't wait for input). If tool schemas are deferred:
1161
1223
  ToolSearch(query="select:meshcode_set_status,meshcode_check,meshcode_tasks,meshcode_auto_wake,meshcode_status,meshcode_wait,meshcode_send,meshcode_task_claim,meshcode_task_complete,meshcode_remember,meshcode_recall")
1162
- Then execute:
1163
- 1. meshcode_set_status(status="online", task="ready")
1164
- 2. meshcode_check() read NEW messages only (last_seen is auto-restored from mesh memory on boot, old messages are skipped automatically)
1165
- 3. meshcode_tasks() — check for assigned/pending tasks and claim any unclaimed ones
1166
- 4. meshcode_auto_wake() — scan meshwork health, create tasks for issues found
1167
- 5. meshcode_status() — see who's online
1168
- 6. If other agents are online → meshcode_send them a greeting
1169
- 7. meshcode_wait() — enter the loop
1170
- DO NOT ASK THE USER "what do you want to work on" — the mesh tells you via tasks/messages.
1171
-
1172
- CRITICAL: You communicate by CALLING TOOLS, not by thinking or writing text.
1173
- To talk to another agent → call meshcode_send(to="agent", message="...")
1174
- To check who's online → call meshcode_status()
1175
- To wait for messages → call meshcode_wait()
1176
- NEVER just describe what you would do. DO IT by calling the tool.
1177
-
1178
- CROSS-MESH: meshcode_send(to="agent@meshwork") routes via active link.
1179
- meshcode_link(target) creates pending link, target accepts. Expand with
1180
- meshcode_expand_link(). No sensitive msgs cross-mesh.
1181
-
1182
- MEMORY: meshcode_remember(key, value) persists across sessions.
1183
- meshcode_recall(key?) retrieves. meshcode_forget(key) deletes.
1184
- Only remember reusable learnings: mistakes, feedback, patterns, preferences.
1185
- Do NOT save task summaries — tasks already persist in the task system.
1186
- Do NOT use memory for session state or ephemeral data.
1187
- Save reusable code patterns as template_* keys for instant recall.
1188
-
1189
- SCRATCHPAD: meshcode_scratchpad_set/get for shared meshwork-level context
1190
- (decisions, conventions, architecture notes). All agents can read/write.
1191
-
1192
- ACCOUNT MANAGEMENT: you can create meshworks (meshcode_create_meshwork),
1193
- add agents (meshcode_add_agent), edit roles/prompts (meshcode_edit_agent),
1194
- and edit other agents' memory (meshcode_edit_memory). Always tell the user
1195
- what CLI command to run next (e.g. "meshcode run backend in a new terminal").
1196
-
1197
- Setup help → README.md or https://meshcode.io/docs
1224
+ Then: set_status(online,ready) → check() → tasks() → auto_wake() → status() → wait(). Don't ask user "what to work on" — the mesh tells you.
1225
+
1226
+ COMMUNICATE BY CALLING TOOLS, not by thinking aloud. Cross-mesh: send(to="agent@meshwork"). Reference docs (memory/scratchpad/account ops) recall agent_protocol_quick_ref when needed.
1198
1227
  """
1199
1228
  # Inject commander protocol if this agent is a leader
1200
1229
  is_leader = _is_leader_agent()
@@ -1370,6 +1399,47 @@ _STASHED_SESSION = None
1370
1399
  _MAIN_LOOP: asyncio.AbstractEventLoop | None = None
1371
1400
 
1372
1401
  _heartbeat_stop = _threading.Event()
1402
+ _orphan_watchdog_stop = _threading.Event()
1403
+
1404
+
1405
+ def _orphan_watchdog_fn():
1406
+ """Independent orphan detector — polls every 2s.
1407
+
1408
+ Defense-in-depth alongside the heartbeat-loop orphan check. The
1409
+ heartbeat thread can stall on Supabase HTTP calls (5-30s), and the
1410
+ interval itself is 5-15s. This watchdog gives sub-2s detection of
1411
+ "parent terminal closed" so phantom MCPs can't accumulate Supabase
1412
+ HTTP traffic or appear alive on the dashboard.
1413
+
1414
+ Exits via os._exit(0) — SIG_IGN handlers in run_server() make
1415
+ signal-based shutdown unreliable.
1416
+ """
1417
+ import platform as _pl_wd
1418
+ if _pl_wd.system() == "Windows":
1419
+ # Windows doesn't auto-reparent to PID 1; rely on heartbeat path.
1420
+ return
1421
+ while not _orphan_watchdog_stop.is_set():
1422
+ try:
1423
+ ppid_now = os.getppid()
1424
+ if ppid_now == 1:
1425
+ # Parent exited (or boot was already orphaned). Confirm with
1426
+ # stdin peer probe to avoid killing intentional daemons.
1427
+ if _BOOT_PPID != 1 or _stdin_peer_dead():
1428
+ log.warning(
1429
+ f"orphan watchdog: parent gone "
1430
+ f"(boot_ppid={_BOOT_PPID} → ppid=1, "
1431
+ f"stdin_peer_dead={_stdin_peer_dead()}) — "
1432
+ f"force-exiting MCP for {AGENT_NAME}"
1433
+ )
1434
+ try:
1435
+ _release_lease()
1436
+ except Exception:
1437
+ pass
1438
+ _heartbeat_stop.set()
1439
+ os._exit(0)
1440
+ except Exception:
1441
+ pass
1442
+ _orphan_watchdog_stop.wait(2)
1373
1443
 
1374
1444
  # Windows CPU tracking: (Get-Process).CPU returns cumulative seconds, not a
1375
1445
  # real-time percentage like Unix `ps -o %cpu`. We track the previous reading
@@ -1470,29 +1540,37 @@ def _heartbeat_loop_inner():
1470
1540
  while not _heartbeat_stop.is_set():
1471
1541
  try:
1472
1542
  # Orphan-MCP guard (Unix): when the Claude Code parent exits without
1473
- # closing stdio, the MCP child gets reparented to PID 1 and would
1474
- # otherwise keep heartbeating forever the dashboard then shows the
1475
- # agent as alive ("sleeping") with no terminal behind it. Skip the
1476
- # check on Windows (no automatic reparenting) and skip when the
1477
- # boot PPID was already 1 (intentional daemon launch via launchd).
1478
- # Use os._exit(0) SIGTERM/SIGINT/SIGHUP are all installed as
1479
- # ignore handlers in run_server() so signaling ourselves is a no-op.
1543
+ # closing stdio, the MCP child gets reparented to PID 1. If we
1544
+ # heartbeat anyway, the dashboard shows the agent alive ("sleeping")
1545
+ # with no terminal behind it (incident 2026-05-04: front-2 phantom
1546
+ # ran 4h with PPID=1, fd 0/1/2 → unix:(none), ~5K HTTP calls).
1547
+ #
1548
+ # Two scenarios fire here:
1549
+ # (a) PPID went from real-parent 1: parent died after boot.
1550
+ # (b) PPID was already 1 at module import: parent died DURING
1551
+ # import (race), or it was intentionally daemon-launched.
1552
+ # For (b) we used to skip — that hid the race. Now we also peek
1553
+ # at stdin: if the unix-socket peer is gone, EOF is returned and
1554
+ # we exit. This catches the race without breaking real daemons
1555
+ # (a launchd plist that pipes a real stdin keeps blocking).
1480
1556
  try:
1481
1557
  import platform as _pl_orphan
1482
1558
  if (_pl_orphan.system() != "Windows"
1483
- and _BOOT_PPID != 1
1484
1559
  and os.getppid() == 1):
1485
- log.warning(
1486
- f"parent process exited (boot_ppid={_BOOT_PPID} → "
1487
- f"current_ppid=1) — orphan MCP for {AGENT_NAME}, "
1488
- f"releasing lease and exiting"
1489
- )
1490
- try:
1491
- _release_lease()
1492
- except Exception:
1493
- pass
1494
- _heartbeat_stop.set()
1495
- os._exit(0)
1560
+ _orphan = (_BOOT_PPID != 1) or _stdin_peer_dead()
1561
+ if _orphan:
1562
+ log.warning(
1563
+ f"parent process exited (boot_ppid={_BOOT_PPID} → "
1564
+ f"current_ppid=1, stdin_peer_dead={_BOOT_PPID == 1}) "
1565
+ f"— orphan MCP for {AGENT_NAME}, releasing lease "
1566
+ f"and exiting"
1567
+ )
1568
+ try:
1569
+ _release_lease()
1570
+ except Exception:
1571
+ pass
1572
+ _heartbeat_stop.set()
1573
+ os._exit(0)
1496
1574
  except Exception:
1497
1575
  pass
1498
1576
 
@@ -1669,6 +1747,25 @@ async def lifespan(_app):
1669
1747
  be.set_status(_PROJECT_ID, AGENT_NAME, "idle", "MCP session active", api_key=_get_api_key())
1670
1748
  log.info(f"[meshcode] Agent {AGENT_NAME} online — initial heartbeat sent")
1671
1749
  _log_activity_bg("agent_online", f"{AGENT_NAME} came online")
1750
+ # Soft-relay any boot_version_drift detected at startup. One-shot:
1751
+ # consume the env-var sentinel set in run_server() so we don't
1752
+ # spam the commander on every retry attempt or future heartbeat.
1753
+ _drift = os.environ.pop("_MESHCODE_BOOT_DRIFT", None)
1754
+ if _drift:
1755
+ try:
1756
+ be.send_message(
1757
+ _PROJECT_ID, AGENT_NAME, "mesh-commander",
1758
+ {
1759
+ "type": "boot_version_drift",
1760
+ "drift": _drift,
1761
+ "agent": AGENT_NAME,
1762
+ "platform": sys.platform,
1763
+ },
1764
+ msg_type="report",
1765
+ api_key=_get_api_key(),
1766
+ )
1767
+ except Exception as _drift_e:
1768
+ log.debug(f"boot_version_drift relay failed: {_drift_e}")
1672
1769
  break
1673
1770
  except Exception as e:
1674
1771
  log.warning(f"initial heartbeat attempt {_attempt+1} failed: {e}")
@@ -1726,7 +1823,16 @@ async def lifespan(_app):
1726
1823
  _heartbeat_stop.clear()
1727
1824
  hb_thread = _threading.Thread(target=_heartbeat_thread_fn, daemon=True, name="meshcode-heartbeat")
1728
1825
  hb_thread.start()
1729
- log.info(f"lifespan started — Realtime + heartbeat thread active for {AGENT_NAME}")
1826
+
1827
+ # Orphan watchdog — second layer of defense, independent of heartbeat
1828
+ # cadence. Polls every 2s for "PPID==1 AND stdin peer is dead" and
1829
+ # SIGKILLs self if so. Catches the close-terminal-while-MCP-is-paused
1830
+ # case faster than the 5-15s heartbeat tick (incident 2026-05-04).
1831
+ _orphan_watchdog_stop.clear()
1832
+ wd_thread = _threading.Thread(target=_orphan_watchdog_fn, daemon=True, name="meshcode-orphan-watchdog")
1833
+ wd_thread.start()
1834
+
1835
+ log.info(f"lifespan started — Realtime + heartbeat thread + orphan watchdog active for {AGENT_NAME}")
1730
1836
  # Enable session recording in backend.py (hot-reloadable)
1731
1837
  try:
1732
1838
  be.enable_recording(_get_api_key(), _PROJECT_ID, AGENT_NAME, _SESSION_ID)
@@ -1844,14 +1950,7 @@ async def meshcode_debug_sleep(seconds: int = 30) -> Dict[str, Any]:
1844
1950
  def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
1845
1951
  sensitive: bool = False, encrypted: bool = False,
1846
1952
  type: Optional[str] = None) -> Dict[str, Any]:
1847
- """Send message. Use "agent@meshwork" for cross-mesh. sensitive=True hides from exports. Pass encrypted=True for secrets/credentials (AES-256-GCM).
1848
-
1849
- Optional `type` argument (mig 233 typed catalog): one of report, ask,
1850
- proposal, status, blocker, decision, broadcast, ack, msg, dm, etc.
1851
- When passed, the SDK soft-validates the payload against the schema
1852
- in mc_message_schema(p_type) and logs a warning on mismatch — never
1853
- refuses the send (per memory feedback_meshcode_wait_mark_read).
1854
- """
1953
+ """Send message. Use "agent@meshwork" for cross-mesh. sensitive=True hides from exports. encrypted=True for secrets (AES-256-GCM). type= optional typed-catalog tag; soft-validates, never refuses."""
1855
1954
  if not to or not to.strip():
1856
1955
  return {"error": "recipient 'to' cannot be empty"}
1857
1956
  to = to.strip()
@@ -2360,20 +2459,7 @@ def _try_auto_claim_task() -> Optional[Dict[str, str]]:
2360
2459
  @mcp.tool()
2361
2460
  @with_working_status
2362
2461
  async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -> Dict[str, Any]:
2363
- """Block until a mesh message arrives or a task needs attention.
2364
-
2365
- INTERNAL LOOP: This function loops internally and only returns when
2366
- there is real work (message, task, or done signal). The agent NEVER
2367
- needs to decide whether to call meshcode_wait() again — it just stays
2368
- blocked here until something happens. This prevents agents from
2369
- accidentally using ScheduleWakeup or exiting the loop.
2370
-
2371
- Args:
2372
- timeout_seconds: Max wait time per poll cycle (default 20, hard cap 20).
2373
- Short cap keeps the outer tool call bounded so the user can press
2374
- ESC in Claude Code without killing the MCP server — the inner
2375
- loop continues polling across cycles at zero token cost.
2376
- """
2462
+ """Block until a mesh message arrives or a task needs attention. Loops internally; agent never decides to re-call. timeout_seconds: per-cycle cap (default+max 20)."""
2377
2463
  global _IN_WAIT, _CONSECUTIVE_IDLE_SECONDS, _LAST_SEEN_TS
2378
2464
 
2379
2465
  # PRODUCT RULE 1: If agent has open tasks, refuse to wait. Work first.
@@ -2409,12 +2495,17 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
2409
2495
  split = _split_messages(deduped)
2410
2496
  # Only refuse for real messages — ack-only batches should not block wait
2411
2497
  if split["messages"] or split["done_signals"]:
2412
- return {
2498
+ resp = {
2413
2499
  "refused": True,
2414
2500
  "reason": f"You have {split['count']} unread messages. Process them before waiting.",
2415
2501
  "got_message": True,
2416
2502
  **split,
2417
2503
  }
2504
+ # BE-S5.11: surface explicit must_exit when sleep authorized.
2505
+ if split["done_signals"]:
2506
+ resp["must_exit"] = True
2507
+ resp["exit_reason"] = "sleep authorization received — set status=sleeping and end session"
2508
+ return resp
2418
2509
  # Ack-only batch — fall through to wait loop
2419
2510
  except Exception:
2420
2511
  pass
@@ -2526,6 +2617,13 @@ async def meshcode_wait(timeout_seconds: int = 20, include_acks: bool = False) -
2526
2617
  result["memory_hints"] = _hints
2527
2618
  except Exception:
2528
2619
  pass
2620
+ # BE-S5.11: surface must_exit + exit_reason at top level of inner-loop
2621
+ # results too, so commander broadcasts (which arrive via the inner
2622
+ # path, not PRODUCT RULE 2) authorize wait-loop exit consistently.
2623
+ if isinstance(result, dict) and result.get("done_signals"):
2624
+ result.setdefault("must_exit", True)
2625
+ result.setdefault("exit_reason",
2626
+ "sleep authorization received — set status=sleeping and end session")
2529
2627
  return result
2530
2628
  finally:
2531
2629
  _IN_WAIT = False
@@ -2771,16 +2869,7 @@ def meshcode_done(reason: str) -> Dict[str, Any]:
2771
2869
  @mcp.tool()
2772
2870
  @with_working_status
2773
2871
  def meshcode_check(include_acks: bool = False, since: Optional[str] = None, mark_read: bool = False) -> Dict[str, Any]:
2774
- """Peek at inbox (non-destructive). Returns pending count + new messages.
2775
-
2776
- Args:
2777
- include_acks: Include ack messages in response.
2778
- since: ISO-8601 timestamp. Only return messages newer than this.
2779
- Use meshcode_remember("last_seen", ts) to persist across sessions.
2780
- mark_read: When True, consume messages (mark as read in DB) instead of
2781
- just peeking. Useful during boot when meshcode_wait() refuses
2782
- to run because of open tasks.
2783
- """
2872
+ """Peek inbox (non-destructive). since=ISO ts (older skipped). mark_read=True consumes (use during boot when wait refuses on open tasks)."""
2784
2873
  global _LAST_SEEN_TS
2785
2874
  pending = be.count_pending(_PROJECT_ID, AGENT_NAME, api_key=_get_api_key())
2786
2875
  # Peek at realtime buffer WITHOUT draining — check is non-destructive
@@ -4235,6 +4324,12 @@ def _auto_update() -> None:
4235
4324
  log.debug("[meshcode] Auto-update disabled (MESHCODE_AUTO_UPDATE=0)")
4236
4325
  return
4237
4326
  if os.environ.get("MESHCODE_UPDATED") == "1":
4327
+ # Sentinel was set by the parent process right before it execv'd
4328
+ # into us. Clear it now — single-use per upgrade. Without this, any
4329
+ # subprocess we spawn (or a downstream re-exec triggered by some
4330
+ # other code path) inherits MESHCODE_UPDATED=1 and skips the next
4331
+ # legitimate upgrade check. Per task 8d5eed7a.
4332
+ os.environ.pop("MESHCODE_UPDATED", None)
4238
4333
  return
4239
4334
 
4240
4335
  import subprocess
@@ -4293,29 +4388,82 @@ def _auto_update() -> None:
4293
4388
  log.debug(f"[meshcode] Auto-update failed: {e}")
4294
4389
  return
4295
4390
 
4296
- # 4. In MCP mode, NEVER re-exec it kills the stdio pipe to Claude Code.
4297
- # The new version will load on the next clean boot.
4298
- if os.environ.get("MESHCODE_MCP_SERVE") == "1":
4299
- print(f"[meshcode] Updated {current} {latest}. Will load on next boot (MCP mode — cannot restart).", file=sys.stderr)
4300
- return
4301
-
4302
- # CLI mode: safe to re-exec
4303
- print(f"[meshcode] Updated to {latest}, restarting...", file=sys.stderr)
4391
+ # 4. Re-exec so the upgraded code loads in THIS launch, not the next one.
4392
+ #
4393
+ # Unix (MCP + CLI mode): os.execv preserves the PID and stdio fds, so
4394
+ # Claude Code's pipe to us stays open. We're called from run_server()
4395
+ # BEFORE mcp.run() — no MCP protocol state has been negotiated yet, so
4396
+ # the handshake just happens with the new process image. This was
4397
+ # previously gated behind "NEVER re-exec in MCP mode" but field
4398
+ # evidence (Samuel 2026-05-04: "agente debe salir en la versión más
4399
+ # actualizada") shows the gate forces a two-launch upgrade cycle —
4400
+ # bug fixes shipped to PyPI never reach users on first relaunch.
4401
+ #
4402
+ # Windows: subprocess.Popen + sys.exit(0) on the parent DOES close the
4403
+ # stdio pipe to Claude Code (different PID). Keep the "next boot" path
4404
+ # there; ship a Windows execv replacement separately if it matters.
4405
+ print(f"[meshcode] Updated {current} → {latest}, re-exec'ing to load new code in this launch...", file=sys.stderr)
4304
4406
  os.environ["MESHCODE_UPDATED"] = "1"
4305
4407
  try:
4306
4408
  if sys.platform == "win32":
4307
- import subprocess as _sp_reexec
4308
- _sp_reexec.Popen([sys.executable] + sys.argv)
4309
- sys.exit(0)
4310
- else:
4311
- os.execv(sys.executable, [sys.executable] + sys.argv)
4409
+ # Windows can't preserve stdio across a re-exec. Defer to next boot.
4410
+ print(f"[meshcode] Windows MCP mode: new code loads on next boot.", file=sys.stderr)
4411
+ return
4412
+ os.execv(sys.executable, [sys.executable] + sys.argv)
4312
4413
  except Exception as e:
4313
4414
  log.debug(f"[meshcode] Re-exec failed: {e}, continuing with old version")
4314
4415
 
4315
4416
 
4417
+ def _check_boot_version_drift():
4418
+ """Compare pip-installed version (on disk) vs imported __version__.
4419
+
4420
+ Returns (installed, loaded) tuple if they differ, else None. Used as a
4421
+ canary for "auto-update wrote new code to disk but our running process
4422
+ is still the old version" — should be unreachable on Unix after the
4423
+ 2.10.93 execv fix, but the empirical check guards against:
4424
+ - path-precedence regressions (multiple meshcode installs)
4425
+ - Windows MCP mode (we deliberately defer there)
4426
+ - execv failures that fall through to the old version
4427
+ Best-effort: must not raise.
4428
+ """
4429
+ try:
4430
+ from importlib.metadata import version as _md_version
4431
+ from meshcode import __version__ as _loaded
4432
+ _installed = _md_version("meshcode")
4433
+ if _installed != _loaded:
4434
+ return (_installed, _loaded)
4435
+ except Exception:
4436
+ pass
4437
+ return None
4438
+
4439
+
4316
4440
  def run_server():
4317
4441
  """Start the MCP server on stdio (default for Claude Code)."""
4318
4442
  _auto_update()
4443
+ # Telemetry canary: surface any installed/loaded version drift loudly
4444
+ # to stderr so it shows up in Claude Code's MCP server output. The first
4445
+ # heartbeat carries the loaded version to mc_agents; this stderr line is
4446
+ # the human-visible signal that auto-update did NOT land cleanly.
4447
+ _drift = _check_boot_version_drift()
4448
+ if _drift is not None:
4449
+ _installed, _loaded = _drift
4450
+ print(
4451
+ f"[meshcode] WARN boot_version_drift: installed={_installed} loaded={_loaded} "
4452
+ f"— pip has a newer build on disk than what's running in this process. "
4453
+ f"Restart should pick it up. If it persists, check sys.path precedence.",
4454
+ file=sys.stderr,
4455
+ )
4456
+ # Stash for the heartbeat thread to relay to commander on first beat.
4457
+ os.environ["_MESHCODE_BOOT_DRIFT"] = f"{_installed}->{_loaded}"
4458
+ if sys.platform == "win32":
4459
+ # Windows can't execv-preserve stdio; auto-update defers to next boot.
4460
+ # Always announce so Windows users know why their first launch may
4461
+ # still show an older version even with PyPI updated.
4462
+ print(
4463
+ "[meshcode] note: Windows defers auto-update to next launch "
4464
+ "(execv preserves Claude Code's stdio pipe only on Unix).",
4465
+ file=sys.stderr,
4466
+ )
4319
4467
  print(
4320
4468
  f"[meshcode-mcp] Starting server for {AGENT_NAME}@{PROJECT_NAME}",
4321
4469
  file=sys.stderr,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.91
3
+ Version: 2.10.94
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -422,6 +422,11 @@ $env:MESHCODE_PROJECT_ID="your-project-uuid" # Windows PowerShell
422
422
 
423
423
  You can also run `meshcode doctor` (v2.10.41+) to diagnose stale paths, missing dependencies, and config issues across all your workspaces.
424
424
 
425
+ **9b. Windows: first relaunch shows the old version after a PyPI upgrade**
426
+ Auto-update on Unix uses `os.execv` to swap the running Python image in-place — one launch, latest code (v2.10.93+). On Windows this isn't possible: an `os.execv` replacement breaks Claude Code's stdio pipe to the MCP child (the new process has a different PID). Meshcode therefore installs the new version but defers the swap to the **next** launch on Windows. Expect: `[meshcode] note: Windows defers auto-update to next launch ...` on stderr, then close and re-open Claude Code once to get the new code. To force-load now, run `pip install --upgrade meshcode` manually before launching Claude Code.
427
+
428
+ If the dashboard tags an agent as having `boot_version_drift`, that means pip wrote the new version to disk but our running process is still the old one — typically benign on Windows (next launch fixes it) and worth investigating on Unix (path-precedence regression).
429
+
425
430
  **10. `MCP server failed to start` in the Claude Code `/mcp` panel**
426
431
  Run `claude --debug` to see the underlying error. Nine times out of ten it's a stale or missing key — run `meshcode login mc_xxx` again.
427
432