meshcode 2.10.95__tar.gz → 2.10.98__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 (210) hide show
  1. {meshcode-2.10.95 → meshcode-2.10.98}/PKG-INFO +1 -1
  2. meshcode-2.10.98/meshcode/__init__.py +82 -0
  3. meshcode-2.10.98/meshcode/daemon.py +492 -0
  4. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/server.py +197 -56
  5. meshcode-2.10.98/meshcode/protocol_handler.py +375 -0
  6. meshcode-2.10.98/meshcode/setup_clients.py +1420 -0
  7. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/PKG-INFO +1 -1
  8. meshcode-2.10.95/meshcode.egg-info/SOURCES 2.txt → meshcode-2.10.98/meshcode.egg-info/SOURCES.txt +2 -0
  9. {meshcode-2.10.95 → meshcode-2.10.98}/pyproject.toml +1 -1
  10. meshcode-2.10.95/meshcode/__init__.py +0 -82
  11. meshcode-2.10.95/meshcode/setup_clients.py +0 -926
  12. meshcode-2.10.95/meshcode.egg-info/PKG-INFO 2 +0 -446
  13. meshcode-2.10.95/meshcode.egg-info/SOURCES.txt +0 -204
  14. meshcode-2.10.95/meshcode.egg-info/dependency_links 2.txt +0 -1
  15. meshcode-2.10.95/meshcode.egg-info/entry_points 2.txt +0 -3
  16. meshcode-2.10.95/meshcode.egg-info/requires 2.txt +0 -5
  17. meshcode-2.10.95/meshcode.egg-info/top_level 2.txt +0 -4
  18. {meshcode-2.10.95 → meshcode-2.10.98}/README.md +0 -0
  19. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/ascii_art.py +0 -0
  20. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/cli.py +0 -0
  21. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/comms_v4.py +0 -0
  22. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/compat.py +0 -0
  23. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/error_hints.py +0 -0
  24. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/exceptions.py +0 -0
  25. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/invites.py +0 -0
  26. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/launcher.py +0 -0
  27. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/launcher_install.py +0 -0
  28. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/__init__.py +0 -0
  29. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/__main__.py +0 -0
  30. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/backend.py +0 -0
  31. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/realtime.py +0 -0
  32. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/test_backend.py +0 -0
  33. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  34. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  35. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/preferences.py +0 -0
  36. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/protocol_v2.py +0 -0
  37. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/quickstart.py +0 -0
  38. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/run_agent.py +0 -0
  39. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/secrets.py +0 -0
  40. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/self_update.py +0 -0
  41. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/supervisor.py +0 -0
  42. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode/upload.py +0 -0
  43. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/comms_v4.py +0 -0
  44. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  45. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  46. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/cli.py +0 -0
  47. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  48. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/compat.py +0 -0
  49. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  50. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  51. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/invites.py +0 -0
  52. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  53. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  54. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  55. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  56. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  57. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  58. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  59. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  60. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  61. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  62. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  63. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  64. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  65. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  66. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  67. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  68. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  69. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  70. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/meshcode/upload.py +0 -0
  71. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  72. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_core.py +0 -0
  73. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  74. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  75. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  76. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  77. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  78. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  79. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  80. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  81. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  82. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  83. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  84. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  85. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  86. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  87. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  88. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  89. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  90. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  91. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  92. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  93. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  94. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  95. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  96. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  97. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  98. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  99. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  100. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  101. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  102. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  103. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  104. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  105. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  106. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  107. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  108. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  109. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  110. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  111. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/comms_v4.py +0 -0
  112. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  113. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  114. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/cli.py +0 -0
  115. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  116. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/compat.py +0 -0
  117. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  118. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  119. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/invites.py +0 -0
  120. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  121. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  122. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  123. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  124. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  125. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  126. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  127. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  128. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  129. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  130. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  131. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  132. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  133. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  134. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  135. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  136. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  137. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  138. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/meshcode/upload.py +0 -0
  139. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  140. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_core.py +0 -0
  141. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  142. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  143. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  144. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  145. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  146. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  147. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  148. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  149. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  150. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  151. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  152. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/comms_v4.py +0 -0
  153. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  154. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  155. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  156. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  157. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  158. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  159. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  160. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  161. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  162. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  163. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  164. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  165. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  166. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  167. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  168. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  169. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  170. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  171. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  172. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  173. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  174. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  175. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  176. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  177. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  178. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  179. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  180. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  181. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_core.py +0 -0
  182. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  183. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  184. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  185. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  186. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  187. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  188. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  189. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  190. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  191. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  192. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  193. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/dependency_links.txt +0 -0
  194. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/entry_points.txt +0 -0
  195. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/requires.txt +0 -0
  196. {meshcode-2.10.95 → meshcode-2.10.98}/meshcode.egg-info/top_level.txt +0 -0
  197. {meshcode-2.10.95 → meshcode-2.10.98}/setup.cfg +0 -0
  198. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_auto_update_hardening.py +0 -0
  199. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_core.py +0 -0
  200. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_cross_agent_messaging.py +0 -0
  201. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_esc_deaf_state.py +0 -0
  202. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_exceptions.py +0 -0
  203. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_mark_read_batch.py +0 -0
  204. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_migration_integrity.py +0 -0
  205. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_realtime_event_freshness.py +0 -0
  206. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_rls_cross_tenant.py +0 -0
  207. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_rpc_migrations.py +0 -0
  208. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_security_regressions.py +0 -0
  209. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_sentinel.py +0 -0
  210. {meshcode-2.10.95 → meshcode-2.10.98}/tests/test_status_enum_coverage.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.10.95
3
+ Version: 2.10.98
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.10.98"
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,492 @@
1
+ """MeshCode autonomy v1 — headless wake daemon.
2
+
3
+ Per-agent background daemon that polls `mc_agent_should_wake` every ~60s and
4
+ spawns `meshcode run <project> <agent>` only when the backend reports real
5
+ work (urgent task, scheduled action, unread P0 broadcast, kicked status).
6
+
7
+ Layout:
8
+ Darwin: ~/Library/LaunchAgents/io.meshcode.daemon.<project>.<agent>.plist
9
+ Linux: ~/.config/systemd/user/meshcode-daemon-<project>-<agent>.{service,timer}
10
+
11
+ The plist/timer fires `meshcode daemon-tick <project> <agent>` every 60s. The
12
+ tick is short-lived: it asks the RPC, checks anti-flap, and either exits or
13
+ spawns a detached agent process. The agent runs its own session and exits
14
+ when meshcode_wait returns must_exit=True. The daemon stays asleep between
15
+ ticks — zero LLM tokens consumed during idle.
16
+
17
+ Anti-flap:
18
+ - PID file at ~/.meshcode/daemons/<project>.<agent>.json carries last_spawn_at,
19
+ consecutive_failures.
20
+ - 30s hold-down: never re-spawn within 30s of the last spawn.
21
+ - Failure backoff: after 3 consecutive spawn failures inside 5 min, the
22
+ daemon suspends itself for 15 min before retrying.
23
+ """
24
+ from __future__ import annotations
25
+
26
+ import json
27
+ import os
28
+ import platform
29
+ import subprocess
30
+ import sys
31
+ import time
32
+ from pathlib import Path
33
+
34
+ HOME = Path.home()
35
+ DAEMON_STATE_DIR = HOME / ".meshcode" / "daemons"
36
+ CREDS_PATH = HOME / ".meshcode" / "credentials.json"
37
+
38
+ LAUNCHD_DIR = HOME / "Library" / "LaunchAgents"
39
+ SYSTEMD_DIR = HOME / ".config" / "systemd" / "user"
40
+
41
+ POLL_INTERVAL_SECONDS = 60
42
+ HOLD_DOWN_SECONDS = 30
43
+ FAILURE_WINDOW_SECONDS = 300
44
+ FAILURE_BACKOFF_SECONDS = 900
45
+ MAX_FAILURES = 3
46
+
47
+
48
+ def _label(project: str, agent: str) -> str:
49
+ return f"io.meshcode.daemon.{project}.{agent}"
50
+
51
+
52
+ def _systemd_unit_name(project: str, agent: str) -> str:
53
+ return f"meshcode-daemon-{project}-{agent}"
54
+
55
+
56
+ def _plist_path(project: str, agent: str) -> Path:
57
+ return LAUNCHD_DIR / f"{_label(project, agent)}.plist"
58
+
59
+
60
+ def _systemd_service_path(project: str, agent: str) -> Path:
61
+ return SYSTEMD_DIR / f"{_systemd_unit_name(project, agent)}.service"
62
+
63
+
64
+ def _systemd_timer_path(project: str, agent: str) -> Path:
65
+ return SYSTEMD_DIR / f"{_systemd_unit_name(project, agent)}.timer"
66
+
67
+
68
+ def _state_path(project: str, agent: str) -> Path:
69
+ return DAEMON_STATE_DIR / f"{project}.{agent}.json"
70
+
71
+
72
+ def _read_state(project: str, agent: str) -> dict:
73
+ p = _state_path(project, agent)
74
+ if not p.exists():
75
+ return {}
76
+ try:
77
+ return json.loads(p.read_text())
78
+ except Exception:
79
+ return {}
80
+
81
+
82
+ def _write_state(project: str, agent: str, state: dict) -> None:
83
+ DAEMON_STATE_DIR.mkdir(parents=True, exist_ok=True)
84
+ _state_path(project, agent).write_text(json.dumps(state, indent=2))
85
+
86
+
87
+ def _meshcode_bin() -> str:
88
+ """Best-effort: prefer the meshcode console_script on PATH; fall back to
89
+ `python -m meshcode`."""
90
+ from shutil import which
91
+
92
+ found = which("meshcode")
93
+ if found:
94
+ return found
95
+ return f"{sys.executable} -m meshcode"
96
+
97
+
98
+ def _plist_xml(project: str, agent: str) -> str:
99
+ label = _label(project, agent)
100
+ log_dir = HOME / ".meshcode" / "logs"
101
+ log_dir.mkdir(parents=True, exist_ok=True)
102
+ log_out = log_dir / f"daemon.{project}.{agent}.out.log"
103
+ log_err = log_dir / f"daemon.{project}.{agent}.err.log"
104
+ bin_parts = _meshcode_bin().split()
105
+ program_args = "\n ".join(f"<string>{p}</string>" for p in bin_parts) + (
106
+ f"\n <string>daemon-tick</string>\n <string>{project}</string>\n <string>{agent}</string>"
107
+ )
108
+ # Bake current env into the plist so the headless tick can reach the
109
+ # backend without relying on the user's shell rc files.
110
+ sb_url = os.environ.get("SUPABASE_URL", "")
111
+ sb_key = os.environ.get("SUPABASE_KEY") or os.environ.get("SUPABASE_ANON_KEY") or ""
112
+ extra_env = ""
113
+ if sb_url:
114
+ extra_env += f"\n <key>SUPABASE_URL</key><string>{sb_url}</string>"
115
+ if sb_key:
116
+ extra_env += f"\n <key>SUPABASE_KEY</key><string>{sb_key}</string>"
117
+ return f"""<?xml version="1.0" encoding="UTF-8"?>
118
+ <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
119
+ "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
120
+ <plist version="1.0">
121
+ <dict>
122
+ <key>Label</key><string>{label}</string>
123
+ <key>ProgramArguments</key>
124
+ <array>
125
+ {program_args}
126
+ </array>
127
+ <key>StartInterval</key><integer>{POLL_INTERVAL_SECONDS}</integer>
128
+ <key>RunAtLoad</key><true/>
129
+ <key>KeepAlive</key><false/>
130
+ <key>StandardOutPath</key><string>{log_out}</string>
131
+ <key>StandardErrorPath</key><string>{log_err}</string>
132
+ <key>EnvironmentVariables</key>
133
+ <dict>
134
+ <key>PATH</key><string>/usr/local/bin:/opt/homebrew/bin:/usr/bin:/bin</string>
135
+ <key>HOME</key><string>{HOME}</string>
136
+ <key>PYTHONUNBUFFERED</key><string>1</string>
137
+ <key>PYTHONIOENCODING</key><string>utf-8</string>{extra_env}
138
+ </dict>
139
+ </dict>
140
+ </plist>
141
+ """
142
+
143
+
144
+ def _systemd_service(project: str, agent: str) -> str:
145
+ bin_cmd = _meshcode_bin()
146
+ sb_url = os.environ.get("SUPABASE_URL", "")
147
+ sb_key = os.environ.get("SUPABASE_KEY") or os.environ.get("SUPABASE_ANON_KEY") or ""
148
+ env_lines = ""
149
+ if sb_url:
150
+ env_lines += f"\nEnvironment=SUPABASE_URL={sb_url}"
151
+ if sb_key:
152
+ env_lines += f"\nEnvironment=SUPABASE_KEY={sb_key}"
153
+ return f"""[Unit]
154
+ Description=MeshCode autonomy daemon ({project}/{agent})
155
+
156
+ [Service]
157
+ Type=oneshot{env_lines}
158
+ ExecStart={bin_cmd} daemon-tick {project} {agent}
159
+ StandardOutput=append:{HOME}/.meshcode/logs/daemon.{project}.{agent}.out.log
160
+ StandardError=append:{HOME}/.meshcode/logs/daemon.{project}.{agent}.err.log
161
+ """
162
+
163
+
164
+ def _systemd_timer(project: str, agent: str) -> str:
165
+ return f"""[Unit]
166
+ Description=MeshCode autonomy daemon timer ({project}/{agent})
167
+
168
+ [Timer]
169
+ OnBootSec=30
170
+ OnUnitActiveSec={POLL_INTERVAL_SECONDS}
171
+ AccuracySec=10
172
+
173
+ [Install]
174
+ WantedBy=default.target
175
+ """
176
+
177
+
178
+ def _run(cmd: list[str]) -> tuple[int, str, str]:
179
+ try:
180
+ p = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
181
+ return p.returncode, p.stdout, p.stderr
182
+ except Exception as e:
183
+ return 1, "", str(e)
184
+
185
+
186
+ def cmd_install(project: str, agent: str) -> int:
187
+ if not project or not agent:
188
+ print("[daemon] ERROR: usage: meshcode daemon install <project> <agent>", file=sys.stderr)
189
+ return 2
190
+ DAEMON_STATE_DIR.mkdir(parents=True, exist_ok=True)
191
+ (HOME / ".meshcode" / "logs").mkdir(parents=True, exist_ok=True)
192
+
193
+ sysname = platform.system()
194
+ if sysname == "Darwin":
195
+ LAUNCHD_DIR.mkdir(parents=True, exist_ok=True)
196
+ plist = _plist_path(project, agent)
197
+ plist.write_text(_plist_xml(project, agent))
198
+ print(f"[daemon] wrote {plist}")
199
+ _run(["launchctl", "unload", str(plist)])
200
+ rc, _, err = _run(["launchctl", "load", "-w", str(plist)])
201
+ if rc != 0:
202
+ print(f"[daemon] launchctl load failed: {err}", file=sys.stderr)
203
+ return rc
204
+ print(f"[daemon] launchd registered: {_label(project, agent)} (poll {POLL_INTERVAL_SECONDS}s)")
205
+ return 0
206
+ elif sysname == "Linux":
207
+ SYSTEMD_DIR.mkdir(parents=True, exist_ok=True)
208
+ svc = _systemd_service_path(project, agent)
209
+ tmr = _systemd_timer_path(project, agent)
210
+ svc.write_text(_systemd_service(project, agent))
211
+ tmr.write_text(_systemd_timer(project, agent))
212
+ print(f"[daemon] wrote {svc} + {tmr}")
213
+ _run(["systemctl", "--user", "daemon-reload"])
214
+ rc, _, err = _run(["systemctl", "--user", "enable", "--now", tmr.name])
215
+ if rc != 0:
216
+ print(f"[daemon] systemctl enable failed: {err}", file=sys.stderr)
217
+ return rc
218
+ print(f"[daemon] systemd timer enabled: {tmr.name}")
219
+ return 0
220
+ else:
221
+ print(f"[daemon] ERROR: unsupported platform '{sysname}'. Darwin/Linux only.", file=sys.stderr)
222
+ return 2
223
+
224
+
225
+ def cmd_uninstall(project: str, agent: str) -> int:
226
+ if not project or not agent:
227
+ print("[daemon] ERROR: usage: meshcode daemon uninstall <project> <agent>", file=sys.stderr)
228
+ return 2
229
+ sysname = platform.system()
230
+ if sysname == "Darwin":
231
+ plist = _plist_path(project, agent)
232
+ if plist.exists():
233
+ _run(["launchctl", "unload", str(plist)])
234
+ plist.unlink()
235
+ print(f"[daemon] removed {plist}")
236
+ else:
237
+ print("[daemon] plist not present")
238
+ return 0
239
+ elif sysname == "Linux":
240
+ svc = _systemd_service_path(project, agent)
241
+ tmr = _systemd_timer_path(project, agent)
242
+ _run(["systemctl", "--user", "disable", "--now", tmr.name])
243
+ for p in (svc, tmr):
244
+ if p.exists():
245
+ p.unlink()
246
+ print(f"[daemon] removed {p}")
247
+ _run(["systemctl", "--user", "daemon-reload"])
248
+ return 0
249
+ else:
250
+ print(f"[daemon] ERROR: unsupported platform '{sysname}'.", file=sys.stderr)
251
+ return 2
252
+
253
+
254
+ def cmd_status(project: str, agent: str) -> int:
255
+ if not project or not agent:
256
+ print("[daemon] ERROR: usage: meshcode daemon status <project> <agent>", file=sys.stderr)
257
+ return 2
258
+ state = _read_state(project, agent)
259
+ sysname = platform.system()
260
+ print(f"[daemon] {project}/{agent}")
261
+ if sysname == "Darwin":
262
+ plist = _plist_path(project, agent)
263
+ if plist.exists():
264
+ rc, out, _ = _run(["launchctl", "list", _label(project, agent)])
265
+ print(f" launchd: {'loaded' if rc == 0 else 'not loaded'}")
266
+ else:
267
+ print(" launchd: not installed")
268
+ elif sysname == "Linux":
269
+ tmr = _systemd_timer_path(project, agent)
270
+ if tmr.exists():
271
+ rc, out, _ = _run(["systemctl", "--user", "is-active", tmr.name])
272
+ print(f" systemd timer: {out.strip() or 'unknown'}")
273
+ else:
274
+ print(" systemd timer: not installed")
275
+ if state:
276
+ last_spawn = state.get("last_spawn_at", 0)
277
+ since = time.time() - last_spawn if last_spawn else None
278
+ print(f" last_spawn: {since:.0f}s ago" if since else " last_spawn: never")
279
+ print(f" consecutive_failures: {state.get('consecutive_failures', 0)}")
280
+ if state.get("backoff_until", 0) > time.time():
281
+ print(f" backoff_until: {int(state['backoff_until'] - time.time())}s remaining")
282
+ return 0
283
+
284
+
285
+ def _load_api_key() -> str:
286
+ env_key = os.environ.get("MESHCODE_API_KEY", "")
287
+ if env_key:
288
+ return env_key
289
+ if CREDS_PATH.exists():
290
+ try:
291
+ return json.loads(CREDS_PATH.read_text()).get("api_key", "") or ""
292
+ except Exception:
293
+ return ""
294
+ return ""
295
+
296
+
297
+ def _should_wake(api_key: str, project_id: str, agent: str) -> dict:
298
+ """Call mc_agent_should_wake via Supabase REST. Lightweight — no SDK
299
+ dependencies inside the daemon hot path."""
300
+ import urllib.request
301
+ import urllib.error
302
+
303
+ supabase_url = os.environ.get("SUPABASE_URL") or _supabase_url_from_env()
304
+ supabase_key = os.environ.get("SUPABASE_KEY") or _supabase_key_from_env()
305
+ if not (supabase_url and supabase_key):
306
+ return {"ok": False, "should_wake": False, "error": "no_supabase_env"}
307
+ body = json.dumps({
308
+ "p_api_key": api_key,
309
+ "p_project_id": project_id,
310
+ "p_agent_name": agent,
311
+ }).encode()
312
+ req = urllib.request.Request(
313
+ f"{supabase_url}/rest/v1/rpc/mc_agent_should_wake",
314
+ data=body,
315
+ method="POST",
316
+ headers={
317
+ "Content-Type": "application/json",
318
+ "apikey": supabase_key,
319
+ "Authorization": f"Bearer {supabase_key}",
320
+ },
321
+ )
322
+ try:
323
+ with urllib.request.urlopen(req, timeout=10) as r:
324
+ return json.loads(r.read().decode())
325
+ except urllib.error.HTTPError as e:
326
+ return {"ok": False, "should_wake": False, "http": e.code, "error": e.reason}
327
+ except Exception as e:
328
+ return {"ok": False, "should_wake": False, "error": str(e)}
329
+
330
+
331
+ def _supabase_url_from_env() -> str:
332
+ """No hardcoded fallback — wrong project ref would silently misroute the
333
+ tick. Caller must export SUPABASE_URL or have it baked into the launchd /
334
+ systemd EnvironmentVariables."""
335
+ return os.environ.get("SUPABASE_URL", "")
336
+
337
+
338
+ def _supabase_key_from_env() -> str:
339
+ # Daemon avoids embedding service-role; use the anon publishable key.
340
+ return os.environ.get("SUPABASE_KEY") or os.environ.get("SUPABASE_ANON_KEY") or ""
341
+
342
+
343
+ def _resolve_project_id(api_key: str, project_name: str) -> str:
344
+ """Resolve project name → uuid via mc_resolve_project (read-only, gated by
345
+ api_key). Cached in daemon state to avoid an extra round-trip per tick."""
346
+ state = _read_state(project_name, "_resolver")
347
+ cached = state.get("project_id")
348
+ if cached:
349
+ return cached
350
+ import urllib.request
351
+
352
+ supabase_url = _supabase_url_from_env()
353
+ supabase_key = _supabase_key_from_env()
354
+ if not (supabase_url and supabase_key):
355
+ return ""
356
+ body = json.dumps({"p_api_key": api_key, "p_project_name": project_name}).encode()
357
+ req = urllib.request.Request(
358
+ f"{supabase_url}/rest/v1/rpc/mc_resolve_project",
359
+ data=body,
360
+ method="POST",
361
+ headers={
362
+ "Content-Type": "application/json",
363
+ "apikey": supabase_key,
364
+ "Authorization": f"Bearer {supabase_key}",
365
+ },
366
+ )
367
+ try:
368
+ with urllib.request.urlopen(req, timeout=10) as r:
369
+ payload = json.loads(r.read().decode())
370
+ project_id = payload.get("project_id") or payload.get("id") or ""
371
+ if project_id:
372
+ _write_state(project_name, "_resolver", {"project_id": project_id})
373
+ return project_id
374
+ except Exception:
375
+ return ""
376
+
377
+
378
+ def _spawn_agent(project: str, agent: str) -> bool:
379
+ """Detached `meshcode run <agent>` so the agent owns its own lifetime.
380
+ Daemon does NOT wait — the launchd/systemd tick just kicked off the spawn
381
+ and exits."""
382
+ bin_cmd = _meshcode_bin().split()
383
+ args = bin_cmd + ["run", project, agent]
384
+ log_dir = HOME / ".meshcode" / "logs"
385
+ log_dir.mkdir(parents=True, exist_ok=True)
386
+ out = open(log_dir / f"agent.{project}.{agent}.out.log", "ab", buffering=0)
387
+ err = open(log_dir / f"agent.{project}.{agent}.err.log", "ab", buffering=0)
388
+ try:
389
+ subprocess.Popen(
390
+ args,
391
+ stdout=out,
392
+ stderr=err,
393
+ stdin=subprocess.DEVNULL,
394
+ start_new_session=True,
395
+ close_fds=True,
396
+ )
397
+ return True
398
+ except Exception as e:
399
+ print(f"[daemon] spawn failed: {e}", file=sys.stderr)
400
+ return False
401
+
402
+
403
+ def cmd_tick(project: str, agent: str) -> int:
404
+ """One iteration of the wake loop. Designed to be called by launchd /
405
+ systemd at POLL_INTERVAL_SECONDS cadence."""
406
+ if not project or not agent:
407
+ print("[daemon-tick] ERROR: usage: meshcode daemon-tick <project> <agent>", file=sys.stderr)
408
+ return 2
409
+ now = time.time()
410
+ state = _read_state(project, agent)
411
+
412
+ backoff_until = state.get("backoff_until", 0)
413
+ if backoff_until and now < backoff_until:
414
+ return 0
415
+
416
+ api_key = _load_api_key()
417
+ if not api_key:
418
+ print("[daemon-tick] no api_key (run `meshcode login <key>` first)", file=sys.stderr)
419
+ return 1
420
+
421
+ project_id = _resolve_project_id(api_key, project)
422
+ if not project_id:
423
+ print(f"[daemon-tick] could not resolve project '{project}'", file=sys.stderr)
424
+ return 1
425
+
426
+ resp = _should_wake(api_key, project_id, agent)
427
+ should = bool(resp.get("should_wake"))
428
+ if not should:
429
+ return 0
430
+
431
+ last_spawn = state.get("last_spawn_at", 0)
432
+ if now - last_spawn < HOLD_DOWN_SECONDS:
433
+ return 0
434
+
435
+ spawned = _spawn_agent(project, agent)
436
+ state["last_spawn_at"] = now
437
+ if spawned:
438
+ state["consecutive_failures"] = 0
439
+ state.pop("backoff_until", None)
440
+ state["last_reasons"] = resp.get("reasons", [])
441
+ _write_state(project, agent, state)
442
+ print(f"[daemon-tick] spawned {project}/{agent} reasons={resp.get('reasons')}")
443
+ return 0
444
+
445
+ fails = state.get("consecutive_failures", 0) + 1
446
+ # If the prior burst is older than the failure window, treat this as a
447
+ # fresh first failure so backoff math is monotonic.
448
+ first = state.get("first_failure_at", 0)
449
+ if fails == 1 or (first and now - first > FAILURE_WINDOW_SECONDS):
450
+ state["first_failure_at"] = now
451
+ first = now
452
+ fails = 1
453
+ state["consecutive_failures"] = fails
454
+ state["last_failure_at"] = now
455
+ if fails >= MAX_FAILURES and now - first < FAILURE_WINDOW_SECONDS:
456
+ state["backoff_until"] = now + FAILURE_BACKOFF_SECONDS
457
+ # Clear the burst so the next failure cycle starts fresh.
458
+ state.pop("first_failure_at", None)
459
+ state["consecutive_failures"] = 0
460
+ print(f"[daemon-tick] entering {FAILURE_BACKOFF_SECONDS}s backoff after {fails} failures")
461
+ _write_state(project, agent, state)
462
+ return 1
463
+
464
+
465
+ def main(argv: list[str]) -> int:
466
+ if not argv:
467
+ print(
468
+ "usage:\n"
469
+ " meshcode daemon install <project> <agent>\n"
470
+ " meshcode daemon uninstall <project> <agent>\n"
471
+ " meshcode daemon status <project> <agent>\n"
472
+ " meshcode daemon-tick <project> <agent> # internal, fired by launchd/systemd",
473
+ file=sys.stderr,
474
+ )
475
+ return 2
476
+ sub = argv[0]
477
+ proj = argv[1] if len(argv) > 1 else ""
478
+ name = argv[2] if len(argv) > 2 else ""
479
+ if sub == "install":
480
+ return cmd_install(proj, name)
481
+ if sub == "uninstall":
482
+ return cmd_uninstall(proj, name)
483
+ if sub == "status":
484
+ return cmd_status(proj, name)
485
+ if sub == "tick":
486
+ return cmd_tick(proj, name)
487
+ print(f"[daemon] unknown subcommand: {sub}", file=sys.stderr)
488
+ return 2
489
+
490
+
491
+ if __name__ == "__main__":
492
+ sys.exit(main(sys.argv[1:]))