meshcode 2.10.101__tar.gz → 2.11.1__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 (209) hide show
  1. {meshcode-2.10.101 → meshcode-2.11.1}/PKG-INFO +1 -1
  2. meshcode-2.11.1/meshcode/__init__.py +82 -0
  3. meshcode-2.11.1/meshcode/_stop_hook_template.py +514 -0
  4. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode/comms_v4.py +9 -1
  5. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode/meshcode_mcp/backend.py +15 -4
  6. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode/meshcode_mcp/server.py +273 -14
  7. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/run_agent.py +23 -0
  8. meshcode-2.11.1/meshcode/scripts/check_secrets.py +134 -0
  9. meshcode-2.11.1/meshcode/scripts/race_rate_harness.py +308 -0
  10. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode/setup_clients.py +164 -260
  11. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode.egg-info/PKG-INFO +1 -1
  12. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode.egg-info/SOURCES.txt +3 -0
  13. {meshcode-2.10.101 → meshcode-2.11.1}/pyproject.toml +1 -1
  14. meshcode-2.10.101/meshcode/__init__.py +0 -82
  15. {meshcode-2.10.101 → meshcode-2.11.1}/README.md +0 -0
  16. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/ascii_art.py +0 -0
  17. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/cli.py +0 -0
  18. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/compat.py +0 -0
  19. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode/daemon.py +0 -0
  20. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/error_hints.py +0 -0
  21. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/exceptions.py +0 -0
  22. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/invites.py +0 -0
  23. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/launcher.py +0 -0
  24. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/launcher_install.py +0 -0
  25. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/meshcode_mcp/__init__.py +0 -0
  26. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/meshcode_mcp/__main__.py +0 -0
  27. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/meshcode_mcp/realtime.py +0 -0
  28. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/meshcode_mcp/test_backend.py +0 -0
  29. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  30. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  31. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/preferences.py +0 -0
  32. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode/protocol_handler.py +0 -0
  33. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/protocol_v2.py +0 -0
  34. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/quickstart.py +0 -0
  35. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/secrets.py +0 -0
  36. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/self_update.py +0 -0
  37. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/supervisor.py +0 -0
  38. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/meshcode/upload.py +0 -0
  39. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-backend-wt}/comms_v4.py +0 -0
  40. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  41. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/ascii_art.py +0 -0
  42. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/cli.py +0 -0
  43. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-backend-wt}/meshcode/comms_v4.py +0 -0
  44. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/compat.py +0 -0
  45. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/error_hints.py +0 -0
  46. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/exceptions.py +0 -0
  47. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/invites.py +0 -0
  48. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/launcher.py +0 -0
  49. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/launcher_install.py +0 -0
  50. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/meshcode_mcp/__init__.py +0 -0
  51. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/meshcode_mcp/__main__.py +0 -0
  52. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-backend-wt}/meshcode/meshcode_mcp/backend.py +0 -0
  53. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/meshcode_mcp/realtime.py +0 -0
  54. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-backend-wt}/meshcode/meshcode_mcp/server.py +0 -0
  55. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/meshcode_mcp/test_backend.py +0 -0
  56. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  57. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  58. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/preferences.py +0 -0
  59. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/protocol_v2.py +0 -0
  60. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/quickstart.py +0 -0
  61. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/run_agent.py +0 -0
  62. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/secrets.py +0 -0
  63. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/self_update.py +0 -0
  64. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-backend-wt}/meshcode/setup_clients.py +0 -0
  65. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/supervisor.py +0 -0
  66. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/meshcode/upload.py +0 -0
  67. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-backend-wt}/scripts/sentinel.py +0 -0
  68. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_core.py +0 -0
  69. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_cross_agent_messaging.py +0 -0
  70. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_esc_deaf_state.py +0 -0
  71. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_exceptions.py +0 -0
  72. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_mark_read_batch.py +0 -0
  73. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_migration_integrity.py +0 -0
  74. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_realtime_event_freshness.py +0 -0
  75. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_rls_cross_tenant.py +0 -0
  76. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-backend-wt}/tests/test_rpc_migrations.py +0 -0
  77. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_security_regressions.py +0 -0
  78. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_sentinel.py +0 -0
  79. {meshcode-2.10.101 → meshcode-2.11.1/meshcode-backend-wt}/tests/test_status_enum_coverage.py +0 -0
  80. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/__init__.py +0 -0
  81. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/ascii_art.py +0 -0
  82. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/cli.py +0 -0
  83. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/comms_v4.py +0 -0
  84. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/compat.py +0 -0
  85. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/error_hints.py +0 -0
  86. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/exceptions.py +0 -0
  87. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/invites.py +0 -0
  88. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/launcher.py +0 -0
  89. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/launcher_install.py +0 -0
  90. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/meshcode_mcp/__init__.py +0 -0
  91. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/meshcode_mcp/__main__.py +0 -0
  92. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/meshcode_mcp/backend.py +0 -0
  93. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/meshcode_mcp/realtime.py +0 -0
  94. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/meshcode_mcp/server.py +0 -0
  95. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/meshcode_mcp/test_backend.py +0 -0
  96. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  97. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  98. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/preferences.py +0 -0
  99. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/protocol_v2.py +0 -0
  100. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/quickstart.py +0 -0
  101. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/run_agent.py +0 -0
  102. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/secrets.py +0 -0
  103. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/self_update.py +0 -0
  104. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/setup_clients.py +0 -0
  105. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/supervisor.py +0 -0
  106. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt/build/lib}/meshcode/upload.py +0 -0
  107. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/comms_v4.py +0 -0
  108. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  109. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  110. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/cli.py +0 -0
  111. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-noun-wt}/meshcode/comms_v4.py +0 -0
  112. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/compat.py +0 -0
  113. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  114. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  115. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/invites.py +0 -0
  116. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  117. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  118. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  119. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  120. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-noun-wt}/meshcode/meshcode_mcp/backend.py +0 -0
  121. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  122. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-noun-wt}/meshcode/meshcode_mcp/server.py +0 -0
  123. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  124. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  125. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  126. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  127. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  128. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  129. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  130. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  131. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  132. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-noun-wt}/meshcode/setup_clients.py +0 -0
  133. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  134. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/meshcode/upload.py +0 -0
  135. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  136. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_core.py +0 -0
  137. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_cross_agent_messaging.py +0 -0
  138. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_esc_deaf_state.py +0 -0
  139. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_exceptions.py +0 -0
  140. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_mark_read_batch.py +0 -0
  141. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_migration_integrity.py +0 -0
  142. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_realtime_event_freshness.py +0 -0
  143. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_rls_cross_tenant.py +0 -0
  144. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  145. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_security_regressions.py +0 -0
  146. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_sentinel.py +0 -0
  147. {meshcode-2.10.101/meshcode-tasks-wt → meshcode-2.11.1/meshcode-noun-wt}/tests/test_status_enum_coverage.py +0 -0
  148. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1/meshcode-tasks-wt}/comms_v4.py +0 -0
  149. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/__init__.py +0 -0
  150. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/ascii_art.py +0 -0
  151. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/cli.py +0 -0
  152. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/comms_v4.py +0 -0
  153. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/compat.py +0 -0
  154. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/error_hints.py +0 -0
  155. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/exceptions.py +0 -0
  156. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/invites.py +0 -0
  157. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/launcher.py +0 -0
  158. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/launcher_install.py +0 -0
  159. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/meshcode_mcp/__init__.py +0 -0
  160. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/meshcode_mcp/__main__.py +0 -0
  161. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/meshcode_mcp/backend.py +0 -0
  162. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/meshcode_mcp/realtime.py +0 -0
  163. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/meshcode_mcp/server.py +0 -0
  164. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/meshcode_mcp/test_backend.py +0 -0
  165. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  166. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  167. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/preferences.py +0 -0
  168. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/protocol_v2.py +0 -0
  169. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/quickstart.py +0 -0
  170. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/run_agent.py +0 -0
  171. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/secrets.py +0 -0
  172. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/self_update.py +0 -0
  173. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/setup_clients.py +0 -0
  174. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/supervisor.py +0 -0
  175. {meshcode-2.10.101/meshcode-noun-wt/build/lib → meshcode-2.11.1/meshcode-tasks-wt}/meshcode/upload.py +0 -0
  176. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1/meshcode-tasks-wt}/scripts/sentinel.py +0 -0
  177. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_core.py +0 -0
  178. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_cross_agent_messaging.py +0 -0
  179. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_esc_deaf_state.py +0 -0
  180. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_exceptions.py +0 -0
  181. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_mark_read_batch.py +0 -0
  182. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_migration_integrity.py +0 -0
  183. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_realtime_event_freshness.py +0 -0
  184. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_rls_cross_tenant.py +0 -0
  185. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_rpc_migrations.py +0 -0
  186. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_security_regressions.py +0 -0
  187. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_sentinel.py +0 -0
  188. {meshcode-2.10.101/meshcode-noun-wt → meshcode-2.11.1/meshcode-tasks-wt}/tests/test_status_enum_coverage.py +0 -0
  189. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode.egg-info/dependency_links.txt +0 -0
  190. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode.egg-info/entry_points.txt +0 -0
  191. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode.egg-info/requires.txt +0 -0
  192. {meshcode-2.10.101 → meshcode-2.11.1}/meshcode.egg-info/top_level.txt +0 -0
  193. {meshcode-2.10.101 → meshcode-2.11.1}/setup.cfg +0 -0
  194. {meshcode-2.10.101 → meshcode-2.11.1}/tests/test_auto_update_hardening.py +0 -0
  195. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_core.py +0 -0
  196. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_cross_agent_messaging.py +0 -0
  197. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_esc_deaf_state.py +0 -0
  198. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_exceptions.py +0 -0
  199. {meshcode-2.10.101 → meshcode-2.11.1}/tests/test_lease_sigterm_release.py +0 -0
  200. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_mark_read_batch.py +0 -0
  201. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_migration_integrity.py +0 -0
  202. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_realtime_event_freshness.py +0 -0
  203. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_rls_cross_tenant.py +0 -0
  204. {meshcode-2.10.101 → meshcode-2.11.1}/tests/test_rpc_migrations.py +0 -0
  205. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_security_regressions.py +0 -0
  206. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_sentinel.py +0 -0
  207. {meshcode-2.10.101/meshcode-backend-wt → meshcode-2.11.1}/tests/test_status_enum_coverage.py +0 -0
  208. {meshcode-2.10.101 → meshcode-2.11.1}/tests/test_stay_on_loop_hook.py +0 -0
  209. {meshcode-2.10.101 → meshcode-2.11.1}/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.10.101
3
+ Version: 2.11.1
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -0,0 +1,82 @@
1
+ """MeshCode — Real-time communication between AI agents."""
2
+ __version__ = "2.11.1"
3
+
4
+ # Exception hierarchy — eagerly imported (lightweight, no deps)
5
+ from meshcode.exceptions import ( # noqa: F401
6
+ MeshCodeError,
7
+ AuthError,
8
+ RPCError,
9
+ MeshCodeTimeoutError,
10
+ MeshCodeConnectionError,
11
+ )
12
+
13
+ # Public API — lazy imports to avoid heavy deps at import time
14
+ def __getattr__(name):
15
+ if name == "backend":
16
+ from meshcode.meshcode_mcp import backend
17
+ return backend
18
+ if name in _BACKEND_EXPORTS:
19
+ from meshcode.meshcode_mcp import backend
20
+ return getattr(backend, name)
21
+ if name in _SECRETS_EXPORTS:
22
+ from meshcode import secrets
23
+ return getattr(secrets, name)
24
+ raise AttributeError(f"module 'meshcode' has no attribute {name!r}")
25
+
26
+
27
+ # Backend: core messaging & agent management
28
+ _BACKEND_EXPORTS = {
29
+ "send_message",
30
+ "read_inbox",
31
+ "count_pending",
32
+ "get_board",
33
+ "heartbeat",
34
+ "set_status",
35
+ "register_agent",
36
+ "get_project_id",
37
+ "sb_rpc",
38
+ "task_create",
39
+ "task_list",
40
+ "encrypt_payload",
41
+ "decrypt_payload",
42
+ }
43
+
44
+ # Secrets: credential management
45
+ _SECRETS_EXPORTS = {
46
+ "get_api_key",
47
+ "set_api_key",
48
+ "list_profiles",
49
+ }
50
+
51
+ __all__ = [
52
+ "__version__",
53
+ "backend",
54
+ # Exceptions
55
+ "MeshCodeError",
56
+ "AuthError",
57
+ "RPCError",
58
+ "MeshCodeTimeoutError",
59
+ "MeshCodeConnectionError",
60
+ # Messaging
61
+ "send_message",
62
+ "read_inbox",
63
+ "count_pending",
64
+ # Agent management
65
+ "register_agent",
66
+ "get_project_id",
67
+ "get_board",
68
+ "heartbeat",
69
+ "set_status",
70
+ # Tasks
71
+ "task_create",
72
+ "task_list",
73
+ # Low-level
74
+ "sb_rpc",
75
+ # Encryption
76
+ "encrypt_payload",
77
+ "decrypt_payload",
78
+ # Credentials
79
+ "get_api_key",
80
+ "set_api_key",
81
+ "list_profiles",
82
+ ]
@@ -0,0 +1,514 @@
1
+ """Stop hook template — single source of truth for stay_on_loop.py.
2
+
3
+ Extracted from setup_clients.py 2026-05-13 to enable `meshcode patch-hooks`
4
+ to rewrite existing workspaces with the latest hook logic (BUG-STOP-HOOK-
5
+ TRAPS-ON-EXPLICIT-SLEEP-AUTH, task 60565831). Single source means future
6
+ hook fixes only edit this file; setup_workspace + patch-hooks both read it.
7
+ """
8
+
9
+ STOP_HOOK_BODY = '''#!/usr/bin/env python3
10
+ """Stop hook: refuse to end the agent's turn unless one of:
11
+ (a) the last user message contains a release keyword,
12
+ (b) the last assistant turn already called meshcode_wait,
13
+ (c) the most recent meshcode_wait tool_result authorized exit
14
+ (must_exit=True OR done_signals non-empty OR a sammybenu mesh
15
+ message contains a release keyword in payload.text), or
16
+ (d) the MCP server itself is unreachable (errored wait results, or
17
+ ToolSearch found no meshcode_wait — agent has no way to call it).
18
+
19
+ Samuel directive 2026-04-30 — agents kept saying "en loop" without actually
20
+ entering meshcode_wait, so the loop only existed in chat text. This hook
21
+ forces the agent to make a real meshcode_wait tool call before its turn
22
+ can stop.
23
+
24
+ Bug-fix 2026-05-05 (condition c): mesh broadcasts and direct sleep-
25
+ authorization done_signals were getting blocked — the agent set
26
+ status=sleeping then ended the turn, but neither (a) nor (b) matched, so
27
+ the hook trapped the agent in an infinite block-loop. Now any explicit
28
+ must_exit=True or non-empty done_signals from the latest wait result
29
+ releases the agent.
30
+
31
+ Bug-fix 2026-05-08 (condition d, PROTO-MCP-UNREACHABLE-RELEASE, task
32
+ d2bdc974): when the MCP server drops mid-session, stay_on_loop kept
33
+ demanding meshcode_wait but the tool was de-registered → agent printed
34
+ "Type stop." infinitely until the human typed stop/sleep/exit/done.
35
+ Now we probe the transcript for evidence the tool is unreachable
36
+ (errored tool_result OR ToolSearch miss) and release with mcp_unreachable.
37
+
38
+ Bug-fix 2026-05-08b (condition d extended, mesh-commander launch fail):
39
+ the prior detection only matched if the agent (a) called meshcode_wait
40
+ and got an error or (b) ran ToolSearch select:meshcode_wait and got
41
+ "no matching deferred tools found". Failed when the agent observed MCP
42
+ absence via system-reminder before ever attempting either tool — Samuel
43
+ reported commander trapped in this exact path. Two new release paths:
44
+ (1) latest <system-reminder> deferred-tool listing enumerates mcp__
45
+ entries but ZERO meshcode_* entries → MCP not registered.
46
+ (2) latest user-typed message names MCP failure ("mcp server fail",
47
+ "mcp no esta conectado", "mcp no esta cargado", "mcp not
48
+ connected") → human-confirmed unreachable.
49
+ """
50
+ import json
51
+ import sys
52
+ from pathlib import Path
53
+
54
+ RELEASE_WORDS = (
55
+ "stop", "sleep", "exit", "quit", "done",
56
+ "duerme", "descansa", "dormir", "quedate dormido", "se acab",
57
+ "got_done",
58
+ )
59
+
60
+ # Known human-user handles. Used as a back-compat fallback when the server
61
+ # hasn't stamped `sender_role="human_user"` on the message payload. Keep
62
+ # tight — these names get release-keyword power in mesh broadcasts.
63
+ # Server-side fix is to populate sender_role; this set is the safety net.
64
+ _KNOWN_HUMAN_HANDLES = frozenset({
65
+ "sammybenu", "samuel", "sam",
66
+ })
67
+
68
+ WAIT_TOOL_SUFFIX = "meshcode_wait"
69
+
70
+ # Tail-window for the unreachable probe. 40 records covers the typical
71
+ # boot + first few wait cycles without scanning the entire transcript.
72
+ _UNREACHABLE_LOOKBACK = 40
73
+
74
+ # Substrings in tool_result text that indicate MCP-side death. Matched
75
+ # case-insensitively. Keep the list tight — false positives here
76
+ # release the loop early.
77
+ _MCP_DEAD_MARKERS = (
78
+ "no such tool",
79
+ "connection closed",
80
+ "no matching deferred tools found",
81
+ "mcp server",
82
+ "mcp error",
83
+ )
84
+
85
+ # Substrings in a system-reminder that indicate a deferred-tool listing
86
+ # is being shown to the agent. Used to find the latest tool-availability
87
+ # snapshot in the transcript.
88
+ _DEFERRED_LISTING_MARKERS = (
89
+ "the following deferred tools are now available",
90
+ "deferred tools are now available via toolsearch",
91
+ )
92
+
93
+ # Substrings in a user-typed message indicating the human observed MCP
94
+ # failure. All matched case-insensitively; presence of any → release.
95
+ # Kept tight: each phrase explicitly names MCP + a failure verb so we
96
+ # don't false-positive on casual mentions.
97
+ _USER_MCP_FAIL_PHRASES = (
98
+ "mcp server fail",
99
+ "mcp server failed",
100
+ "mcp no esta conectado",
101
+ "mcp no está conectado",
102
+ "mcp no esta cargado",
103
+ "mcp no está cargado",
104
+ "mcp not connected",
105
+ "mcp failed at launch",
106
+ "mcp unreachable",
107
+ "meshcode mcp not",
108
+ "el mcp de meshcode no",
109
+ )
110
+
111
+
112
+ def _last_user_message(transcript_path):
113
+ try:
114
+ last = ""
115
+ with transcript_path.open() as f:
116
+ for line in f:
117
+ try:
118
+ rec = json.loads(line)
119
+ except json.JSONDecodeError:
120
+ continue
121
+ if rec.get("role") == "user":
122
+ content = rec.get("content")
123
+ if isinstance(content, str):
124
+ last = content
125
+ elif isinstance(content, list):
126
+ # Skip tool_result-only user turns; we want the human's text.
127
+ text_parts = [
128
+ p.get("text", "")
129
+ for p in content
130
+ if isinstance(p, dict) and p.get("type") == "text"
131
+ ]
132
+ if text_parts:
133
+ last = " ".join(text_parts)
134
+ return last
135
+ except (OSError, FileNotFoundError):
136
+ return ""
137
+
138
+
139
+ def _last_assistant_turn_called_wait(transcript_path):
140
+ try:
141
+ records = []
142
+ with transcript_path.open() as f:
143
+ for line in f:
144
+ try:
145
+ records.append(json.loads(line))
146
+ except json.JSONDecodeError:
147
+ continue
148
+ last_assistant_blocks = []
149
+ for rec in reversed(records):
150
+ if rec.get("role") == "user":
151
+ if last_assistant_blocks:
152
+ break
153
+ continue
154
+ if rec.get("role") == "assistant":
155
+ content = rec.get("content")
156
+ if isinstance(content, list):
157
+ last_assistant_blocks.extend(content)
158
+ elif isinstance(content, dict):
159
+ last_assistant_blocks.append(content)
160
+ for block in last_assistant_blocks:
161
+ if not isinstance(block, dict):
162
+ continue
163
+ if block.get("type") == "tool_use":
164
+ name = str(block.get("name", ""))
165
+ if name.endswith(WAIT_TOOL_SUFFIX):
166
+ return True
167
+ return False
168
+ except (OSError, FileNotFoundError):
169
+ return False
170
+
171
+
172
+ def _latest_wait_result_authorizes_exit(transcript_path):
173
+ """Walk transcript backwards. Find the most recent meshcode_wait
174
+ tool_use → match its tool_result by tool_use_id → if the parsed
175
+ result has must_exit=True or non-empty done_signals, return True.
176
+ """
177
+ try:
178
+ records = []
179
+ with transcript_path.open() as f:
180
+ for line in f:
181
+ try:
182
+ records.append(json.loads(line))
183
+ except json.JSONDecodeError:
184
+ continue
185
+
186
+ wait_use_ids = set()
187
+ for rec in records:
188
+ if rec.get("role") != "assistant":
189
+ continue
190
+ content = rec.get("content")
191
+ blocks = content if isinstance(content, list) else [content] if isinstance(content, dict) else []
192
+ for block in blocks:
193
+ if not isinstance(block, dict):
194
+ continue
195
+ if block.get("type") == "tool_use" and str(block.get("name", "")).endswith(WAIT_TOOL_SUFFIX):
196
+ use_id = block.get("id")
197
+ if use_id:
198
+ wait_use_ids.add(use_id)
199
+
200
+ if not wait_use_ids:
201
+ return False
202
+
203
+ for rec in reversed(records):
204
+ if rec.get("role") != "user":
205
+ continue
206
+ content = rec.get("content")
207
+ blocks = content if isinstance(content, list) else [content] if isinstance(content, dict) else []
208
+ for block in blocks:
209
+ if not isinstance(block, dict):
210
+ continue
211
+ if block.get("type") != "tool_result":
212
+ continue
213
+ if block.get("tool_use_id") not in wait_use_ids:
214
+ continue
215
+ inner = block.get("content")
216
+ text = ""
217
+ if isinstance(inner, str):
218
+ text = inner
219
+ elif isinstance(inner, list):
220
+ text = " ".join(
221
+ p.get("text", "") for p in inner
222
+ if isinstance(p, dict) and p.get("type") == "text"
223
+ )
224
+ if not text:
225
+ continue
226
+ try:
227
+ parsed = json.loads(text)
228
+ except json.JSONDecodeError:
229
+ continue
230
+ payload = parsed.get("result", parsed) if isinstance(parsed, dict) else parsed
231
+ if not isinstance(payload, dict):
232
+ continue
233
+ if payload.get("must_exit") is True:
234
+ return True
235
+ signals = payload.get("done_signals")
236
+ if isinstance(signals, list) and signals:
237
+ return True
238
+ # Condition (e) — 2026-05-08, extended 2026-05-13: a mesh
239
+ # broadcast / message from a HUMAN USER containing a release
240
+ # keyword counts as a sleep authorization. Without this branch,
241
+ # `meshcode_broadcast("pueden exit y dormir")` leaves the agent
242
+ # trapped because the keyword never entered the Claude Code
243
+ # transcript as user-typed text.
244
+ #
245
+ # 2026-05-13 (BUG-STOP-HOOK-TRAPS-ON-EXPLICIT-SLEEP-AUTH,
246
+ # task 60565831): widened beyond the hardcoded "sammybenu"
247
+ # check. wren@justforfun was trapped because Samuel's mesh
248
+ # handle in justforfun isn't "sammybenu". New scoping:
249
+ # (1) sender_role == "human_user" (server-stamped, forward-compat)
250
+ # (2) sender name in known-human handle set (back-compat)
251
+ # Both are scoped to release keywords only — same threat
252
+ # surface as before, broader cover for new meshes.
253
+ msgs = payload.get("messages") or []
254
+ if isinstance(msgs, list):
255
+ for m in msgs:
256
+ if not isinstance(m, dict):
257
+ continue
258
+ sender = m.get("from", "")
259
+ m_payload = m.get("payload") or {}
260
+ sender_role = None
261
+ if isinstance(m_payload, dict):
262
+ sender_role = m_payload.get("sender_role") or m_payload.get("from_role")
263
+ is_human = (
264
+ sender_role == "human_user"
265
+ or sender in _KNOWN_HUMAN_HANDLES
266
+ )
267
+ if not is_human:
268
+ continue
269
+ m_text = ""
270
+ if isinstance(m_payload, dict):
271
+ m_text = str(m_payload.get("text", "")).lower()
272
+ elif isinstance(m_payload, str):
273
+ m_text = m_payload.lower()
274
+ if any(word in m_text for word in RELEASE_WORDS):
275
+ return True
276
+ return False
277
+ return False
278
+ except (OSError, FileNotFoundError):
279
+ return False
280
+
281
+
282
+ def _mcp_unreachable_recently(transcript_path, lookback_records=_UNREACHABLE_LOOKBACK):
283
+ """Detect that MCP `meshcode_wait` is currently unreachable.
284
+
285
+ Without this signal, a dead MCP traps the agent: the stop hook keeps
286
+ demanding meshcode_wait while the tool itself has been de-registered
287
+ by the runtime, and the agent has no way to satisfy it short of the
288
+ user typing a release keyword. Lived through 2026-05-07 by backend
289
+ after first wait dropped stdio.
290
+
291
+ Three classifications, all → release:
292
+ 1. registered+errored — recent meshcode_wait tool_result has
293
+ is_error=True OR contains an _MCP_DEAD_MARKERS substring.
294
+ 2. unregistered — agent ran ToolSearch select:meshcode_wait and
295
+ got "no matching deferred tools found".
296
+ 3. (current implementation) any of the above wins; we do NOT
297
+ require a count threshold because a single tool-down event
298
+ is high-signal: meshcode_wait should never legitimately fail
299
+ except via must_exit/done_signals which are handled in (c).
300
+ """
301
+ try:
302
+ records = []
303
+ with transcript_path.open() as f:
304
+ for line in f:
305
+ try:
306
+ records.append(json.loads(line))
307
+ except json.JSONDecodeError:
308
+ continue
309
+ tail = records[-lookback_records:] if lookback_records else records
310
+ wait_tool_use_ids = set()
311
+ toolsearch_wait_ids = set()
312
+ for rec in tail:
313
+ content = rec.get("content")
314
+ blocks = content if isinstance(content, list) else [content] if isinstance(content, dict) else []
315
+ for block in blocks:
316
+ if not isinstance(block, dict):
317
+ continue
318
+ btype = block.get("type")
319
+ if btype == "tool_use":
320
+ name = str(block.get("name", ""))
321
+ use_id = block.get("id")
322
+ if name.endswith(WAIT_TOOL_SUFFIX) and use_id:
323
+ wait_tool_use_ids.add(use_id)
324
+ elif name.endswith("ToolSearch") or name == "ToolSearch":
325
+ query = ""
326
+ inp = block.get("input")
327
+ if isinstance(inp, dict):
328
+ query = str(inp.get("query", ""))
329
+ if "meshcode_wait" in query and use_id:
330
+ toolsearch_wait_ids.add(use_id)
331
+ elif btype == "tool_result":
332
+ use_id = block.get("tool_use_id")
333
+ if not use_id:
334
+ continue
335
+ is_error = bool(block.get("is_error"))
336
+ raw_text = block.get("content")
337
+ if isinstance(raw_text, list):
338
+ raw_text = " ".join(
339
+ p.get("text", "")
340
+ for p in raw_text
341
+ if isinstance(p, dict)
342
+ )
343
+ text_lower = str(raw_text or "").lower()
344
+ if use_id in wait_tool_use_ids and (
345
+ is_error or any(m in text_lower for m in _MCP_DEAD_MARKERS)
346
+ ):
347
+ return True
348
+ if use_id in toolsearch_wait_ids and "no matching deferred tools found" in text_lower:
349
+ return True
350
+ return False
351
+ except (OSError, FileNotFoundError):
352
+ return False
353
+
354
+
355
+ def _latest_deferred_listing_lacks_meshcode(transcript_path, lookback_records=_UNREACHABLE_LOOKBACK):
356
+ """Find the most recent <system-reminder> that enumerates deferred
357
+ tools. If it lists mcp__ entries (proving the listing format is
358
+ canonical) but ZERO meshcode_* entries, the meshcode MCP server is
359
+ not registered for this session. Release.
360
+
361
+ Note: the listing is delivered as user-role content, often inside a
362
+ <system-reminder>...</system-reminder> block but sometimes plain.
363
+ We match on the marker substrings rather than tag parsing.
364
+ """
365
+ try:
366
+ records = []
367
+ with transcript_path.open() as f:
368
+ for line in f:
369
+ try:
370
+ records.append(json.loads(line))
371
+ except json.JSONDecodeError:
372
+ continue
373
+ tail = records[-lookback_records:] if lookback_records else records
374
+ for rec in reversed(tail):
375
+ if rec.get("role") != "user":
376
+ continue
377
+ content = rec.get("content")
378
+ blocks = content if isinstance(content, list) else [content] if isinstance(content, dict) else [content]
379
+ for block in blocks:
380
+ if isinstance(block, str):
381
+ text = block
382
+ elif isinstance(block, dict):
383
+ if block.get("type") == "tool_result":
384
+ # tool_result blocks aren't system-reminders; skip
385
+ continue
386
+ text = str(block.get("text", "") or block.get("content", "") or "")
387
+ else:
388
+ continue
389
+ lower = text.lower()
390
+ if not any(m in lower for m in _DEFERRED_LISTING_MARKERS):
391
+ continue
392
+ # Found a listing. Check if it enumerates real tools.
393
+ has_mcp = "mcp__" in lower
394
+ has_meshcode = "meshcode_" in lower
395
+ if has_mcp and not has_meshcode:
396
+ return True
397
+ # First listing that contains meshcode_ is enough to
398
+ # confirm MCP IS registered — stop searching backwards.
399
+ if has_meshcode:
400
+ return False
401
+ return False
402
+ except (OSError, FileNotFoundError):
403
+ return False
404
+
405
+
406
+ def _user_explicitly_reports_mcp_failure(transcript_path, lookback_records=_UNREACHABLE_LOOKBACK):
407
+ """Scan recent user-typed text for explicit MCP-failure reports.
408
+ Distinct from `_last_user_message`: that helper checks for release
409
+ KEYWORDS (stop/sleep/etc.); this one checks for human-narrated MCP
410
+ breakage so the agent can release even when the human didn't think
411
+ to type a release keyword.
412
+ """
413
+ try:
414
+ records = []
415
+ with transcript_path.open() as f:
416
+ for line in f:
417
+ try:
418
+ records.append(json.loads(line))
419
+ except json.JSONDecodeError:
420
+ continue
421
+ tail = records[-lookback_records:] if lookback_records else records
422
+ for rec in reversed(tail):
423
+ if rec.get("role") != "user":
424
+ continue
425
+ content = rec.get("content")
426
+ text = ""
427
+ if isinstance(content, str):
428
+ text = content
429
+ elif isinstance(content, list):
430
+ parts = []
431
+ for p in content:
432
+ if isinstance(p, dict) and p.get("type") == "text":
433
+ parts.append(str(p.get("text", "")))
434
+ text = " ".join(parts)
435
+ if not text:
436
+ continue
437
+ lower = text.lower()
438
+ if any(p in lower for p in _USER_MCP_FAIL_PHRASES):
439
+ return True
440
+ return False
441
+ except (OSError, FileNotFoundError):
442
+ return False
443
+
444
+
445
+ def main():
446
+ raw = sys.stdin.read()
447
+ try:
448
+ payload = json.loads(raw) if raw else {}
449
+ except json.JSONDecodeError:
450
+ payload = {}
451
+
452
+ transcript = payload.get("transcript_path") or payload.get("transcriptPath")
453
+ transcript_path = Path(transcript) if transcript else None
454
+
455
+ last_user = _last_user_message(transcript_path).lower() if transcript_path else ""
456
+ if any(word in last_user for word in RELEASE_WORDS):
457
+ sys.exit(0)
458
+
459
+ if transcript_path and _last_assistant_turn_called_wait(transcript_path):
460
+ sys.exit(0)
461
+
462
+ if transcript_path and _latest_wait_result_authorizes_exit(transcript_path):
463
+ sys.exit(0)
464
+
465
+ if transcript_path and _mcp_unreachable_recently(transcript_path):
466
+ # PROTO-MCP-UNREACHABLE-RELEASE: log the reason to stderr so
467
+ # session traces can show why the loop released without a user
468
+ # release keyword. Released without printing a block decision.
469
+ try:
470
+ sys.stderr.write("[stay_on_loop] release: mcp_unreachable\n")
471
+ sys.stderr.flush()
472
+ except Exception:
473
+ pass
474
+ sys.exit(0)
475
+
476
+ if transcript_path and _latest_deferred_listing_lacks_meshcode(transcript_path):
477
+ try:
478
+ sys.stderr.write("[stay_on_loop] release: mcp_not_registered (deferred listing has no meshcode_*)\n")
479
+ sys.stderr.flush()
480
+ except Exception:
481
+ pass
482
+ sys.exit(0)
483
+
484
+ if transcript_path and _user_explicitly_reports_mcp_failure(transcript_path):
485
+ try:
486
+ sys.stderr.write("[stay_on_loop] release: user_reported_mcp_failure\n")
487
+ sys.stderr.flush()
488
+ except Exception:
489
+ pass
490
+ sys.exit(0)
491
+
492
+ print(
493
+ json.dumps(
494
+ {
495
+ "decision": "block",
496
+ "reason": (
497
+ "Stay-on-loop: you ended your turn without calling "
498
+ "meshcode_wait. Per Samuel's standing rule, every turn "
499
+ "ends with an active meshcode_wait. Call meshcode_wait "
500
+ "now (timeout_seconds=20). Do not reply with text — "
501
+ "just make the tool call. The only way out is the user "
502
+ "typing stop / sleep / exit / done, a sleep "
503
+ "authorization (must_exit / done_signals) from the "
504
+ "mesh, or MCP becoming unreachable (auto-released)."
505
+ ),
506
+ }
507
+ )
508
+ )
509
+ sys.exit(0)
510
+
511
+
512
+ if __name__ == "__main__":
513
+ main()
514
+ '''
@@ -3505,6 +3505,14 @@ if __name__ == "__main__":
3505
3505
  _upl = importlib.import_module("meshcode.upload")
3506
3506
  sys.exit(_upl.cmd_upload(pos[0]))
3507
3507
 
3508
+ elif cmd == "patch-hooks":
3509
+ # meshcode patch-hooks [--dry-run]
3510
+ # Rewrite stay_on_loop.py in every local workspace to the latest
3511
+ # template. Use after a hook fix lands without rerunning full setup.
3512
+ import importlib
3513
+ _sc = importlib.import_module("meshcode.setup_clients")
3514
+ sys.exit(_sc.patch_hooks(dry_run=("--dry-run" in flags)))
3515
+
3508
3516
  elif cmd in ("help", "--help", "-h"):
3509
3517
  show_help()
3510
3518
 
@@ -3541,7 +3549,7 @@ if __name__ == "__main__":
3541
3549
  "setup", "run", "go", "invite", "join", "invites", "members",
3542
3550
  "revoke-invite", "revoke-member", "login", "prefs", "launcher",
3543
3551
  "help", "init", "doctor", "compat", "upgrade", "profile", "validate-sessions", "wake-headless",
3544
- "supervisor", "upload", "quickstart",
3552
+ "supervisor", "upload", "quickstart", "patch-hooks",
3545
3553
  ]
3546
3554
  # Simple fuzzy: prefix match + Levenshtein-like best match
3547
3555
  suggestions = [c for c in known_cmds if c.startswith(cmd)]
@@ -560,11 +560,17 @@ def register_agent(project: str, name: str, role: str = "", api_key: Optional[st
560
560
  if not project_id:
561
561
  return {"error": f"Project '{project}' not found"}
562
562
 
563
+ # status="needs_launch" (BUG-NEW-AGENT-DEFAULT-STATUS-MISLEADS,
564
+ # 2026-05-13): a freshly-registered agent has no running MCP session
565
+ # yet. Treat needs_launch as the transitional state; the agent's own
566
+ # MCP boot will flip to 'idle' via _flip_status() on startup (see
567
+ # server.py L807), and the @with_working_status decorator subsequently
568
+ # flips to 'working' on each tool call.
563
569
  params = {
564
570
  "p_project_id": project_id,
565
571
  "p_name": name,
566
572
  "p_role": role,
567
- "p_status": "online",
573
+ "p_status": "needs_launch",
568
574
  }
569
575
  if api_key:
570
576
  params["p_api_key"] = api_key
@@ -573,7 +579,12 @@ def register_agent(project: str, name: str, role: str = "", api_key: Optional[st
573
579
  if not result or is_error(result):
574
580
  return {"error": get_error_message(result) or "Failed to register agent"}
575
581
 
576
- # Prefer SECURITY DEFINER RPC for mc_agents update
582
+ # Seed task only NOT last_heartbeat. Dashboard "needs_launch" CTA
583
+ # (BUG-NEW-AGENT-DEFAULT-STATUS-MISLEADS) relies on last_heartbeat
584
+ # remaining NULL until the agent's MCP session writes a real one. If
585
+ # the underlying default now() populated it on INSERT, the dashboard
586
+ # render path treats needs_launch state as the source of truth, so
587
+ # NULL vs default-now() doesn't change UX (just FE's tooltip detail).
577
588
  _ak = api_key or _get_api_key()
578
589
  _agent_updated = False
579
590
  if _ak:
@@ -581,7 +592,7 @@ def register_agent(project: str, name: str, role: str = "", api_key: Optional[st
581
592
  "p_api_key": _ak,
582
593
  "p_project_id": project_id,
583
594
  "p_agent_name": name,
584
- "p_fields": {"task": role, "last_heartbeat": _now_iso()},
595
+ "p_fields": {"task": role},
585
596
  })
586
597
  if isinstance(_upd, dict) and _upd.get("ok"):
587
598
  _agent_updated = True
@@ -589,7 +600,7 @@ def register_agent(project: str, name: str, role: str = "", api_key: Optional[st
589
600
  # Fallback: direct PATCH (gradual rollout)
590
601
  sb_update("mc_agents",
591
602
  f"project_id=eq.{project_id}&name=eq.{quote(name)}",
592
- {"task": role, "last_heartbeat": _now_iso()})
603
+ {"task": role})
593
604
 
594
605
  return {
595
606
  "registered": True,