meshcode 2.10.93__tar.gz → 2.10.95__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 (207) hide show
  1. {meshcode-2.10.93/meshcode.egg-info → meshcode-2.10.95}/PKG-INFO +6 -1
  2. {meshcode-2.10.93 → meshcode-2.10.95}/README.md +5 -0
  3. meshcode-2.10.95/meshcode/__init__.py +82 -0
  4. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/meshcode_mcp/backend.py +92 -16
  5. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/meshcode_mcp/server.py +124 -19
  6. meshcode-2.10.95/meshcode.egg-info/PKG-INFO +446 -0
  7. meshcode-2.10.93/PKG-INFO → meshcode-2.10.95/meshcode.egg-info/PKG-INFO 2 +5 -0
  8. meshcode-2.10.93/meshcode.egg-info/SOURCES.txt → meshcode-2.10.95/meshcode.egg-info/SOURCES 2.txt +1 -0
  9. meshcode-2.10.95/meshcode.egg-info/SOURCES.txt +204 -0
  10. meshcode-2.10.95/meshcode.egg-info/dependency_links.txt +1 -0
  11. meshcode-2.10.95/meshcode.egg-info/entry_points.txt +3 -0
  12. meshcode-2.10.95/meshcode.egg-info/requires.txt +5 -0
  13. meshcode-2.10.95/meshcode.egg-info/top_level.txt +4 -0
  14. {meshcode-2.10.93 → meshcode-2.10.95}/pyproject.toml +1 -1
  15. meshcode-2.10.95/tests/test_auto_update_hardening.py +238 -0
  16. meshcode-2.10.93/meshcode/__init__.py +0 -82
  17. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/ascii_art.py +0 -0
  18. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/cli.py +0 -0
  19. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/comms_v4.py +0 -0
  20. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/compat.py +0 -0
  21. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/error_hints.py +0 -0
  22. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/exceptions.py +0 -0
  23. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/invites.py +0 -0
  24. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/launcher.py +0 -0
  25. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/launcher_install.py +0 -0
  26. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/meshcode_mcp/__init__.py +0 -0
  27. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/meshcode_mcp/__main__.py +0 -0
  28. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/meshcode_mcp/realtime.py +0 -0
  29. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/meshcode_mcp/test_backend.py +0 -0
  30. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  31. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  32. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/preferences.py +0 -0
  33. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/protocol_v2.py +0 -0
  34. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/quickstart.py +0 -0
  35. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/run_agent.py +0 -0
  36. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/secrets.py +0 -0
  37. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/self_update.py +0 -0
  38. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/setup_clients.py +0 -0
  39. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/supervisor.py +0 -0
  40. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode/upload.py +0 -0
  41. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/comms_v4.py +0 -0
  42. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  43. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  44. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/cli.py +0 -0
  45. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  46. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/compat.py +0 -0
  47. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  48. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  49. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/invites.py +0 -0
  50. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  51. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  52. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  53. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  54. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  55. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  56. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  57. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  58. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  59. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  60. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  61. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  62. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  63. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  64. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  65. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  66. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  67. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  68. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/meshcode/upload.py +0 -0
  69. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  70. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_core.py +0 -0
  71. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  72. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  73. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  74. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  75. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  76. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  77. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  78. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  79. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  80. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  81. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  82. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  83. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  84. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  85. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  86. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  87. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  88. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  89. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  90. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  91. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  92. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  93. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  94. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  95. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  96. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  97. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  98. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  99. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  100. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  101. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  102. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  103. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  104. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  105. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  106. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  107. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  108. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  109. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/comms_v4.py +0 -0
  110. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  111. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  112. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/cli.py +0 -0
  113. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  114. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/compat.py +0 -0
  115. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  116. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  117. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/invites.py +0 -0
  118. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  119. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  120. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  121. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  122. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  123. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  124. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  125. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  126. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  127. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  128. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  129. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  130. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  131. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  132. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  133. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  134. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  135. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  136. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/meshcode/upload.py +0 -0
  137. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  138. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_core.py +0 -0
  139. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  140. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  141. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  142. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  143. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  144. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  145. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  146. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  147. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  148. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  149. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  150. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/comms_v4.py +0 -0
  151. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  152. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  153. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  154. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  155. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  156. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  157. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  158. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  159. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  160. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  161. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  162. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  163. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  164. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  165. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  166. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  167. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  168. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  169. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  170. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  171. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  172. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  173. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  174. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  175. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  176. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  177. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  178. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  179. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_core.py +0 -0
  180. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  181. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  182. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  183. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  184. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  185. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  186. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  187. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  188. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  189. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  190. {meshcode-2.10.93 → meshcode-2.10.95}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  191. /meshcode-2.10.93/meshcode.egg-info/dependency_links.txt → /meshcode-2.10.95/meshcode.egg-info/dependency_links 2.txt +0 -0
  192. /meshcode-2.10.93/meshcode.egg-info/entry_points.txt → /meshcode-2.10.95/meshcode.egg-info/entry_points 2.txt +0 -0
  193. /meshcode-2.10.93/meshcode.egg-info/requires.txt → /meshcode-2.10.95/meshcode.egg-info/requires 2.txt +0 -0
  194. /meshcode-2.10.93/meshcode.egg-info/top_level.txt → /meshcode-2.10.95/meshcode.egg-info/top_level 2.txt +0 -0
  195. {meshcode-2.10.93 → meshcode-2.10.95}/setup.cfg +0 -0
  196. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_core.py +0 -0
  197. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_cross_agent_messaging.py +0 -0
  198. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_esc_deaf_state.py +0 -0
  199. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_exceptions.py +0 -0
  200. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_mark_read_batch.py +0 -0
  201. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_migration_integrity.py +0 -0
  202. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_realtime_event_freshness.py +0 -0
  203. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_rls_cross_tenant.py +0 -0
  204. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_rpc_migrations.py +0 -0
  205. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_security_regressions.py +0 -0
  206. {meshcode-2.10.93 → meshcode-2.10.95}/tests/test_sentinel.py +0 -0
  207. {meshcode-2.10.93 → meshcode-2.10.95}/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.93
3
+ Version: 2.10.95
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.95"
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
+ ]
@@ -826,14 +826,29 @@ def read_inbox(project_id: str, agent: str, mark_read: bool = True, api_key: Opt
826
826
  for m in messages:
827
827
  p = m.get("payload")
828
828
  if isinstance(p, dict) and "_encrypted" in p:
829
- if mesh_key is None:
830
- mesh_key = get_mesh_key(api_key, project_id)
831
- if mesh_key:
832
- msg_aad = p.get("_aad", project_id)
833
- decrypted = decrypt_payload(p["_encrypted"], mesh_key, aad=msg_aad)
834
- if decrypted is not None:
835
- m["payload"] = decrypted
836
- m["_was_encrypted"] = True
829
+ msg_aad = p.get("_aad", project_id)
830
+ from_project_marker = p.get("_from_project")
831
+ decrypted = None
832
+ # Cross-mesh: payload tagged with sender's project.
833
+ # Fetch per-link shared key (NOT local mesh key).
834
+ if from_project_marker and from_project_marker != project_id:
835
+ link_key = get_link_shared_key(api_key, project_id, from_project_marker)
836
+ if link_key:
837
+ decrypted = decrypt_payload(p["_encrypted"], link_key, aad=msg_aad)
838
+ # Decrypt fail → key rotation; refetch once.
839
+ if decrypted is None:
840
+ link_key = get_link_shared_key(api_key, project_id, from_project_marker, bypass_cache=True)
841
+ if link_key:
842
+ decrypted = decrypt_payload(p["_encrypted"], link_key, aad=msg_aad)
843
+ # In-mesh OR no _from_project marker (legacy): local key.
844
+ if decrypted is None:
845
+ if mesh_key is None:
846
+ mesh_key = get_mesh_key(api_key, project_id)
847
+ if mesh_key:
848
+ decrypted = decrypt_payload(p["_encrypted"], mesh_key, aad=msg_aad)
849
+ if decrypted is not None:
850
+ m["payload"] = decrypted
851
+ m["_was_encrypted"] = True
837
852
  if mark_read and messages:
838
853
  import datetime as _dt
839
854
  now = _dt.datetime.now(_dt.timezone.utc)
@@ -872,14 +887,25 @@ def read_inbox(project_id: str, agent: str, mark_read: bool = True, api_key: Opt
872
887
  for m in messages:
873
888
  p = m.get("payload")
874
889
  if isinstance(p, dict) and "_encrypted" in p:
875
- if mesh_key is None:
876
- mesh_key = get_mesh_key(api_key, project_id)
877
- if mesh_key:
878
- msg_aad = p.get("_aad", project_id)
879
- decrypted = decrypt_payload(p["_encrypted"], mesh_key, aad=msg_aad)
880
- if decrypted is not None:
881
- m["payload"] = decrypted
882
- m["_was_encrypted"] = True
890
+ msg_aad = p.get("_aad", project_id)
891
+ from_project_marker = p.get("_from_project")
892
+ decrypted = None
893
+ if from_project_marker and from_project_marker != project_id:
894
+ link_key = get_link_shared_key(api_key, project_id, from_project_marker)
895
+ if link_key:
896
+ decrypted = decrypt_payload(p["_encrypted"], link_key, aad=msg_aad)
897
+ if decrypted is None:
898
+ link_key = get_link_shared_key(api_key, project_id, from_project_marker, bypass_cache=True)
899
+ if link_key:
900
+ decrypted = decrypt_payload(p["_encrypted"], link_key, aad=msg_aad)
901
+ if decrypted is None:
902
+ if mesh_key is None:
903
+ mesh_key = get_mesh_key(api_key, project_id)
904
+ if mesh_key:
905
+ decrypted = decrypt_payload(p["_encrypted"], mesh_key, aad=msg_aad)
906
+ if decrypted is not None:
907
+ m["payload"] = decrypted
908
+ m["_was_encrypted"] = True
883
909
  if mark_read and messages:
884
910
  for m in messages:
885
911
  marked = False
@@ -983,6 +1009,56 @@ def get_mesh_key(api_key: str, project_id: str) -> Optional[str]:
983
1009
  return None
984
1010
 
985
1011
 
1012
+ # Per-link shared key cache (cross-mesh encrypted msgs). Keyed by
1013
+ # (my_project, other_project) tuple. Populated on demand when an encrypted
1014
+ # cross-mesh message arrives. Cache invalidates on decrypt failure (rotation).
1015
+ _link_key_cache: Dict[tuple, tuple] = {} # (my_proj, other_proj) -> (hex_key, expiry_ts)
1016
+ _LINK_KEY_CACHE_TTL = 600
1017
+ _LINK_KEY_CACHE_MAX = 64
1018
+
1019
+
1020
+ def get_link_shared_key(api_key: str, my_project: str, other_project: str,
1021
+ bypass_cache: bool = False) -> Optional[str]:
1022
+ """Retrieve the per-link symmetric encryption key (hex-encoded AES-256).
1023
+
1024
+ Used for cross-mesh encrypted messages — backend `mc_get_link_shared_key`
1025
+ returns a key SHARED by both ends of the link, so encrypt-on-sender +
1026
+ decrypt-on-recipient roundtrips cleanly. `my_project` and `other_project`
1027
+ accept either project name OR project_id; the RPC resolves both.
1028
+
1029
+ Set `bypass_cache=True` after a decrypt failure to handle key rotation
1030
+ (`mc_rotate_link_shared_key`).
1031
+ """
1032
+ cache_key = (my_project, other_project)
1033
+ if not bypass_cache:
1034
+ entry = _link_key_cache.get(cache_key)
1035
+ if entry and entry[1] > _time.time():
1036
+ return entry[0]
1037
+ _link_key_cache.pop(cache_key, None)
1038
+ result = sb_rpc("mc_get_link_shared_key", {
1039
+ "p_api_key": api_key,
1040
+ "p_my_project": my_project,
1041
+ "p_other_project": other_project,
1042
+ })
1043
+ if isinstance(result, dict) and result.get("ok"):
1044
+ try:
1045
+ import base64 as _b64
1046
+ hex_key = _b64.b64decode(result["shared_encryption_key_b64"]).hex()
1047
+ except Exception:
1048
+ return None
1049
+ if len(_link_key_cache) >= _LINK_KEY_CACHE_MAX:
1050
+ now = _time.time()
1051
+ expired = [k for k, v in _link_key_cache.items() if v[1] <= now]
1052
+ for k in expired:
1053
+ del _link_key_cache[k]
1054
+ if len(_link_key_cache) >= _LINK_KEY_CACHE_MAX:
1055
+ oldest = min(_link_key_cache, key=lambda k: _link_key_cache[k][1])
1056
+ del _link_key_cache[oldest]
1057
+ _link_key_cache[cache_key] = (hex_key, _time.time() + _LINK_KEY_CACHE_TTL)
1058
+ return hex_key
1059
+ return None
1060
+
1061
+
986
1062
  def encrypt_payload(payload: Dict, hex_key: str, aad: Optional[str] = None) -> str:
987
1063
  """Encrypt a JSON payload using AES-256-GCM with optional AAD.
988
1064
 
@@ -1747,6 +1747,25 @@ async def lifespan(_app):
1747
1747
  be.set_status(_PROJECT_ID, AGENT_NAME, "idle", "MCP session active", api_key=_get_api_key())
1748
1748
  log.info(f"[meshcode] Agent {AGENT_NAME} online — initial heartbeat sent")
1749
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}")
1750
1769
  break
1751
1770
  except Exception as e:
1752
1771
  log.warning(f"initial heartbeat attempt {_attempt+1} failed: {e}")
@@ -1987,23 +2006,36 @@ def meshcode_send(to: str, message: Any, in_reply_to: Optional[str] = None,
1987
2006
  return {"error": "sensitive messages must use encrypted=True for cross-mesh"}
1988
2007
  api_key = _get_api_key()
1989
2008
 
1990
- # Bridge re-encryption: encrypt payload with the TARGET mesh key so
1991
- # the receiving agent decrypts normally. Uses mc_get_cross_mesh_key
1992
- # RPC which validates the link exists before returning the key.
2009
+ # Cross-mesh encryption: per-link symmetric AES-256-GCM key (backend
2010
+ # commit cc6a988 / mig 2026-05-04). Old `mc_get_cross_mesh_key`
2011
+ # returned target's per-mesh key, which empirically failed to decrypt
2012
+ # on recipient (incident 2026-05-04 chief@mesh-dev). New
2013
+ # `mc_get_link_shared_key` returns a key SHARED by sender + recipient
2014
+ # via the link record — both sides resolve to the same bytes, so
2015
+ # encryption roundtrips cleanly. Tag payload with _from_project so the
2016
+ # recipient knows to fetch the link key (not its local mesh key) on
2017
+ # decrypt.
1993
2018
  if encrypted:
1994
- key_result = be.sb_rpc("mc_get_cross_mesh_key", {
2019
+ key_result = be.sb_rpc("mc_get_link_shared_key", {
1995
2020
  "p_api_key": api_key,
1996
- "p_source_project": PROJECT_NAME,
1997
- "p_target_project": target_meshwork,
1998
- "p_agent_name": AGENT_NAME,
2021
+ "p_my_project": PROJECT_NAME,
2022
+ "p_other_project": target_meshwork,
1999
2023
  })
2000
2024
  if not isinstance(key_result, dict) or not key_result.get("ok"):
2001
- err = key_result.get("error", "unknown") if isinstance(key_result, dict) else "RPC failed"
2025
+ err = key_result.get("error_code") or key_result.get("error", "unknown") if isinstance(key_result, dict) else "RPC failed"
2002
2026
  return {"error": f"cross-mesh encryption failed: {err}"}
2003
- tgt_key = key_result["key"]
2027
+ link_key_b64 = key_result["shared_encryption_key_b64"]
2028
+ link_id = key_result.get("link_id", "")
2004
2029
  tgt_project_id = key_result.get("target_project_id", "")
2005
- encrypted_data = be.encrypt_payload(payload, tgt_key, aad=tgt_project_id)
2006
- payload = {"_encrypted": encrypted_data, "_aad": tgt_project_id}
2030
+ import base64 as _b64
2031
+ link_key_hex = _b64.b64decode(link_key_b64).hex()
2032
+ encrypted_data = be.encrypt_payload(payload, link_key_hex, aad=link_id or tgt_project_id)
2033
+ payload = {
2034
+ "_encrypted": encrypted_data,
2035
+ "_aad": link_id or tgt_project_id,
2036
+ "_from_project": PROJECT_NAME,
2037
+ "_link_id": link_id,
2038
+ }
2007
2039
 
2008
2040
  result = be.sb_rpc("mc_send_cross_mesh", {
2009
2041
  "p_api_key": api_key,
@@ -2669,20 +2701,40 @@ def _mark_realtime_msgs_read_in_db(messages: List[Dict[str, Any]]) -> None:
2669
2701
  async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[str, Any]:
2670
2702
 
2671
2703
  def _return_from_buffered(buffered: List[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
2672
- # Auto-decrypt encrypted payloads
2704
+ # Auto-decrypt encrypted payloads. Cross-mesh msgs carry _from_project
2705
+ # marker → use per-link shared key. In-mesh → local mesh key.
2673
2706
  _mesh_key = None
2707
+ _api_key = _get_api_key()
2674
2708
  for msg in buffered:
2675
2709
  p = msg.get("payload")
2676
2710
  if isinstance(p, dict) and "_encrypted" in p:
2677
- if _mesh_key is None:
2711
+ _msg_aad = p.get("_aad", PROJECT_NAME)
2712
+ _from_proj = p.get("_from_project")
2713
+ _decrypted = None
2714
+ if _from_proj and _from_proj != PROJECT_NAME:
2678
2715
  try:
2679
- _mesh_key = be.get_mesh_key(_get_api_key(), _PROJECT_ID) or ""
2716
+ _link_key = be.get_link_shared_key(_api_key, PROJECT_NAME, _from_proj)
2717
+ if _link_key:
2718
+ _decrypted = be.decrypt_payload(p["_encrypted"], _link_key, aad=_msg_aad)
2719
+ if _decrypted is None:
2720
+ _link_key = be.get_link_shared_key(_api_key, PROJECT_NAME, _from_proj, bypass_cache=True)
2721
+ if _link_key:
2722
+ _decrypted = be.decrypt_payload(p["_encrypted"], _link_key, aad=_msg_aad)
2680
2723
  except Exception:
2681
- _mesh_key = ""
2682
- if _mesh_key:
2683
- decrypted = be.decrypt_payload(p["_encrypted"], _mesh_key)
2684
- if decrypted is not None:
2685
- msg["payload"] = decrypted
2724
+ pass
2725
+ if _decrypted is None:
2726
+ if _mesh_key is None:
2727
+ try:
2728
+ _mesh_key = be.get_mesh_key(_api_key, _PROJECT_ID) or ""
2729
+ except Exception:
2730
+ _mesh_key = ""
2731
+ if _mesh_key:
2732
+ try:
2733
+ _decrypted = be.decrypt_payload(p["_encrypted"], _mesh_key, aad=_msg_aad)
2734
+ except Exception:
2735
+ _decrypted = None
2736
+ if _decrypted is not None:
2737
+ msg["payload"] = _decrypted
2686
2738
  deduped = _filter_and_mark(buffered)
2687
2739
  if not deduped:
2688
2740
  return None
@@ -4305,6 +4357,12 @@ def _auto_update() -> None:
4305
4357
  log.debug("[meshcode] Auto-update disabled (MESHCODE_AUTO_UPDATE=0)")
4306
4358
  return
4307
4359
  if os.environ.get("MESHCODE_UPDATED") == "1":
4360
+ # Sentinel was set by the parent process right before it execv'd
4361
+ # into us. Clear it now — single-use per upgrade. Without this, any
4362
+ # subprocess we spawn (or a downstream re-exec triggered by some
4363
+ # other code path) inherits MESHCODE_UPDATED=1 and skips the next
4364
+ # legitimate upgrade check. Per task 8d5eed7a.
4365
+ os.environ.pop("MESHCODE_UPDATED", None)
4308
4366
  return
4309
4367
 
4310
4368
  import subprocess
@@ -4389,9 +4447,56 @@ def _auto_update() -> None:
4389
4447
  log.debug(f"[meshcode] Re-exec failed: {e}, continuing with old version")
4390
4448
 
4391
4449
 
4450
+ def _check_boot_version_drift():
4451
+ """Compare pip-installed version (on disk) vs imported __version__.
4452
+
4453
+ Returns (installed, loaded) tuple if they differ, else None. Used as a
4454
+ canary for "auto-update wrote new code to disk but our running process
4455
+ is still the old version" — should be unreachable on Unix after the
4456
+ 2.10.93 execv fix, but the empirical check guards against:
4457
+ - path-precedence regressions (multiple meshcode installs)
4458
+ - Windows MCP mode (we deliberately defer there)
4459
+ - execv failures that fall through to the old version
4460
+ Best-effort: must not raise.
4461
+ """
4462
+ try:
4463
+ from importlib.metadata import version as _md_version
4464
+ from meshcode import __version__ as _loaded
4465
+ _installed = _md_version("meshcode")
4466
+ if _installed != _loaded:
4467
+ return (_installed, _loaded)
4468
+ except Exception:
4469
+ pass
4470
+ return None
4471
+
4472
+
4392
4473
  def run_server():
4393
4474
  """Start the MCP server on stdio (default for Claude Code)."""
4394
4475
  _auto_update()
4476
+ # Telemetry canary: surface any installed/loaded version drift loudly
4477
+ # to stderr so it shows up in Claude Code's MCP server output. The first
4478
+ # heartbeat carries the loaded version to mc_agents; this stderr line is
4479
+ # the human-visible signal that auto-update did NOT land cleanly.
4480
+ _drift = _check_boot_version_drift()
4481
+ if _drift is not None:
4482
+ _installed, _loaded = _drift
4483
+ print(
4484
+ f"[meshcode] WARN boot_version_drift: installed={_installed} loaded={_loaded} "
4485
+ f"— pip has a newer build on disk than what's running in this process. "
4486
+ f"Restart should pick it up. If it persists, check sys.path precedence.",
4487
+ file=sys.stderr,
4488
+ )
4489
+ # Stash for the heartbeat thread to relay to commander on first beat.
4490
+ os.environ["_MESHCODE_BOOT_DRIFT"] = f"{_installed}->{_loaded}"
4491
+ if sys.platform == "win32":
4492
+ # Windows can't execv-preserve stdio; auto-update defers to next boot.
4493
+ # Always announce so Windows users know why their first launch may
4494
+ # still show an older version even with PyPI updated.
4495
+ print(
4496
+ "[meshcode] note: Windows defers auto-update to next launch "
4497
+ "(execv preserves Claude Code's stdio pipe only on Unix).",
4498
+ file=sys.stderr,
4499
+ )
4395
4500
  print(
4396
4501
  f"[meshcode-mcp] Starting server for {AGENT_NAME}@{PROJECT_NAME}",
4397
4502
  file=sys.stderr,