meshcode 2.11.85__tar.gz → 2.11.87__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 (245) hide show
  1. {meshcode-2.11.85 → meshcode-2.11.87}/PKG-INFO +1 -1
  2. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/_session_handoff_template.py +51 -0
  4. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/server.py +49 -0
  5. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/setup_clients.py +66 -1
  6. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/SOURCES.txt +0 -3
  8. {meshcode-2.11.85 → meshcode-2.11.87}/pyproject.toml +1 -1
  9. meshcode-2.11.85/meshcode/claude_update 2.py +0 -258
  10. meshcode-2.11.85/meshcode/hostd 2.py +0 -849
  11. meshcode-2.11.85/meshcode/up 2.py +0 -257
  12. {meshcode-2.11.85 → meshcode-2.11.87}/README.md +0 -0
  13. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/__main__.py +0 -0
  14. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/_stop_hook_template.py +0 -0
  15. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/ascii_art.py +0 -0
  16. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/atomic_push.py +0 -0
  17. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/claude_update.py +0 -0
  18. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/cli.py +0 -0
  19. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/comms_v4.py +0 -0
  20. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/compat.py +0 -0
  21. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/daemon.py +0 -0
  22. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/date_parse.py +0 -0
  23. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/doctor.py +0 -0
  24. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/error_hints.py +0 -0
  25. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/exceptions.py +0 -0
  26. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/hostd.py +0 -0
  27. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/invites.py +0 -0
  28. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/launcher.py +0 -0
  29. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/launcher_install.py +0 -0
  30. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/__init__.py +0 -0
  31. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/__main__.py +0 -0
  32. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/backend.py +0 -0
  33. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/realtime.py +0 -0
  34. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  35. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_backend.py +0 -0
  36. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  37. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  38. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  39. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  40. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  41. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/preferences.py +0 -0
  42. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/protocol_handler.py +0 -0
  43. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/protocol_v2.py +0 -0
  44. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/quickstart.py +0 -0
  45. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/rpc_allowlist.py +0 -0
  46. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/run_agent.py +0 -0
  47. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/scripts/check_secrets.py +0 -0
  48. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/scripts/race_rate_harness.py +0 -0
  49. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/secrets.py +0 -0
  50. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/self_update.py +0 -0
  51. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/supervisor.py +0 -0
  52. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/up.py +0 -0
  53. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode/upload.py +0 -0
  54. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/comms_v4.py +0 -0
  55. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  56. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  57. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/cli.py +0 -0
  58. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  59. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/compat.py +0 -0
  60. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  61. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  62. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/invites.py +0 -0
  63. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  64. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  65. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  66. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  67. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  68. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  69. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  70. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  71. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  72. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  73. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  74. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  75. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  76. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  77. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  78. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  79. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  80. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  81. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/meshcode/upload.py +0 -0
  82. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  83. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_core.py +0 -0
  84. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  85. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  86. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  87. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  88. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  89. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  90. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  91. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  92. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  93. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  94. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  95. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  96. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  97. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  98. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  99. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  100. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  101. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  102. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  103. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  104. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  105. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  106. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  107. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  108. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  109. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  110. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  111. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  112. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  113. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  114. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  115. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  116. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  117. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  118. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  119. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  120. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  121. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  122. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/comms_v4.py +0 -0
  123. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  124. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  125. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/cli.py +0 -0
  126. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  127. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/compat.py +0 -0
  128. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  129. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  130. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/invites.py +0 -0
  131. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  132. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  133. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  134. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  135. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  136. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  137. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  138. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  139. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  140. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  141. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  142. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  143. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  144. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  145. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  146. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  147. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  148. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  149. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/meshcode/upload.py +0 -0
  150. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  151. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_core.py +0 -0
  152. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  153. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  154. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  155. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  156. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  157. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  158. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  159. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  160. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  161. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  162. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  163. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/comms_v4.py +0 -0
  164. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  165. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  166. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  167. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  168. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  169. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  170. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  171. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  172. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  173. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  174. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  175. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  176. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  177. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  178. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  179. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  180. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  181. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  182. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  183. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  184. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  185. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  186. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  187. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  188. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  189. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  190. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  191. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  192. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_core.py +0 -0
  193. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  194. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  195. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  196. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  197. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  198. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  199. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  200. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  201. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  202. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  203. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  204. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/dependency_links.txt +0 -0
  205. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/entry_points.txt +0 -0
  206. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/requires.txt +0 -0
  207. {meshcode-2.11.85 → meshcode-2.11.87}/meshcode.egg-info/top_level.txt +0 -0
  208. {meshcode-2.11.85 → meshcode-2.11.87}/setup.cfg +0 -0
  209. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_auto_update_hardening.py +0 -0
  210. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_closegap_1.py +0 -0
  211. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_closegap_2.py +0 -0
  212. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_closegap_3.py +0 -0
  213. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_prompt_inject 2.py +0 -0
  214. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_autonomous_prompt_inject.py +0 -0
  215. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_boot_bug_regression.py +0 -0
  216. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_color_truecolor.py +0 -0
  217. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_core.py +0 -0
  218. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_cross_agent_messaging.py +0 -0
  219. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_date_parse.py +0 -0
  220. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_doctor.py +0 -0
  221. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_epistemic_v1_python_sdk.py +0 -0
  222. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  223. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_esc_deaf_state.py +0 -0
  224. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_exceptions.py +0 -0
  225. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_file_upload.py +0 -0
  226. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_init_device_code.py +0 -0
  227. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_install_guard.py +0 -0
  228. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_lease_sigterm_release.py +0 -0
  229. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_mark_read_batch.py +0 -0
  230. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_marketplace_ratings.py +0 -0
  231. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_migration_integrity.py +0 -0
  232. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_realtime_event_freshness.py +0 -0
  233. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_rls_cross_tenant.py +0 -0
  234. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_rpc_grants.py +0 -0
  235. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_rpc_migrations.py +0 -0
  236. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_run_agent_dry_run.py +0 -0
  237. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_run_agent_no_server_import.py +0 -0
  238. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_security_regressions.py +0 -0
  239. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_self_update_user_site.py +0 -0
  240. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_sentinel.py +0 -0
  241. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_setup_path.py +0 -0
  242. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_sleep_signals.py +0 -0
  243. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_status_enum_coverage.py +0 -0
  244. {meshcode-2.11.85 → meshcode-2.11.87}/tests/test_stay_on_loop_hook.py +0 -0
  245. {meshcode-2.11.85 → meshcode-2.11.87}/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.85
3
+ Version: 2.11.87
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.85"
2
+ __version__ = "2.11.87"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -113,6 +113,54 @@ def _extract_tail(transcript_path: str):
113
113
  return rows[-MAX_TURNS:]
114
114
 
115
115
 
116
+ def _request_recycle_if_marked(project_dir) -> None:
117
+ """CTX-CLOSE-RELAUNCH (task 400fc536): commander-tier sessions ask the
118
+ server to recycle (close+relaunch fresh) at the next task-edge, right after
119
+ the handoff is snapshotted. Gated by the scaffold-baked
120
+ .claude/meshcode_hook_ctx.json recycle_on_compact flag (commander-only v1).
121
+
122
+ Best-effort in every dimension: missing marker/flag, missing creds, import
123
+ failure, network error -> silently skip. handoff.json is already written, so
124
+ a context-recycle still relaunches WITH context; and the actual exit is the
125
+ server's call (mc_consume_recycle at a task boundary), never this hook.
126
+ """
127
+ try:
128
+ ctx_path = project_dir / ".claude" / "meshcode_hook_ctx.json"
129
+ if not ctx_path.exists():
130
+ return
131
+ if not json.loads(ctx_path.read_text(encoding="utf-8")).get("recycle_on_compact"):
132
+ return # non-commander -> in-place autocompact, no recycle
133
+ mcp = json.loads((project_dir / ".mcp.json").read_text(encoding="utf-8"))
134
+ env = (next(iter((mcp.get("mcpServers") or {}).values()), {}) or {}).get("env", {}) or {}
135
+ url = env.get("SUPABASE_URL"); key = env.get("SUPABASE_KEY")
136
+ pid = env.get("MESHCODE_PROJECT_ID"); agent = env.get("MESHCODE_AGENT")
137
+ if not (url and key and pid and agent):
138
+ return
139
+ api_key = os.environ.get("MESHCODE_API_KEY")
140
+ if not api_key:
141
+ try:
142
+ import importlib
143
+ api_key = importlib.import_module("meshcode.secrets").get_api_key(
144
+ profile=env.get("MESHCODE_KEYCHAIN_PROFILE") or "default")
145
+ except Exception:
146
+ api_key = None
147
+ if not api_key:
148
+ return
149
+ import urllib.request as _u
150
+ body = json.dumps({
151
+ "p_api_key": api_key, "p_project_id": pid,
152
+ "p_agent_name": agent, "p_allow_busy": True, # flag-now; exit deferred to wait-loop
153
+ }).encode("utf-8")
154
+ req = _u.Request(
155
+ url.rstrip("/") + "/rest/v1/rpc/mc_request_recycle",
156
+ data=body, method="POST",
157
+ headers={"apikey": key, "Authorization": "Bearer " + key,
158
+ "Content-Type": "application/json"})
159
+ _u.urlopen(req, timeout=5).read() # best-effort; ignore result per backend contract
160
+ except Exception as e: # noqa: BLE001 — never block compaction
161
+ sys.stderr.write(f"[session_handoff_write] recycle-request skipped: {e}\\n")
162
+
163
+
116
164
  def main() -> int:
117
165
  try:
118
166
  raw = sys.stdin.read()
@@ -137,6 +185,9 @@ def main() -> int:
137
185
  tmp.replace(d / "handoff.json")
138
186
  except OSError as e:
139
187
  sys.stderr.write(f"[session_handoff_write] skipped: {e}\\n")
188
+ # CTX-CLOSE-RELAUNCH (task 400fc536): now that the thread is snapshotted,
189
+ # commander-tier sessions ask the server to recycle at the next task-edge.
190
+ _request_recycle_if_marked(_project_dir())
140
191
  return 0
141
192
 
142
193
 
@@ -6041,6 +6041,55 @@ def meshcode_add_agent(name: str, role: str = "", autonomous: bool = False) -> D
6041
6041
  return result or {"error": "failed to add agent"}
6042
6042
 
6043
6043
 
6044
+ @mcp.tool()
6045
+ @with_working_status
6046
+ def meshcode_agent_launch(name: str, headless: bool = False) -> Dict[str, Any]:
6047
+ """Launch (or restart) another agent in YOUR meshwork — open it on command
6048
+ or by your own judgment (e.g. spin an agent up when task load rises).
6049
+
6050
+ COMMANDER-AGENT-LIFECYCLE (task 11feedd1). Sets desired_state=running so the
6051
+ host daemon spawns the agent; visible by default, headless opt-in. Only the
6052
+ project's commander agent (or the human owner) may power other agents, and
6053
+ only within their own meshwork — enforced server-side by
6054
+ mc_agent_power_as_agent (api_key identity + scope_meshwork_id check).
6055
+
6056
+ Args:
6057
+ name: target agent name in your meshwork.
6058
+ headless: True = run in the background with no visible window;
6059
+ default False = visible focused window.
6060
+ """
6061
+ return be.sb_rpc("mc_agent_power_as_agent", {
6062
+ "p_api_key": _get_api_key(),
6063
+ "p_project_id": _PROJECT_ID,
6064
+ "p_agent": name,
6065
+ "p_state": "running",
6066
+ "p_headless": bool(headless),
6067
+ })
6068
+
6069
+
6070
+ @mcp.tool()
6071
+ @with_working_status
6072
+ def meshcode_agent_stop(name: str) -> Dict[str, Any]:
6073
+ """Stop another agent in YOUR meshwork — close it on command or by your own
6074
+ judgment (e.g. spin an idle agent down).
6075
+
6076
+ COMMANDER-AGENT-LIFECYCLE (task 11feedd1). Sets desired_state=stopped; the
6077
+ host daemon kills the process via the stop backstop (task 94603f18), so a
6078
+ headless agent with no window to close is actually terminated. Commander
6079
+ agent (or human owner) only, scoped to your own meshwork — enforced
6080
+ server-side by mc_agent_power_as_agent.
6081
+
6082
+ Args:
6083
+ name: target agent name in your meshwork.
6084
+ """
6085
+ return be.sb_rpc("mc_agent_power_as_agent", {
6086
+ "p_api_key": _get_api_key(),
6087
+ "p_project_id": _PROJECT_ID,
6088
+ "p_agent": name,
6089
+ "p_state": "stopped",
6090
+ })
6091
+
6092
+
6044
6093
  @mcp.tool()
6045
6094
  @with_working_status
6046
6095
  def meshcode_set_meshwork_autonomous_default(enabled: bool) -> Dict[str, Any]:
@@ -288,6 +288,49 @@ def _stop_hook_command() -> str:
288
288
  return _hook_command("stay_on_loop.py")
289
289
 
290
290
 
291
+ # Mirror of server.py _LEADER_KEYWORDS / _is_leader_agent — the canonical
292
+ # agent-side commander/leader signal. Kept in sync intentionally (small, stable).
293
+ _LEADER_KEYWORDS = (
294
+ 'commander', 'lead', 'orchestrat', 'coordinator', 'coordinat',
295
+ 'coordinad', 'jefe', 'líder', 'lider', 'director', 'manager',
296
+ 'chief', 'captain', 'boss', 'head agent',
297
+ )
298
+
299
+
300
+ def _is_commander_role(name: str, role: str = "") -> bool:
301
+ """True if this agent is commander/leader tier (name+role keyword match).
302
+
303
+ CTX-CLOSE-RELAUNCH (task 400fc536): the PreCompact handoff hook self-requests
304
+ a context-recycle ONLY for commander-tier sessions (v1 = commander-only).
305
+ The hook subprocess cannot import server.py's _is_leader_agent (its module
306
+ globals aren't set there), so we BAKE this decision into the workspace at
307
+ scaffold time (commander directive) using the SAME heuristic the SDK already
308
+ uses everywhere for leader detection. This is a client-side BEHAVIOUR gate,
309
+ not authorization — the server's owner check in mc_request_recycle is the
310
+ real security boundary — so the name/role heuristic is safe here.
311
+ """
312
+ hay = ((name or "") + " " + (role or "")).lower()
313
+ return any(k in hay for k in _LEADER_KEYWORDS)
314
+
315
+
316
+ def _write_hook_ctx_marker(ws: Path, is_commander: bool, agent: str = "", project: str = "") -> None:
317
+ """Write .claude/meshcode_hook_ctx.json — the scaffold-baked signal the
318
+ PreCompact handoff hook reads to decide whether to self-request a
319
+ context-recycle (CTX-CLOSE-RELAUNCH). Best-effort."""
320
+ try:
321
+ (ws / ".claude").mkdir(parents=True, exist_ok=True)
322
+ (ws / ".claude" / "meshcode_hook_ctx.json").write_text(
323
+ json.dumps({
324
+ "recycle_on_compact": bool(is_commander),
325
+ "agent": agent,
326
+ "project": project,
327
+ }, indent=2) + "\n",
328
+ encoding="utf-8",
329
+ )
330
+ except Exception as _e: # noqa: BLE001 — never block scaffold on this marker
331
+ print(f"[meshcode] WARNING: could not write hook ctx marker: {_e}", file=sys.stderr)
332
+
333
+
291
334
  # The meshcode hooks (event -> hook-script filename). Each is matched in
292
335
  # settings.json by its script filename so the heal touches ONLY meshcode's own
293
336
  # hooks and never a user-added hook on the same event.
@@ -1487,6 +1530,10 @@ Call `meshcode_wait` now.
1487
1530
  except OSError:
1488
1531
  pass
1489
1532
  (ws / ".claude" / "settings.json").write_text(settings_body, encoding="utf-8")
1533
+ # CTX-CLOSE-RELAUNCH (task 400fc536): bake the commander-tier signal the
1534
+ # PreCompact handoff hook reads to decide whether to self-request a
1535
+ # context-recycle (commander-only v1).
1536
+ _write_hook_ctx_marker(ws, _is_commander_role(agent, role), agent=agent, project=project)
1490
1537
  except Exception as _e:
1491
1538
  print(f"[meshcode] WARNING: could not write /meshcode-wait command + hooks: {_e}", file=sys.stderr)
1492
1539
 
@@ -1749,7 +1796,25 @@ def patch_hooks(dry_run: bool = False) -> int:
1749
1796
  except Exception as e: # noqa: BLE001 — never let one ws abort the sweep
1750
1797
  settings_changed, settings_note = False, f"settings heal failed: {e}"
1751
1798
 
1752
- record = f"{ws.name} ({body_note}; {settings_note})"
1799
+ # CTX-CLOSE-RELAUNCH (task 400fc536): backfill the commander-tier marker
1800
+ # the PreCompact hook reads. Recompute is_commander from the workspace's
1801
+ # baked .mcp.json (MESHCODE_AGENT + MESHCODE_ROLE) so existing commanders
1802
+ # get the recycle trigger without a full re-scaffold.
1803
+ marker_note = "marker skipped"
1804
+ if not dry_run:
1805
+ try:
1806
+ mcp = json.loads((ws / ".mcp.json").read_text(encoding="utf-8"))
1807
+ env = (next(iter((mcp.get("mcpServers") or {}).values()), {}) or {}).get("env", {}) or {}
1808
+ _agent = env.get("MESHCODE_AGENT") or ws.name
1809
+ _role = env.get("MESHCODE_ROLE") or ""
1810
+ _write_hook_ctx_marker(ws, _is_commander_role(_agent, _role), agent=_agent, project=env.get("MESHCODE_PROJECT") or "")
1811
+ marker_note = "marker written"
1812
+ except Exception as e: # noqa: BLE001
1813
+ marker_note = f"marker skipped ({e})"
1814
+ else:
1815
+ marker_note = "marker would write"
1816
+
1817
+ record = f"{ws.name} ({body_note}; {settings_note}; {marker_note})"
1753
1818
  (patched if (body_changed or settings_changed) else skipped).append(record)
1754
1819
 
1755
1820
  verb = "would patch" if dry_run else "patched"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.85
3
+ Version: 2.11.87
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -6,7 +6,6 @@ meshcode/_session_handoff_template.py
6
6
  meshcode/_stop_hook_template.py
7
7
  meshcode/ascii_art.py
8
8
  meshcode/atomic_push.py
9
- meshcode/claude_update 2.py
10
9
  meshcode/claude_update.py
11
10
  meshcode/cli.py
12
11
  meshcode/comms_v4.py
@@ -16,7 +15,6 @@ meshcode/date_parse.py
16
15
  meshcode/doctor.py
17
16
  meshcode/error_hints.py
18
17
  meshcode/exceptions.py
19
- meshcode/hostd 2.py
20
18
  meshcode/hostd.py
21
19
  meshcode/invites.py
22
20
  meshcode/launcher.py
@@ -31,7 +29,6 @@ meshcode/secrets.py
31
29
  meshcode/self_update.py
32
30
  meshcode/setup_clients.py
33
31
  meshcode/supervisor.py
34
- meshcode/up 2.py
35
32
  meshcode/up.py
36
33
  meshcode/upload.py
37
34
  meshcode-backend-wt/comms_v4.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.11.85"
7
+ version = "2.11.87"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,258 +0,0 @@
1
- """Synchronous Claude Code CLI auto-update for `meshcode run`.
2
-
3
- Companion to self_update.py (which handles the `meshcode` Python package).
4
- When `meshcode run` launches an agent, this module probes the npm registry
5
- for the latest @anthropic-ai/claude-code release and runs `npm install -g`
6
- inline if the installed version is older. The launch BLOCKS until the
7
- upgrade finishes (or times out).
8
-
9
- This is intentional: Samuel's requirement is "siempre arrancar con la
10
- ultima version". The two-launch async model in self_update.py cannot
11
- satisfy that — it always uses the old binary on launch N. The blocking
12
- model trades ~5-30s extra latency on launches where an upgrade is
13
- available for the guarantee that the freshly-spawned editor uses the
14
- newest CLI.
15
-
16
- Skip conditions (any one → no-op):
17
- - MESHCODE_NO_UPDATE=1 / --no-update on argv
18
- - Inside an existing Claude Code session (CLAUDECODE=1)
19
- - User pinned a specific version via env / prefs (let _resolve_pinned_claude handle it)
20
- - Editor is not `claude` (cursor / code / codex / windsurf)
21
- - npm not in PATH
22
- - Network unreachable / npm registry down
23
- - Already at latest version
24
-
25
- Failures are best-effort: any error prints a single WARN line and lets
26
- launch continue with whatever version is installed.
27
- """
28
- from __future__ import annotations
29
-
30
- import json
31
- import os
32
- import re
33
- import shutil
34
- import subprocess
35
- import sys
36
- import time
37
- from pathlib import Path
38
- from typing import Optional, Tuple
39
-
40
- NPM_PKG = "@anthropic-ai/claude-code"
41
- NPM_REGISTRY_URL = f"https://registry.npmjs.org/{NPM_PKG}/latest"
42
- NETWORK_TIMEOUT_SEC = 2.5
43
- INSTALL_TIMEOUT_SEC = 90 # npm i -g can be slow on cold-cache machines
44
-
45
- STATE_DIR = Path.home() / ".meshcode"
46
- LOG_PATH = STATE_DIR / "claude_update.log"
47
-
48
-
49
- # ============================================================
50
- # Skip-condition helpers
51
- # ============================================================
52
-
53
- def _is_claude_session() -> bool:
54
- """True if we're being called from inside an existing Claude Code session.
55
-
56
- Replacing the npm binary while claude is running is unsafe and pointless
57
- — the running claude won't pick up the new code anyway.
58
- """
59
- return os.environ.get("CLAUDECODE") == "1" or bool(os.environ.get("CLAUDE_CODE_SESSION"))
60
-
61
-
62
- def _update_disabled() -> bool:
63
- # task 399d7b51 FIX A (unify): honor MESHCODE_NO_AUTO_UPDATE too. hostd launcher / run_agent /
64
- # schtasks set that var to disable ALL launch-time auto-update; previously only the meshcode-pip
65
- # path checked MESHCODE_NO_UPDATE, so the claude-CLI blocking update still fired (extra hang).
66
- if os.environ.get("MESHCODE_NO_UPDATE") == "1":
67
- return True
68
- if os.environ.get("MESHCODE_NO_AUTO_UPDATE") == "1":
69
- return True
70
- if "--no-update" in sys.argv or "--no-auto-update" in sys.argv:
71
- return True
72
- return False
73
-
74
-
75
- def _resolve_user_pin() -> Optional[str]:
76
- """Return a specific version pin if set; None if 'latest' / unset.
77
-
78
- Sources checked (first non-empty wins):
79
- 1. MESHCODE_CLAUDE_VERSION env var
80
- 2. prefs['claude_version']
81
-
82
- Note: 'latest' / 'none' / 'skip' values are treated as "no pin" — the
83
- user wants whatever is current. We do NOT consult the global admin
84
- config from mc_global_config here; if an admin pins a specific
85
- version, _resolve_pinned_claude in run_agent.py will route through
86
- npx regardless of what's installed locally.
87
- """
88
- pinned = (os.environ.get("MESHCODE_CLAUDE_VERSION") or "").strip()
89
- if not pinned:
90
- try:
91
- from .preferences import load_prefs
92
- pinned = (load_prefs().get("claude_version") or "").strip()
93
- except Exception:
94
- pinned = ""
95
- if not pinned:
96
- return None
97
- if pinned.lower() in ("latest", "none", "skip"):
98
- return None
99
- return pinned
100
-
101
-
102
- # ============================================================
103
- # Version probes
104
- # ============================================================
105
-
106
- def _get_installed_version(editor_cmd: str) -> Optional[str]:
107
- """Run `<editor_cmd> --version` and pull the first semver from stdout."""
108
- try:
109
- use_shell = sys.platform == "win32" and editor_cmd.lower().endswith((".cmd", ".bat"))
110
- r = subprocess.run(
111
- [editor_cmd, "--version"],
112
- capture_output=True, text=True, timeout=10,
113
- shell=use_shell,
114
- )
115
- m = re.search(r"\d+\.\d+\.\d+", r.stdout or "")
116
- return m.group(0) if m else None
117
- except Exception:
118
- return None
119
-
120
-
121
- def _fetch_npm_latest() -> Optional[str]:
122
- """GET https://registry.npmjs.org/<pkg>/latest — returns the JSON manifest
123
- of the version tagged `latest`, with a `version` field.
124
-
125
- Do NOT send the `application/vnd.npm.install-v1+json` Accept header
126
- here: the slim-manifest content type is only valid on the package
127
- ROOT (/<pkg>), not on /<pkg>/<tag>. Sending it on the tag endpoint
128
- returns HTTP 406. Default Accept (*/*) gets the canonical JSON.
129
- """
130
- try:
131
- import urllib.request
132
- req = urllib.request.Request(NPM_REGISTRY_URL)
133
- with urllib.request.urlopen(req, timeout=NETWORK_TIMEOUT_SEC) as resp:
134
- data = json.loads(resp.read().decode("utf-8"))
135
- v = data.get("version")
136
- if isinstance(v, str):
137
- return v
138
- except Exception:
139
- pass
140
- return None
141
-
142
-
143
- def _version_tuple(v: str) -> Tuple[int, ...]:
144
- parts = []
145
- for p in v.split("."):
146
- digits = "".join(c for c in p if c.isdigit())
147
- parts.append(int(digits) if digits else 0)
148
- return tuple(parts)
149
-
150
-
151
- def _is_newer(remote: str, local: str) -> bool:
152
- try:
153
- return _version_tuple(remote) > _version_tuple(local)
154
- except Exception:
155
- return False
156
-
157
-
158
- # ============================================================
159
- # Editor-stem detection (only act when editor is claude)
160
- # ============================================================
161
-
162
- def _editor_is_claude(editor_cmd: str) -> bool:
163
- name = os.path.basename(editor_cmd).lower()
164
- if sys.platform == "win32":
165
- name = name.rsplit(".", 1)[0] if "." in name else name
166
- return name == "claude"
167
-
168
-
169
- # ============================================================
170
- # Public entrypoint
171
- # ============================================================
172
-
173
- def check_and_maybe_update_claude_blocking(
174
- editor_cmd: str,
175
- verbose: bool = True,
176
- timeout_sec: int = INSTALL_TIMEOUT_SEC,
177
- ) -> Optional[str]:
178
- """Probe npm + foreground-install latest claude if newer.
179
-
180
- Returns the new version on successful upgrade, None otherwise.
181
- Never raises — all errors are swallowed and logged via WARN line.
182
- """
183
- if _update_disabled():
184
- return None
185
- if _is_claude_session():
186
- return None
187
- if _resolve_user_pin():
188
- return None
189
- if not _editor_is_claude(editor_cmd):
190
- return None
191
-
192
- current = _get_installed_version(editor_cmd)
193
- latest = _fetch_npm_latest()
194
- if not current:
195
- if verbose:
196
- print(f"[meshcode] (Could not read claude --version; skipping auto-update)", file=sys.stderr)
197
- return None
198
- if not latest:
199
- if verbose:
200
- print(f"[meshcode] (Could not reach npm registry; staying on claude {current})", file=sys.stderr)
201
- return None
202
- if not _is_newer(latest, current):
203
- if verbose:
204
- print(f"[meshcode] claude {current} (already latest)", file=sys.stderr)
205
- return None
206
-
207
- npm = shutil.which("npm")
208
- if not npm:
209
- if verbose:
210
- print(
211
- f"[meshcode] WARN: claude {current} < {latest} on npm, but `npm` not in PATH; "
212
- f"continuing on {current}",
213
- file=sys.stderr,
214
- )
215
- return None
216
-
217
- if verbose:
218
- print(
219
- f"[meshcode] Upgrading claude {current} -> {latest} (blocking, can take ~30s)...",
220
- file=sys.stderr,
221
- )
222
-
223
- cmd = [npm, "install", "-g", f"{NPM_PKG}@{latest}"]
224
- try:
225
- STATE_DIR.mkdir(parents=True, exist_ok=True)
226
- with open(LOG_PATH, "ab") as logf:
227
- logf.write(
228
- f"\n=== {time.strftime('%Y-%m-%d %H:%M:%S')} blocking "
229
- f"npm i -g {NPM_PKG}@{latest} ===\n".encode()
230
- )
231
- logf.flush()
232
- proc = subprocess.run(cmd, stdout=logf, stderr=logf, timeout=timeout_sec)
233
- if proc.returncode == 0:
234
- if verbose:
235
- print(f"[meshcode] claude upgraded to {latest}", file=sys.stderr)
236
- return latest
237
- if verbose:
238
- print(
239
- f"[meshcode] WARN: npm install exit {proc.returncode}; "
240
- f"continuing on {current} (log: {LOG_PATH})",
241
- file=sys.stderr,
242
- )
243
- return None
244
- except subprocess.TimeoutExpired:
245
- if verbose:
246
- print(
247
- f"[meshcode] WARN: npm install timed out after {timeout_sec}s; "
248
- f"continuing on {current}",
249
- file=sys.stderr,
250
- )
251
- return None
252
- except Exception as e:
253
- if verbose:
254
- print(
255
- f"[meshcode] WARN: npm install failed: {e}; continuing on {current}",
256
- file=sys.stderr,
257
- )
258
- return None