meshcode 2.11.75__tar.gz → 2.11.76__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 (268) hide show
  1. {meshcode-2.11.75 → meshcode-2.11.76}/PKG-INFO +1 -1
  2. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/__init__.py +1 -1
  3. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/hostd.py +18 -11
  4. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/run_agent.py +6 -5
  5. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/PKG-INFO +1 -1
  6. meshcode-2.11.75/meshcode.egg-info/SOURCES 2.txt → meshcode-2.11.76/meshcode.egg-info/SOURCES.txt +0 -12
  7. {meshcode-2.11.75 → meshcode-2.11.76}/pyproject.toml +1 -1
  8. meshcode-2.11.75/meshcode/hostd 3.py +0 -509
  9. meshcode-2.11.75/meshcode.egg-info/PKG-INFO 2 +0 -452
  10. meshcode-2.11.75/meshcode.egg-info/SOURCES.txt +0 -266
  11. meshcode-2.11.75/meshcode.egg-info/dependency_links 2.txt +0 -1
  12. meshcode-2.11.75/meshcode.egg-info/entry_points 2.txt +0 -3
  13. meshcode-2.11.75/meshcode.egg-info/requires 2.txt +0 -13
  14. meshcode-2.11.75/meshcode.egg-info/top_level 2.txt +0 -4
  15. meshcode-2.11.75/tests/test_autonomous_prompt_inject 10.py +0 -126
  16. meshcode-2.11.75/tests/test_autonomous_prompt_inject 11.py +0 -126
  17. meshcode-2.11.75/tests/test_autonomous_prompt_inject 12.py +0 -126
  18. meshcode-2.11.75/tests/test_autonomous_prompt_inject 13.py +0 -126
  19. meshcode-2.11.75/tests/test_autonomous_prompt_inject 14.py +0 -126
  20. meshcode-2.11.75/tests/test_autonomous_prompt_inject 15.py +0 -126
  21. meshcode-2.11.75/tests/test_autonomous_prompt_inject 16.py +0 -126
  22. meshcode-2.11.75/tests/test_autonomous_prompt_inject 17.py +0 -126
  23. meshcode-2.11.75/tests/test_autonomous_prompt_inject 18.py +0 -126
  24. meshcode-2.11.75/tests/test_autonomous_prompt_inject 19.py +0 -126
  25. meshcode-2.11.75/tests/test_autonomous_prompt_inject 2.py +0 -126
  26. meshcode-2.11.75/tests/test_autonomous_prompt_inject 20.py +0 -126
  27. meshcode-2.11.75/tests/test_autonomous_prompt_inject 21.py +0 -126
  28. meshcode-2.11.75/tests/test_autonomous_prompt_inject 22.py +0 -126
  29. meshcode-2.11.75/tests/test_autonomous_prompt_inject 3.py +0 -126
  30. meshcode-2.11.75/tests/test_autonomous_prompt_inject 4.py +0 -126
  31. meshcode-2.11.75/tests/test_autonomous_prompt_inject 5.py +0 -126
  32. meshcode-2.11.75/tests/test_autonomous_prompt_inject 6.py +0 -126
  33. meshcode-2.11.75/tests/test_autonomous_prompt_inject 7.py +0 -126
  34. meshcode-2.11.75/tests/test_autonomous_prompt_inject 8.py +0 -126
  35. meshcode-2.11.75/tests/test_autonomous_prompt_inject 9.py +0 -126
  36. {meshcode-2.11.75 → meshcode-2.11.76}/README.md +0 -0
  37. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/__main__.py +0 -0
  38. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/_stop_hook_template.py +0 -0
  39. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/ascii_art.py +0 -0
  40. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/atomic_push.py +0 -0
  41. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/claude_update.py +0 -0
  42. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/cli.py +0 -0
  43. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/comms_v4.py +0 -0
  44. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/compat.py +0 -0
  45. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/daemon.py +0 -0
  46. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/date_parse.py +0 -0
  47. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/doctor.py +0 -0
  48. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/error_hints.py +0 -0
  49. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/exceptions.py +0 -0
  50. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/invites.py +0 -0
  51. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/launcher.py +0 -0
  52. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/launcher_install.py +0 -0
  53. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/__init__.py +0 -0
  54. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/__main__.py +0 -0
  55. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/backend.py +0 -0
  56. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/realtime.py +0 -0
  57. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/server.py +0 -0
  58. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  59. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_backend.py +0 -0
  60. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  61. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  62. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  63. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  64. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  65. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/preferences.py +0 -0
  66. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/protocol_handler.py +0 -0
  67. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/protocol_v2.py +0 -0
  68. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/quickstart.py +0 -0
  69. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/rpc_allowlist.py +0 -0
  70. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/scripts/check_secrets.py +0 -0
  71. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/scripts/race_rate_harness.py +0 -0
  72. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/secrets.py +0 -0
  73. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/self_update.py +0 -0
  74. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/setup_clients.py +0 -0
  75. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/supervisor.py +0 -0
  76. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/up.py +0 -0
  77. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode/upload.py +0 -0
  78. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/comms_v4.py +0 -0
  79. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  80. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  81. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/cli.py +0 -0
  82. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  83. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/compat.py +0 -0
  84. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  85. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  86. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/invites.py +0 -0
  87. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  88. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  89. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  90. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  91. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  92. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  93. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  94. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  95. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  96. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  97. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  98. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  99. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  100. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  101. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  102. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  103. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  104. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  105. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/meshcode/upload.py +0 -0
  106. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  107. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_core.py +0 -0
  108. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  109. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  110. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  111. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  112. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  113. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  114. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  115. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  116. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  117. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  118. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  119. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  120. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  121. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  122. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  123. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  124. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  125. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  126. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  127. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  128. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  129. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  130. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  131. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  132. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  133. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  134. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  135. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  136. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  137. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  138. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  139. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  140. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  141. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  142. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  143. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  144. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  145. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  146. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/comms_v4.py +0 -0
  147. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  148. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  149. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/cli.py +0 -0
  150. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  151. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/compat.py +0 -0
  152. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  153. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  154. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/invites.py +0 -0
  155. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  156. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  157. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  158. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  159. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  160. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  161. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  162. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  163. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  164. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  165. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  166. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  167. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  168. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  169. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  170. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  171. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  172. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  173. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/meshcode/upload.py +0 -0
  174. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  175. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_core.py +0 -0
  176. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  177. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  178. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  179. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  180. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  181. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  182. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  183. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  184. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  185. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  186. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  187. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/comms_v4.py +0 -0
  188. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  189. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  190. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  191. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  192. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  193. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  194. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  195. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  196. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  197. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  198. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  199. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  200. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  201. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  202. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  203. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  204. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  205. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  206. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  207. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  208. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  209. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  210. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  211. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  212. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  213. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  214. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  215. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  216. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_core.py +0 -0
  217. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  218. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  219. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  220. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  221. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  222. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  223. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  224. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  225. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  226. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  227. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  228. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/dependency_links.txt +0 -0
  229. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/entry_points.txt +0 -0
  230. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/requires.txt +0 -0
  231. {meshcode-2.11.75 → meshcode-2.11.76}/meshcode.egg-info/top_level.txt +0 -0
  232. {meshcode-2.11.75 → meshcode-2.11.76}/setup.cfg +0 -0
  233. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_auto_update_hardening.py +0 -0
  234. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_autonomous_closegap_1.py +0 -0
  235. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_autonomous_closegap_2.py +0 -0
  236. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_autonomous_closegap_3.py +0 -0
  237. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_autonomous_prompt_inject.py +0 -0
  238. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_boot_bug_regression.py +0 -0
  239. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_color_truecolor.py +0 -0
  240. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_core.py +0 -0
  241. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_cross_agent_messaging.py +0 -0
  242. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_date_parse.py +0 -0
  243. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_doctor.py +0 -0
  244. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_epistemic_v1_python_sdk.py +0 -0
  245. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  246. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_esc_deaf_state.py +0 -0
  247. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_exceptions.py +0 -0
  248. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_file_upload.py +0 -0
  249. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_init_device_code.py +0 -0
  250. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_install_guard.py +0 -0
  251. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_lease_sigterm_release.py +0 -0
  252. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_mark_read_batch.py +0 -0
  253. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_marketplace_ratings.py +0 -0
  254. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_migration_integrity.py +0 -0
  255. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_realtime_event_freshness.py +0 -0
  256. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_rls_cross_tenant.py +0 -0
  257. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_rpc_grants.py +0 -0
  258. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_rpc_migrations.py +0 -0
  259. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_run_agent_dry_run.py +0 -0
  260. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_run_agent_no_server_import.py +0 -0
  261. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_security_regressions.py +0 -0
  262. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_self_update_user_site.py +0 -0
  263. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_sentinel.py +0 -0
  264. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_setup_path.py +0 -0
  265. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_sleep_signals.py +0 -0
  266. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_status_enum_coverage.py +0 -0
  267. {meshcode-2.11.75 → meshcode-2.11.76}/tests/test_stay_on_loop_hook.py +0 -0
  268. {meshcode-2.11.75 → meshcode-2.11.76}/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.75
3
+ Version: 2.11.76
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.75"
2
+ __version__ = "2.11.76"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -212,10 +212,13 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
212
212
  # background, NO terminal — UNIVERSAL macOS/Linux/Windows (task c1a6c6a8, mesh-dev specs).
213
213
  # Clean env (a stale CLAUDECODE aborts `meshcode run`); keep crash logs in a per-agent logfile
214
214
  # (NOT DEVNULL — we want to debug headless agents that die on boot).
215
+ # task 14782bb4: spawned agents DO auto-update (non-blocking) — strip any inherited
216
+ # NO_*UPDATE so the agent's launch runs the bg updater (the daemon launcher stays disabled).
217
+ # No hang: run_agent now uses the NON-blocking updater + we spawn via `python -m meshcode`
218
+ # (not the .exe shim) so a bg `pip install -U` can replace meshcode.exe on Windows.
215
219
  env = {k: v for k, v in os.environ.items()
216
- if k not in ("CLAUDECODE", "CLAUDE_CODE_SESSION")}
217
- env["MESHCODE_NO_AUTO_UPDATE"] = "1"
218
- env["MESHCODE_NO_UPDATE"] = "1" # belt-and-suspenders: var honored pre-2.11.74 (no launch-hang)
220
+ if k not in ("CLAUDECODE", "CLAUDE_CODE_SESSION",
221
+ "MESHCODE_NO_UPDATE", "MESHCODE_NO_AUTO_UPDATE")}
219
222
  log_dir = STATE_DIR / "logs"
220
223
  try:
221
224
  log_dir.mkdir(parents=True, exist_ok=True)
@@ -236,11 +239,11 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
236
239
  }
237
240
  if sys.platform == "win32":
238
241
  # CREATE_NO_WINDOW (0x08000000) | DETACHED_PROCESS (0x08): no console window, fully
239
- # detached from this daemon. Use the console entry-point (meshcode.exe), NEVER pythonw
240
- # (it swallows stderr). Ensure venv Scripts + System32 on PATH (Windows PATH cap ~32k).
242
+ # detached from this daemon. NEVER pythonw (swallows stderr). Ensure venv Scripts +
243
+ # System32 on PATH (Windows PATH cap ~32k).
241
244
  kwargs["creationflags"] = 0x08000000 | 0x00000008
242
245
  try:
243
- scripts = str(Path(bin_).resolve().parent)
246
+ scripts = str(Path(sys.executable).resolve().parent) # venv Scripts dir
244
247
  except Exception:
245
248
  scripts = ""
246
249
  sysroot = os.environ.get("SystemRoot", r"C:\Windows")
@@ -250,7 +253,9 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
250
253
  # POSIX: detach into its own session so it survives the daemon + has no controlling tty.
251
254
  kwargs["start_new_session"] = True
252
255
  try:
253
- subprocess.Popen([bin_, "run", target], **kwargs)
256
+ # `python -m meshcode` (NOT the meshcode.exe shim) so the .exe isn't held open by the
257
+ # agent -> a background `pip install -U` can replace it on Windows (task 14782bb4 #4).
258
+ subprocess.Popen([sys.executable, "-m", "meshcode", "run", target], **kwargs)
254
259
  _log(f"spawned {target} HEADLESS (no window, {sys.platform}; log={log_path})")
255
260
  return True
256
261
  except Exception as e:
@@ -267,13 +272,15 @@ def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
267
272
  # command PER-PLATFORM (mesh-core FIX1, 0x80070002): cmd.exe uses & (NOT ';'), set "V=" to clear
268
273
  # (NOT bash unset/export), double-quotes (NOT shlex POSIX), no exec — else Windows Terminal splits
269
274
  # on ';' -> file-not-found. Layered ON TOP of the 2.11.72 headless-flags fix (visible branch only).
275
+ # task 14782bb4: CLEAR any inherited NO_*UPDATE (so the agent auto-updates non-blocking) and run
276
+ # via `python -m meshcode` (NOT the .exe shim) so a bg `pip install -U` can replace meshcode.exe.
270
277
  if sys.platform == "win32":
271
278
  cmd = (f'set "CLAUDECODE=" & set "CLAUDE_CODE_SESSION=" & '
272
- f'set "MESHCODE_NO_AUTO_UPDATE=1" & set "MESHCODE_NO_UPDATE=1" & '
273
- f'"{bin_}" run "{target}"')
279
+ f'set "MESHCODE_NO_UPDATE=" & set "MESHCODE_NO_AUTO_UPDATE=" & '
280
+ f'"{sys.executable}" -m meshcode run "{target}"')
274
281
  else:
275
- cmd = (f"unset CLAUDECODE CLAUDE_CODE_SESSION; export MESHCODE_NO_AUTO_UPDATE=1; "
276
- f"export MESHCODE_NO_UPDATE=1; exec {shlex.quote(bin_)} run {shlex.quote(target)}")
282
+ cmd = (f"unset CLAUDECODE CLAUDE_CODE_SESSION MESHCODE_NO_UPDATE MESHCODE_NO_AUTO_UPDATE; "
283
+ f"exec {shlex.quote(sys.executable)} -m meshcode run {shlex.quote(target)}")
277
284
  try:
278
285
  from meshcode import protocol_handler as _ph
279
286
  ok, info = _ph._spawn_terminal(cmd)
@@ -698,13 +698,14 @@ def run(agent: str, project: Optional[str] = None, editor_override: Optional[str
698
698
  # Propagate autonomous flag to the editor + MCP child via env var.
699
699
  if autonomous:
700
700
  os.environ["MESHCODE_AUTONOMOUS"] = "1"
701
- # Blocking self-update: pip install -U meshcode BEFORE spawning the
702
- # editor so the child MCP subprocess loads the freshest server.py off
703
- # disk. Skip in dry-run CI / pre-publish smokes must not fork pip.
704
- # See claude_update_blocking below for the npm/Claude Code half.
701
+ # NON-BLOCKING self-update (task 14782bb4): kick a detached background `pip install -U meshcode`
702
+ # and return IMMEDIATELY — the launch NEVER waits/hangs (Samuel req; the old blocking variant hung
703
+ # every Windows launch on WinError 32). This launch runs the current version; the next spawn/recycle
704
+ # picks up the new one (two-launch async). Skip in dry-run (CI/pre-publish smokes must not fork pip).
705
+ # Fully opt-out via MESHCODE_NO_UPDATE / MESHCODE_NO_AUTO_UPDATE (handled inside check_and_maybe_update).
705
706
  if not dry_run:
706
707
  try:
707
- self_update.check_and_maybe_update_blocking()
708
+ self_update.check_and_maybe_update()
708
709
  except Exception:
709
710
  pass
710
711
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.75
3
+ Version: 2.11.76
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -5,9 +5,6 @@ meshcode/__main__.py
5
5
  meshcode/_stop_hook_template.py
6
6
  meshcode/ascii_art.py
7
7
  meshcode/atomic_push.py
8
- meshcode/claude_update 2.py
9
- meshcode/claude_update 3.py
10
- meshcode/claude_update 4.py
11
8
  meshcode/claude_update.py
12
9
  meshcode/cli.py
13
10
  meshcode/comms_v4.py
@@ -17,9 +14,6 @@ meshcode/date_parse.py
17
14
  meshcode/doctor.py
18
15
  meshcode/error_hints.py
19
16
  meshcode/exceptions.py
20
- meshcode/hostd 2.py
21
- meshcode/hostd 3.py
22
- meshcode/hostd 4.py
23
17
  meshcode/hostd.py
24
18
  meshcode/invites.py
25
19
  meshcode/launcher.py
@@ -34,8 +28,6 @@ meshcode/secrets.py
34
28
  meshcode/self_update.py
35
29
  meshcode/setup_clients.py
36
30
  meshcode/supervisor.py
37
- meshcode/up 2.py
38
- meshcode/up 3.py
39
31
  meshcode/up.py
40
32
  meshcode/upload.py
41
33
  meshcode-backend-wt/comms_v4.py
@@ -212,10 +204,6 @@ tests/test_auto_update_hardening.py
212
204
  tests/test_autonomous_closegap_1.py
213
205
  tests/test_autonomous_closegap_2.py
214
206
  tests/test_autonomous_closegap_3.py
215
- tests/test_autonomous_prompt_inject 2.py
216
- tests/test_autonomous_prompt_inject 3.py
217
- tests/test_autonomous_prompt_inject 4.py
218
- tests/test_autonomous_prompt_inject 5.py
219
207
  tests/test_autonomous_prompt_inject.py
220
208
  tests/test_boot_bug_regression.py
221
209
  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.75"
7
+ version = "2.11.76"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,509 +0,0 @@
1
- """meshcode hostd — host-side daemon: respawn + recycle managed agents.
2
-
3
- Complements supervisor.py (launchd KeepAlive = process-exit restart only).
4
- hostd polls the CLOUD for staleness (heartbeat) + recycle triggers that
5
- launchd cannot see:
6
-
7
- - RESPAWN: agent's heartbeat is stale (process/daemon-thread dead) and
8
- desired_state='running' → relaunch. Loop-guard cap 3/10min → 'crashed'.
9
- - RECYCLE: managed agent exceeds its recycle policy (context% or uptime)
10
- → relaunch at a task boundary (never mid-task). Recorded separately
11
- from the crash cap.
12
-
13
- Contract (mig366/367/368/369, DBA-cleared):
14
- mc_register_host, mc_agents_needing_respawn, mc_record_respawn,
15
- mc_record_recycle, mc_set_desired_state, mc_host_recycle_policy.
16
-
17
- Source of truth is the cloud (mc_agents / mc_host_config), never local
18
- folders. host_id lives in ~/.meshcode/host_id (generated once).
19
-
20
- meshcode hostd run — run the daemon loop (foreground)
21
- meshcode hostd status — show this host's managed agents
22
- meshcode hostd install — install launchd trigger-start plist
23
- meshcode hostd uninstall — remove it
24
-
25
- Design invariants:
26
- - NEVER respawn desired_state != 'running' (NULL=unmanaged, stopped=
27
- intentional, crashed=gave-up). The cloud RPC enforces this; we trust it.
28
- - Single retry / capped respawn — never an infinite relaunch loop.
29
- - Recycle only at task boundary: we relaunch a recycle candidate only
30
- when it is NOT actively working (status not in working/online-busy) OR
31
- the agent has self-flagged 'recycling'. Mid-task agents are skipped.
32
- """
33
- from __future__ import annotations
34
-
35
- import json
36
- import os
37
- import shlex
38
- import subprocess
39
- import sys
40
- import time
41
- import urllib.request
42
- import uuid
43
- from pathlib import Path
44
- from typing import Optional
45
-
46
- STATE_DIR = Path.home() / ".meshcode"
47
- HOST_ID_PATH = STATE_DIR / "host_id"
48
- LOG_PATH = STATE_DIR / "hostd.log"
49
-
50
- POLL_INTERVAL_SEC = 45
51
- STALE_SECONDS = 150 # heartbeat liveness cutoff (QA #3)
52
- RESPAWN_TIMEOUT_SEC = 30
53
- # statuses that mean "actively working" → never recycle mid-task
54
- BUSY_STATUSES = {"working", "online", "busy"}
55
-
56
-
57
- def _log(msg: str) -> None:
58
- line = f"[{time.strftime('%Y-%m-%d %H:%M:%S')}] {msg}"
59
- print(line, file=sys.stderr)
60
- try:
61
- STATE_DIR.mkdir(parents=True, exist_ok=True)
62
- with open(LOG_PATH, "a", encoding="utf-8") as f:
63
- f.write(line + "\n")
64
- except Exception:
65
- pass
66
-
67
-
68
- def get_host_id() -> str:
69
- """Read ~/.meshcode/host_id, generating a stable UUID on first use."""
70
- try:
71
- if HOST_ID_PATH.exists():
72
- v = HOST_ID_PATH.read_text(encoding="utf-8").strip()
73
- if v:
74
- return v
75
- except Exception:
76
- pass
77
- new_id = str(uuid.uuid4())
78
- try:
79
- STATE_DIR.mkdir(parents=True, exist_ok=True)
80
- HOST_ID_PATH.write_text(new_id, encoding="utf-8")
81
- except Exception:
82
- pass
83
- return new_id
84
-
85
-
86
- # ------------------------------------------------------------------
87
- # Cloud RPC (PostgREST) — self-contained urllib, no internal client dep
88
- # ------------------------------------------------------------------
89
-
90
- def _supabase_cfg() -> tuple:
91
- # env first (explicit override), then fall back to the SAME resolution the
92
- # rest of the CLI uses (comms_v4: env -> ~/.meshcode/env -> baked publishable
93
- # default). hostd runs under launchd with NO env, so the old env-only lookup
94
- # left it unable to reach the cloud at all ("SUPABASE_URL/KEY not set").
95
- url = os.environ.get("SUPABASE_URL", "").rstrip("/")
96
- key = os.environ.get("SUPABASE_KEY") or os.environ.get("MESHCODE_SUPABASE_ANON_KEY") or ""
97
- if url and key:
98
- return url, key
99
- try:
100
- from meshcode import comms_v4 as _c # type: ignore
101
- return (url or _c.SUPABASE_URL).rstrip("/"), (key or _c.SUPABASE_KEY)
102
- except Exception:
103
- return url, key
104
-
105
-
106
- def _api_key() -> Optional[str]:
107
- """The meshcode agent/user api key, used as p_api_key in mc_ RPCs.
108
-
109
- Resolution mirrors comms_v4._load_api_key_for_cli so hostd authenticates
110
- the same way `meshcode run` does. CRITICAL: launchd starts hostd with NO
111
- environment, so the key MUST be resolvable from the keychain (where
112
- `meshcode login` stores it). The old code only checked env + a
113
- wrong-shaped profiles.json, so launchd-started hostd always FATAL'd
114
- ("no api key") and never registered -> dashboard launch had no host.
115
- """
116
- # 1) explicit env override
117
- k = os.environ.get("MESHCODE_API_KEY")
118
- if k:
119
- return k
120
- # 2) keychain — the actual store `meshcode login` writes to. Same source
121
- # comms_v4._load_api_key_for_cli() uses. This is the launchd fix.
122
- try:
123
- from meshcode import secrets as _secrets # type: ignore
124
- profile = os.environ.get("MESHCODE_KEYCHAIN_PROFILE") or _secrets.DEFAULT_PROFILE
125
- key = _secrets.get_api_key(profile=profile) or ""
126
- if key:
127
- return key
128
- except Exception:
129
- pass
130
- # 3) legacy per-profile state file fallback. Handles BOTH the flat
131
- # {name: {api_key}} and nested {"profiles": {name: {api_key}}} shapes
132
- # (the flat-only scan silently missed the nested writer).
133
- try:
134
- prof = STATE_DIR / "profiles.json"
135
- if prof.exists():
136
- data = json.loads(prof.read_text(encoding="utf-8"))
137
- roots = []
138
- if isinstance(data, dict):
139
- roots.append(data)
140
- if isinstance(data.get("profiles"), dict):
141
- roots.append(data["profiles"])
142
- for root in roots:
143
- for v in root.values():
144
- if isinstance(v, dict) and v.get("api_key"):
145
- return v["api_key"]
146
- except Exception:
147
- pass
148
- return None
149
-
150
-
151
- def _rpc(fn: str, payload: dict) -> Optional[dict]:
152
- """Call a PostgREST RPC. Returns parsed JSON or None on any failure."""
153
- url, key = _supabase_cfg()
154
- if not url or not key:
155
- _log("WARN: SUPABASE_URL/KEY not set — cannot reach cloud")
156
- return None
157
- try:
158
- req = urllib.request.Request(
159
- f"{url}/rest/v1/rpc/{fn}",
160
- data=json.dumps(payload).encode("utf-8"),
161
- headers={
162
- "apikey": key,
163
- "Authorization": f"Bearer {key}",
164
- "Content-Type": "application/json",
165
- },
166
- method="POST",
167
- )
168
- with urllib.request.urlopen(req, timeout=15) as resp:
169
- return json.loads(resp.read().decode("utf-8"))
170
- except Exception as e:
171
- _log(f"WARN: rpc {fn} failed: {e}")
172
- return None
173
-
174
-
175
- # ------------------------------------------------------------------
176
- # Respawn
177
- # ------------------------------------------------------------------
178
-
179
- def _meshcode_bin() -> str:
180
- cand = Path(sys.argv[0]).resolve()
181
- if cand.exists() and cand.name.startswith("meshcode"):
182
- return str(cand)
183
- return "meshcode"
184
-
185
-
186
- def _spawn_agent(project: str, agent: str, headless: bool = False) -> bool:
187
- """Relaunch `meshcode run <project>/<agent>`.
188
-
189
- headless=False (default): VISIBLE terminal window. Samuel must SEE it — we
190
- delegate to protocol_handler._spawn_terminal, the canonical visible spawner
191
- (macOS: osascript-if-Automation-granted else `open` a .command wrapper, no TCC
192
- needed; Linux/Windows: native terminals). On failure we WARN loudly and return
193
- False — we NEVER silently fall back to headless when visible was asked.
194
-
195
- headless=True (Fleet Control mig404 per-agent flag): background process, NO
196
- window — for fleet agents that don't need a terminal (like the qa launch).
197
- """
198
- target = f"{project}/{agent}"
199
- bin_ = _meshcode_bin()
200
- if headless:
201
- # background, NO terminal — UNIVERSAL macOS/Linux/Windows (task c1a6c6a8, mesh-dev specs).
202
- # Clean env (a stale CLAUDECODE aborts `meshcode run`); keep crash logs in a per-agent logfile
203
- # (NOT DEVNULL — we want to debug headless agents that die on boot).
204
- env = {k: v for k, v in os.environ.items()
205
- if k not in ("CLAUDECODE", "CLAUDE_CODE_SESSION")}
206
- env["MESHCODE_NO_AUTO_UPDATE"] = "1"
207
- log_dir = STATE_DIR / "logs"
208
- try:
209
- log_dir.mkdir(parents=True, exist_ok=True)
210
- except Exception:
211
- pass
212
- safe = f"{project}__{agent}".replace("/", "_").replace("\\", "_")
213
- log_path = log_dir / f"{safe}.headless.log"
214
- logf = None
215
- try:
216
- logf = open(log_path, "ab")
217
- except Exception:
218
- logf = None
219
- kwargs = {
220
- "stdin": subprocess.DEVNULL,
221
- "stdout": (logf if logf is not None else subprocess.DEVNULL),
222
- "stderr": subprocess.STDOUT,
223
- "env": env,
224
- }
225
- if sys.platform == "win32":
226
- # CREATE_NO_WINDOW (0x08000000) | DETACHED_PROCESS (0x08): no console window, fully
227
- # detached from this daemon. Use the console entry-point (meshcode.exe), NEVER pythonw
228
- # (it swallows stderr). Ensure venv Scripts + System32 on PATH (Windows PATH cap ~32k).
229
- kwargs["creationflags"] = 0x08000000 | 0x00000008
230
- try:
231
- scripts = str(Path(bin_).resolve().parent)
232
- except Exception:
233
- scripts = ""
234
- sysroot = os.environ.get("SystemRoot", r"C:\Windows")
235
- base_path = os.pathsep.join(p for p in (scripts, sysroot + r"\System32", sysroot) if p)
236
- env["PATH"] = (base_path + os.pathsep + env.get("PATH", ""))[:30000]
237
- else:
238
- # POSIX: detach into its own session so it survives the daemon + has no controlling tty.
239
- kwargs["start_new_session"] = True
240
- try:
241
- subprocess.Popen([bin_, "run", target], **kwargs)
242
- _log(f"spawned {target} HEADLESS (no window, {sys.platform}; log={log_path})")
243
- return True
244
- except Exception as e:
245
- _log(f"WARN: headless spawn {target} failed: {e}")
246
- return False
247
- finally:
248
- # child inherited its own fd; safe to drop the daemon's handle.
249
- if logf is not None:
250
- try:
251
- logf.close()
252
- except Exception:
253
- pass
254
- # env hygiene (item1 RC): a stale CLAUDECODE aborts `meshcode run` (exit2).
255
- # Prefix the shell command so the spawned terminal starts with a clean env.
256
- cmd = (f"unset CLAUDECODE CLAUDE_CODE_SESSION; export MESHCODE_NO_AUTO_UPDATE=1; "
257
- f"exec {shlex.quote(bin_)} run {shlex.quote(target)}")
258
- try:
259
- from meshcode import protocol_handler as _ph
260
- ok, info = _ph._spawn_terminal(cmd)
261
- if ok:
262
- _log(f"spawned {target} in visible terminal ({info})")
263
- return True
264
- _log(f"WARN: visible spawn {target} FAILED ({info}) — agent NOT launched. "
265
- f"macOS: grant Terminal Automation (System Settings > Privacy & Security "
266
- f"> Automation), or ensure `open` works. NOT falling back to headless.")
267
- return False
268
- except Exception as e:
269
- _log(f"WARN: spawn {target} failed: {e}")
270
- return False
271
-
272
-
273
- def _do_respawns(api_key: str, host_id: str) -> int:
274
- """One respawn sweep. Returns number relaunched."""
275
- res = _rpc("mc_agents_needing_respawn",
276
- {"p_api_key": api_key, "p_host_id": host_id, "p_stale_seconds": STALE_SECONDS})
277
- if not res or not res.get("ok"):
278
- return 0
279
- n = 0
280
- for c in res.get("candidates", []):
281
- proj, agent = c.get("project_name"), c.get("agent")
282
- if not proj or not agent:
283
- continue
284
- if not c.get("respawn_allowed", True):
285
- # mig404: not allowed = rate-limited (<60s since last respawn) or at the cap.
286
- # mc_record_respawn v2 sets desired_state='crashed' ATOMICALLY at the cap, so we
287
- # do NOT re-record here (that would inflate the count on a mere rate-limit skip).
288
- _log(f"SKIP respawn {proj}/{agent}: not allowed (count={c.get('respawn_count')}, rate-limited/at-cap)")
289
- continue
290
- _log(f"RESPAWN {proj}/{agent} (stale {c.get('heartbeat_age_s')}s, count={c.get('respawn_count')})")
291
- if _spawn_agent(proj, agent, headless=bool(c.get("headless"))):
292
- rec = _rpc("mc_record_respawn",
293
- {"p_api_key": api_key, "p_project_id": c.get("project_id"), "p_agent_name": agent})
294
- # mig404: give-up is ATOMIC inside mc_record_respawn (sets desired_state='crashed').
295
- # The old mc_set_desired_state(p_state=crashed) call passed NO agent id = no-op; dropped.
296
- if rec and rec.get("give_up"):
297
- _log(f"ALERT {proj}/{agent}: respawn cap hit — marked crashed (atomic). No more respawns.")
298
- n += 1
299
- return n
300
-
301
-
302
- # ------------------------------------------------------------------
303
- # Recycle — relaunch a managed agent at a TASK BOUNDARY when it exceeds
304
- # its recycle policy. Uptime-recycle is daemon-driven (needs mig370's
305
- # project_name in the roster). Context-recycle is agent-cooperative (the
306
- # agent self-persists handoff + exits at a boundary; our respawn path then
307
- # relaunches it). Recorded via mc_record_recycle — NEVER counted against
308
- # the crash respawn cap.
309
- # ------------------------------------------------------------------
310
-
311
- _HOSTD_STATE_PATH = STATE_DIR / "hostd_state.json"
312
-
313
-
314
- def _load_state() -> dict:
315
- try:
316
- if _HOSTD_STATE_PATH.exists():
317
- return json.loads(_HOSTD_STATE_PATH.read_text(encoding="utf-8"))
318
- except Exception:
319
- pass
320
- return {}
321
-
322
-
323
- def _save_state(st: dict) -> None:
324
- try:
325
- STATE_DIR.mkdir(parents=True, exist_ok=True)
326
- _HOSTD_STATE_PATH.write_text(json.dumps(st), encoding="utf-8")
327
- except Exception:
328
- pass
329
-
330
-
331
- def _do_recycles(api_key: str, host_id: str) -> int:
332
- """Uptime-based recycle at task boundary. Returns number recycled."""
333
- cfg = _rpc("mc_host_config_get", {"p_api_key": api_key, "p_host_id": host_id})
334
- if not cfg or not cfg.get("ok"):
335
- return 0
336
- pol = _rpc("mc_host_recycle_policy", {"p_api_key": api_key, "p_host_id": host_id})
337
- mode = (pol or {}).get("recycle_mode")
338
- value = (pol or {}).get("recycle_value")
339
- if mode != "time" or not value:
340
- return 0 # context-recycle is agent-cooperative; only time is daemon-driven
341
- st = _load_state()
342
- now = time.time()
343
- n = 0
344
- seen = set()
345
- for a in cfg.get("agents", []):
346
- if a.get("desired_state") != "running":
347
- continue
348
- proj, agent = a.get("project_name"), a.get("name")
349
- if not proj or not agent:
350
- continue
351
- key = f"{a.get('project_id')}/{agent}"
352
- seen.add(key)
353
- first = st.get(key)
354
- if first is None:
355
- st[key] = now # start the uptime clock on first observation
356
- continue
357
- if (a.get("status") or "") in BUSY_STATUSES:
358
- continue # task boundary only — never mid-task
359
- if (now - first) >= float(value) * 3600.0:
360
- _log(f"RECYCLE {proj}/{agent} (uptime {(now-first)/3600:.1f}h >= {value}h)")
361
- # Server-authorized clean-exit (task 548c863e, mig 364): SIGNAL the
362
- # recycle instead of spawning. A direct _spawn_agent here duplicates
363
- # the still-alive process. mc_request_recycle sets a flag; the
364
- # agent's wait-loop consumes it, returns must_exit/reason=recycle,
365
- # exits CLEAN (Stop-hook writes the handoff), then the respawn path
366
- # (_do_respawns) relaunches it fresh. Recorded via mc_record_recycle
367
- # so it's NEVER counted against the crash respawn cap.
368
- req = _rpc("mc_request_recycle",
369
- {"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
370
- if req and req.get("ok") and req.get("requested"):
371
- _rpc("mc_record_recycle",
372
- {"p_api_key": api_key, "p_project_id": a.get("project_id"), "p_agent_name": agent})
373
- st[key] = now # reset clock after signaling recycle
374
- n += 1
375
- elif req and req.get("ok") and not req.get("requested"):
376
- # Agent flipped busy between the roster read and the request —
377
- # skip; retry next sweep at the next task boundary.
378
- _log(f"SKIP recycle {proj}/{agent}: {req.get('reason','not_requested')}")
379
- # prune state for agents no longer managed on this host
380
- for k in [k for k in st if k not in seen]:
381
- st.pop(k, None)
382
- _save_state(st)
383
- return n
384
-
385
-
386
- # ------------------------------------------------------------------
387
- # launchd trigger-start (macOS) — extends supervisor.py pattern.
388
- # The hostd daemon is NOT autostart-at-login: RunAtLoad=false. It is
389
- # kicked off on demand (button -> meshcode:// -> `launchctl kickstart`)
390
- # and KeepAlive keeps it alive while it runs. One job per host.
391
- # ------------------------------------------------------------------
392
-
393
- _HOSTD_PLIST_LABEL = "io.meshcode.hostd"
394
-
395
-
396
- def _hostd_plist_path():
397
- return Path.home() / "Library" / "LaunchAgents" / f"{_HOSTD_PLIST_LABEL}.plist"
398
-
399
-
400
- def _hostd_plist_xml() -> str:
401
- import shutil
402
- mc = shutil.which("meshcode") or f"{sys.executable} -m meshcode"
403
- args = (mc.split() + ["hostd", "run"]) if " -m " in mc else [mc, "hostd", "run"]
404
- args_xml = "\n".join(f" <string>{a}</string>" for a in args)
405
- logdir = STATE_DIR / "logs"
406
- logdir.mkdir(parents=True, exist_ok=True)
407
- return f"""<?xml version="1.0" encoding="UTF-8"?>
408
- <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
409
- <plist version="1.0">
410
- <dict>
411
- <key>Label</key><string>{_HOSTD_PLIST_LABEL}</string>
412
- <key>ProgramArguments</key>
413
- <array>
414
- {args_xml}
415
- </array>
416
- <key>RunAtLoad</key><false/>
417
- <key>KeepAlive</key><true/>
418
- <key>ThrottleInterval</key><integer>10</integer>
419
- <key>StandardOutPath</key><string>{logdir / "hostd.stdout.log"}</string>
420
- <key>StandardErrorPath</key><string>{logdir / "hostd.stderr.log"}</string>
421
- <key>EnvironmentVariables</key>
422
- <dict>
423
- <key>PATH</key><string>/usr/local/bin:/usr/bin:/bin:{os.path.dirname(sys.executable)}</string>
424
- <key>MESHCODE_NO_AUTO_UPDATE</key><string>1</string>
425
- </dict>
426
- </dict>
427
- </plist>
428
- """
429
-
430
-
431
- def _hostd_install() -> int:
432
- import platform
433
- if platform.system() != "Darwin":
434
- print("[hostd] launchd install is macOS only (Linux: use systemd --user; or run `meshcode hostd run` under your own supervisor).", file=sys.stderr)
435
- return 2
436
- plist = _hostd_plist_path()
437
- plist.parent.mkdir(parents=True, exist_ok=True)
438
- plist.write_text(_hostd_plist_xml(), encoding="utf-8")
439
- subprocess.run(["launchctl", "unload", str(plist)], capture_output=True, text=True)
440
- r = subprocess.run(["launchctl", "load", str(plist)], capture_output=True, text=True)
441
- if r.returncode != 0:
442
- print(f"[hostd] launchctl load failed: {r.stderr.strip()}", file=sys.stderr)
443
- return 1
444
- print(f"[hostd] installed (RunAtLoad=false, trigger-start). plist: {plist}")
445
- print(f"[hostd] start now: launchctl kickstart gui/$(id -u)/{_HOSTD_PLIST_LABEL}")
446
- return 0
447
-
448
-
449
- def _hostd_uninstall() -> int:
450
- plist = _hostd_plist_path()
451
- if not plist.exists():
452
- print("[hostd] not installed")
453
- return 0
454
- subprocess.run(["launchctl", "unload", str(plist)], capture_output=True, text=True)
455
- plist.unlink(missing_ok=True)
456
- print("[hostd] uninstalled")
457
- return 0
458
-
459
-
460
- def cmd_hostd(args: list) -> int:
461
- """Entry point for `meshcode hostd ...`."""
462
- if not args or args[0] in ("-h", "--help"):
463
- print(__doc__)
464
- return 0
465
- sub = args[0]
466
- host_id = get_host_id()
467
- api_key = _api_key()
468
-
469
- if sub == "status":
470
- if not api_key:
471
- print("[hostd] no api key configured", file=sys.stderr)
472
- return 1
473
- res = _rpc("mc_agents_needing_respawn",
474
- {"p_api_key": api_key, "p_host_id": host_id, "p_stale_seconds": STALE_SECONDS})
475
- print(json.dumps({"host_id": host_id, "needing_respawn": res}, indent=2))
476
- return 0
477
-
478
- if sub == "install":
479
- return _hostd_install()
480
-
481
- if sub == "uninstall":
482
- return _hostd_uninstall()
483
-
484
- if sub == "run":
485
- if not api_key:
486
- _log("FATAL: no api key — run `meshcode login` (key is read from the keychain)")
487
- return 1
488
- # Register this host in mc_host_config so the dashboard can list it as a
489
- # launch target (Path2 canonical: dashboard -> mc_host_set_agents ->
490
- # mc_agents.desired_state='running' -> this daemon polls + spawns).
491
- # mc_host_config_set upserts (host_id, owner_user_id) idempotently.
492
- _reg = _rpc("mc_host_config_set", {"p_api_key": api_key, "p_host_id": host_id})
493
- if _reg and _reg.get("ok"):
494
- _log(f"registered host {host_id} in mc_host_config")
495
- else:
496
- _log(f"WARN: host registration failed (dashboard may not list this host): {_reg}")
497
- _log(f"hostd starting — host_id={host_id} interval={POLL_INTERVAL_SEC}s stale={STALE_SECONDS}s")
498
- while True:
499
- try:
500
- relaunched = _do_respawns(api_key, host_id)
501
- recycled = _do_recycles(api_key, host_id)
502
- if relaunched or recycled:
503
- _log(f"sweep done — {relaunched} respawned, {recycled} recycled")
504
- except Exception as e:
505
- _log(f"WARN: sweep error: {e}")
506
- time.sleep(POLL_INTERVAL_SEC)
507
-
508
- print(f"[hostd] unknown subcommand: {sub}", file=sys.stderr)
509
- return 1