meshcode 2.11.104__tar.gz → 2.11.105__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 (255) hide show
  1. {meshcode-2.11.104 → meshcode-2.11.105}/PKG-INFO +1 -1
  2. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/__init__.py +1 -1
  3. meshcode-2.11.105/meshcode/_session_handoff_template.py +296 -0
  4. meshcode-2.11.105/meshcode/claude_update.py +258 -0
  5. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/hostd.py +20 -2
  6. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/SOURCES.txt +3 -0
  8. {meshcode-2.11.104 → meshcode-2.11.105}/pyproject.toml +1 -1
  9. meshcode-2.11.105/tests/test_autonomous_prompt_inject.py +126 -0
  10. {meshcode-2.11.104 → meshcode-2.11.105}/README.md +0 -0
  11. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/__main__.py +0 -0
  12. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/_session_handoff_template 2.py +0 -0
  13. /meshcode-2.11.104/meshcode/_session_handoff_template.py → /meshcode-2.11.105/meshcode/_session_handoff_template 3.py +0 -0
  14. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/_stop_hook_template.py +0 -0
  15. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/ascii_art.py +0 -0
  16. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/atomic_push.py +0 -0
  17. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/claude_update 2.py +0 -0
  18. /meshcode-2.11.104/meshcode/claude_update.py → /meshcode-2.11.105/meshcode/claude_update 3.py +0 -0
  19. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/cli.py +0 -0
  20. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/comms_v4.py +0 -0
  21. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/compat.py +0 -0
  22. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/daemon.py +0 -0
  23. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/date_parse.py +0 -0
  24. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/doctor.py +0 -0
  25. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/error_hints.py +0 -0
  26. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/exceptions.py +0 -0
  27. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/hostd 2.py +0 -0
  28. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/invites.py +0 -0
  29. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/launcher.py +0 -0
  30. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/launcher_install.py +0 -0
  31. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/__init__.py +0 -0
  32. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/__main__.py +0 -0
  33. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/backend.py +0 -0
  34. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/realtime.py +0 -0
  35. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/server.py +0 -0
  36. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  37. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/test_backend.py +0 -0
  38. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  39. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  40. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  41. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  42. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  43. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/preferences.py +0 -0
  44. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/protocol_handler.py +0 -0
  45. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/protocol_v2.py +0 -0
  46. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/quickstart.py +0 -0
  47. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/rpc_allowlist.py +0 -0
  48. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/run_agent.py +0 -0
  49. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/scripts/check_secrets.py +0 -0
  50. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/scripts/race_rate_harness.py +0 -0
  51. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/secrets.py +0 -0
  52. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/self_update.py +0 -0
  53. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/setup_clients.py +0 -0
  54. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/supervisor.py +0 -0
  55. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/up 2.py +0 -0
  56. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/up.py +0 -0
  57. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode/upload.py +0 -0
  58. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/comms_v4.py +0 -0
  59. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  60. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  61. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/cli.py +0 -0
  62. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  63. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/compat.py +0 -0
  64. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  65. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  66. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/invites.py +0 -0
  67. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  68. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  69. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  70. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  71. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  72. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  73. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  74. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  75. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  76. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  77. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  78. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  79. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  80. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  81. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  82. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  83. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  84. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  85. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/meshcode/upload.py +0 -0
  86. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  87. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_core.py +0 -0
  88. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  89. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  90. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  91. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  92. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  93. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  94. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  95. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  96. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  97. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  98. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  99. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  100. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  101. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  102. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  103. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  104. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  105. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  106. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  107. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  108. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  109. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  110. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  111. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  112. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  113. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  114. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  115. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  116. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  117. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  118. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  119. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  120. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  121. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  122. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  123. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  124. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  125. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  126. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/comms_v4.py +0 -0
  127. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  128. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  129. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/cli.py +0 -0
  130. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  131. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/compat.py +0 -0
  132. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  133. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  134. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/invites.py +0 -0
  135. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  136. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  137. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  138. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  139. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  140. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  141. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  142. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  143. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  144. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  145. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  146. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  147. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  148. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  149. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  150. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  151. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  152. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  153. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/meshcode/upload.py +0 -0
  154. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  155. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_core.py +0 -0
  156. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  157. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  158. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  159. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  160. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  161. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  162. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  163. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  164. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  165. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  166. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  167. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/comms_v4.py +0 -0
  168. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  169. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  170. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  171. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  172. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  173. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  174. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  175. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  176. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  177. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  178. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  179. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  180. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  181. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  182. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  183. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  184. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  185. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  186. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  187. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  188. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  189. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  190. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  191. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  192. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  193. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  194. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  195. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  196. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_core.py +0 -0
  197. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  198. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  199. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  200. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  201. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  202. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  203. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  204. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  205. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  206. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  207. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  208. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/PKG-INFO 2 +0 -0
  209. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/SOURCES 2.txt +0 -0
  210. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/dependency_links 2.txt +0 -0
  211. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/dependency_links.txt +0 -0
  212. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/entry_points 2.txt +0 -0
  213. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/entry_points.txt +0 -0
  214. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/requires 2.txt +0 -0
  215. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/requires.txt +0 -0
  216. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/top_level 2.txt +0 -0
  217. {meshcode-2.11.104 → meshcode-2.11.105}/meshcode.egg-info/top_level.txt +0 -0
  218. {meshcode-2.11.104 → meshcode-2.11.105}/setup.cfg +0 -0
  219. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_auto_update_hardening.py +0 -0
  220. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_autonomous_closegap_1.py +0 -0
  221. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_autonomous_closegap_2.py +0 -0
  222. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_autonomous_closegap_3.py +0 -0
  223. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_autonomous_prompt_inject 2.py +0 -0
  224. /meshcode-2.11.104/tests/test_autonomous_prompt_inject.py → /meshcode-2.11.105/tests/test_autonomous_prompt_inject 3.py +0 -0
  225. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_boot_bug_regression.py +0 -0
  226. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_color_truecolor.py +0 -0
  227. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_core.py +0 -0
  228. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_cross_agent_messaging.py +0 -0
  229. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_date_parse.py +0 -0
  230. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_doctor.py +0 -0
  231. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_epistemic_v1_python_sdk.py +0 -0
  232. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  233. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_esc_deaf_state.py +0 -0
  234. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_exceptions.py +0 -0
  235. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_file_upload.py +0 -0
  236. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_init_device_code.py +0 -0
  237. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_install_guard.py +0 -0
  238. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_lease_sigterm_release.py +0 -0
  239. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_mark_read_batch.py +0 -0
  240. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_marketplace_ratings.py +0 -0
  241. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_migration_integrity.py +0 -0
  242. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_realtime_event_freshness.py +0 -0
  243. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_rls_cross_tenant.py +0 -0
  244. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_rpc_grants.py +0 -0
  245. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_rpc_migrations.py +0 -0
  246. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_run_agent_dry_run.py +0 -0
  247. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_run_agent_no_server_import.py +0 -0
  248. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_security_regressions.py +0 -0
  249. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_self_update_user_site.py +0 -0
  250. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_sentinel.py +0 -0
  251. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_setup_path.py +0 -0
  252. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_sleep_signals.py +0 -0
  253. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_status_enum_coverage.py +0 -0
  254. {meshcode-2.11.104 → meshcode-2.11.105}/tests/test_stay_on_loop_hook.py +0 -0
  255. {meshcode-2.11.104 → meshcode-2.11.105}/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.104
3
+ Version: 2.11.105
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.104"
2
+ __version__ = "2.11.105"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -0,0 +1,296 @@
1
+ """Session-handoff hook templates — single source of truth for the
2
+ PreCompact (write) + SessionStart (read) hooks.
3
+
4
+ CTX-CLOSE-RELAUNCH (task bcd157a9, backend2): when a commander/agent session
5
+ gets recycled at high context — either by Claude Code's in-place autocompact
6
+ or by a clean close+relaunch — the IN-FLIGHT working context (which task I was
7
+ mid-executing, what I just edited/decided this session) is lost. meshcode_boot
8
+ restores mesh state (tasks, inbox, memory) authoritatively, but NOT the
9
+ conversational thread. These two hooks bridge that gap:
10
+
11
+ * PreCompact -> session_handoff_write.py: dump a compact snapshot of the
12
+ recent transcript tail to .claude/handoff.json BEFORE the context is
13
+ compacted/the session exits. Pure local file I/O — no creds, no network,
14
+ no MCP — so it is safe to run in any hook subprocess on any platform.
15
+ * SessionStart -> session_handoff_read.py: on the next (fresh) session,
16
+ inject that snapshot back as additionalContext so the agent resumes the
17
+ thread, then archive it so it is injected exactly once.
18
+
19
+ Stored as string constants (same pattern as _stop_hook_template.py) so
20
+ `meshcode patch-hooks` can backfill existing workspaces without a full
21
+ re-scaffold. The recycle TRIGGER itself (mc_request_recycle / wait-loop
22
+ must_exit=recycle / hostd context branch) is owned by the daemon/server side
23
+ and is intentionally NOT in these scripts — handoff preservation is decoupled
24
+ from whatever causes the restart, so it works for autocompact, clean recycle,
25
+ crash-respawn, or a manual relaunch alike.
26
+ """
27
+
28
+ # Shared constants kept in sync between the two bodies.
29
+ _HANDOFF_FILENAME = "handoff.json"
30
+ _HANDOFF_MAX_TURNS = 24 # transcript tail entries to preserve
31
+ _HANDOFF_MAX_CHARS_PER_TURN = 1200
32
+ _HANDOFF_MAX_AGE_S = 24 * 3600 # ignore stale handoffs on read
33
+
34
+ HANDOFF_WRITE_BODY = '''#!/usr/bin/env python3
35
+ """PreCompact hook: snapshot the recent transcript tail to .claude/handoff.json
36
+ so a fresh post-recycle session can resume the in-flight thread.
37
+
38
+ CTX-CLOSE-RELAUNCH (task bcd157a9). Pure local file I/O — never touches the
39
+ network, never imports meshcode, never blocks. Any failure is swallowed and the
40
+ hook exits 0 so it can NEVER stall or break a Claude Code compaction/exit.
41
+
42
+ Claude Code passes a JSON event on stdin:
43
+ {"hook_event_name":"PreCompact","session_id":..,"transcript_path":..,
44
+ "trigger":"auto"|"manual", ...}
45
+ """
46
+ import json
47
+ import os
48
+ import sys
49
+ from pathlib import Path
50
+
51
+ MAX_TURNS = 24
52
+ MAX_CHARS_PER_TURN = 1200
53
+
54
+
55
+ def _project_dir() -> Path:
56
+ # Claude Code exports CLAUDE_PROJECT_DIR for hooks; fall back to cwd.
57
+ return Path(os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd())
58
+
59
+
60
+ def _text_from_message(msg) -> str:
61
+ """Flatten a transcript message's content into plain text + tool markers."""
62
+ if isinstance(msg, str):
63
+ return msg
64
+ if not isinstance(msg, dict):
65
+ return ""
66
+ content = msg.get("content")
67
+ if isinstance(content, str):
68
+ return content
69
+ parts = []
70
+ if isinstance(content, list):
71
+ for block in content:
72
+ if not isinstance(block, dict):
73
+ continue
74
+ btype = block.get("type")
75
+ if btype == "text" and block.get("text"):
76
+ parts.append(str(block["text"]))
77
+ elif btype == "tool_use":
78
+ parts.append(f"[tool_use:{block.get('name','?')}]")
79
+ elif btype == "tool_result":
80
+ parts.append("[tool_result]")
81
+ return " ".join(parts).strip()
82
+
83
+
84
+ def _extract_tail(transcript_path: str):
85
+ """Return up to MAX_TURNS (role, text) tuples from the end of the JSONL
86
+ transcript, skipping empty / meta-only entries."""
87
+ p = Path(transcript_path) if transcript_path else None
88
+ if not p or not p.exists():
89
+ return []
90
+ rows = []
91
+ try:
92
+ with p.open("r", encoding="utf-8", errors="replace") as fh:
93
+ for line in fh:
94
+ line = line.strip()
95
+ if not line:
96
+ continue
97
+ try:
98
+ obj = json.loads(line)
99
+ except (json.JSONDecodeError, ValueError):
100
+ continue
101
+ msg = obj.get("message") if isinstance(obj, dict) else None
102
+ role = (obj.get("type") or (msg or {}).get("role") or "").strip()
103
+ if role not in ("user", "assistant", "human"):
104
+ continue
105
+ text = _text_from_message(msg if msg is not None else obj)
106
+ if not text:
107
+ continue
108
+ if len(text) > MAX_CHARS_PER_TURN:
109
+ text = text[:MAX_CHARS_PER_TURN] + " …[truncated]"
110
+ rows.append(("user" if role in ("user", "human") else "assistant", text))
111
+ except OSError:
112
+ return []
113
+ return rows[-MAX_TURNS:]
114
+
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
+
164
+ def main() -> int:
165
+ try:
166
+ raw = sys.stdin.read()
167
+ event = json.loads(raw) if raw.strip() else {}
168
+ except (json.JSONDecodeError, ValueError):
169
+ event = {}
170
+
171
+ tail = _extract_tail(event.get("transcript_path", ""))
172
+ handoff = {
173
+ "schema": 1,
174
+ "session_id": event.get("session_id"),
175
+ "trigger": event.get("trigger") or event.get("hook_event_name") or "precompact",
176
+ "agent": os.environ.get("MESHCODE_AGENT"),
177
+ "project": os.environ.get("MESHCODE_PROJECT"),
178
+ "turns": [{"role": r, "text": t} for (r, t) in tail],
179
+ }
180
+ try:
181
+ d = _project_dir() / ".claude"
182
+ d.mkdir(parents=True, exist_ok=True)
183
+ tmp = d / "handoff.json.tmp"
184
+ tmp.write_text(json.dumps(handoff, ensure_ascii=False, indent=2), encoding="utf-8")
185
+ tmp.replace(d / "handoff.json")
186
+ except OSError as e:
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())
191
+ return 0
192
+
193
+
194
+ if __name__ == "__main__":
195
+ try:
196
+ sys.exit(main())
197
+ except Exception as e: # never break compaction
198
+ sys.stderr.write(f"[session_handoff_write] error: {e}\\n")
199
+ sys.exit(0)
200
+ '''
201
+
202
+ HANDOFF_READ_BODY = '''#!/usr/bin/env python3
203
+ """SessionStart hook: if a handoff snapshot exists, inject it as
204
+ additionalContext so a freshly-recycled session resumes the in-flight thread,
205
+ then archive it so it is injected exactly once.
206
+
207
+ CTX-CLOSE-RELAUNCH (task bcd157a9). Pure local file I/O — no network, no
208
+ meshcode import, never blocks. meshcode_boot remains authoritative for mesh
209
+ state; this only restores the conversational thread.
210
+
211
+ Claude Code passes a JSON event on stdin:
212
+ {"hook_event_name":"SessionStart","source":"startup"|"resume"|"compact",..}
213
+ and reads our additionalContext from this hook's stdout JSON:
214
+ {"hookSpecificOutput":{"hookEventName":"SessionStart","additionalContext":..}}
215
+ """
216
+ import json
217
+ import os
218
+ import sys
219
+ import time
220
+ from pathlib import Path
221
+
222
+ MAX_AGE_S = 24 * 3600
223
+
224
+
225
+ def _project_dir() -> Path:
226
+ return Path(os.environ.get("CLAUDE_PROJECT_DIR") or os.getcwd())
227
+
228
+
229
+ def _emit(context: str) -> None:
230
+ sys.stdout.write(json.dumps({
231
+ "hookSpecificOutput": {
232
+ "hookEventName": "SessionStart",
233
+ "additionalContext": context,
234
+ }
235
+ }))
236
+
237
+
238
+ def _render(handoff: dict) -> str:
239
+ turns = handoff.get("turns") or []
240
+ if not turns:
241
+ return ""
242
+ lines = [
243
+ "## Recovered session handoff (CTX-CLOSE-RELAUNCH)",
244
+ "",
245
+ "Your previous session was recycled at high context. meshcode_boot has "
246
+ "the authoritative mesh state (tasks/inbox/memory); the thread tail "
247
+ "below is the in-flight context that boot does not restore. Use it to "
248
+ "resume what you were mid-doing, then run the normal boot loop.",
249
+ "",
250
+ f"_trigger: {handoff.get('trigger','?')} · turns preserved: {len(turns)}_",
251
+ "",
252
+ ]
253
+ for t in turns:
254
+ role = (t.get("role") or "?").upper()
255
+ text = (t.get("text") or "").strip()
256
+ if text:
257
+ lines.append(f"**{role}:** {text}")
258
+ lines.append("")
259
+ return "\\n".join(lines).strip()
260
+
261
+
262
+ def main() -> int:
263
+ path = _project_dir() / ".claude" / "handoff.json"
264
+ if not path.exists():
265
+ return 0
266
+ try:
267
+ handoff = json.loads(path.read_text(encoding="utf-8"))
268
+ except (json.JSONDecodeError, ValueError, OSError):
269
+ return 0
270
+
271
+ # Ignore stale snapshots (mtime-based; avoids resurrecting old threads).
272
+ try:
273
+ if (time.time() - path.stat().st_mtime) > MAX_AGE_S:
274
+ path.replace(path.with_suffix(".stale.json"))
275
+ return 0
276
+ except OSError:
277
+ pass
278
+
279
+ context = _render(handoff)
280
+ if context:
281
+ _emit(context)
282
+ # Archive so the handoff is injected exactly once.
283
+ try:
284
+ path.replace(path.with_suffix(".consumed.json"))
285
+ except OSError:
286
+ pass
287
+ return 0
288
+
289
+
290
+ if __name__ == "__main__":
291
+ try:
292
+ sys.exit(main())
293
+ except Exception as e:
294
+ sys.stderr.write(f"[session_handoff_read] error: {e}\\n")
295
+ sys.exit(0)
296
+ '''
@@ -0,0 +1,258 @@
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
@@ -895,9 +895,27 @@ def _do_reap(api_key: str, host_id: str) -> int:
895
895
  seen.pop(target, None)
896
896
  else:
897
897
  seen.pop(target, None) # recovered / running / gone — clear the grace clock
898
- # prune grace entries for agents no longer in the roster
898
+ # (C) DELETED-AGENT ORPHAN (commander Gap 2 / folds 6125f14a): a recorded headless PID whose agent is
899
+ # GONE from the roster — mc_agent_kill bare-DELETEs the row but ORPHANS the process ('lo cerré y sigue
900
+ # ahí'). Reap it, with the SAME grace so a transient roster-read miss can't kill a healthy agent.
901
+ for target in list(pids.keys()):
902
+ if target in live_targets:
903
+ continue
904
+ pid = pids.get(target)
905
+ if not _alive(pid):
906
+ seen.pop(target, None)
907
+ continue
908
+ first = seen.get(target)
909
+ if first is None:
910
+ seen[target] = now
911
+ _log(f"REAP-PENDING {target}: gone from roster but pid {pid} alive (mc_agent_kill orphan?) — "
912
+ f"{_REAP_ORPHAN_GRACE_SEC}s grace before reap")
913
+ elif now - float(first) >= _REAP_ORPHAN_GRACE_SEC:
914
+ n += _reap(target, pid, f"deleted-agent orphan; alive {int(now-float(first))}s after roster removal")
915
+ seen.pop(target, None)
916
+ # prune grace clocks only for targets that are BOTH gone from the roster AND have no alive recorded PID
899
917
  for t in list(seen.keys()):
900
- if t not in live_targets:
918
+ if t not in live_targets and not _alive(pids.get(t)):
901
919
  seen.pop(t, None)
902
920
  st["headless_pids"] = pids
903
921
  st["reapseen"] = seen
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.104
3
+ Version: 2.11.105
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -3,11 +3,13 @@ pyproject.toml
3
3
  meshcode/__init__.py
4
4
  meshcode/__main__.py
5
5
  meshcode/_session_handoff_template 2.py
6
+ meshcode/_session_handoff_template 3.py
6
7
  meshcode/_session_handoff_template.py
7
8
  meshcode/_stop_hook_template.py
8
9
  meshcode/ascii_art.py
9
10
  meshcode/atomic_push.py
10
11
  meshcode/claude_update 2.py
12
+ meshcode/claude_update 3.py
11
13
  meshcode/claude_update.py
12
14
  meshcode/cli.py
13
15
  meshcode/comms_v4.py
@@ -216,6 +218,7 @@ tests/test_autonomous_closegap_1.py
216
218
  tests/test_autonomous_closegap_2.py
217
219
  tests/test_autonomous_closegap_3.py
218
220
  tests/test_autonomous_prompt_inject 2.py
221
+ tests/test_autonomous_prompt_inject 3.py
219
222
  tests/test_autonomous_prompt_inject.py
220
223
  tests/test_boot_bug_regression.py
221
224
  tests/test_color_truecolor.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.11.104"
7
+ version = "2.11.105"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}