meshcode 2.11.12__tar.gz → 2.11.14__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 (225) hide show
  1. {meshcode-2.11.12 → meshcode-2.11.14}/PKG-INFO +1 -1
  2. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/__init__.py +1 -1
  3. meshcode-2.11.14/meshcode/date_parse.py +154 -0
  4. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/backend.py +20 -3
  5. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/server.py +153 -4
  6. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode.egg-info/PKG-INFO +1 -1
  7. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode.egg-info/SOURCES.txt +2 -0
  8. {meshcode-2.11.12 → meshcode-2.11.14}/pyproject.toml +1 -1
  9. meshcode-2.11.14/tests/test_date_parse.py +112 -0
  10. {meshcode-2.11.12 → meshcode-2.11.14}/README.md +0 -0
  11. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/__main__.py +0 -0
  12. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/_stop_hook_template.py +0 -0
  13. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/ascii_art.py +0 -0
  14. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/cli.py +0 -0
  15. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/comms_v4.py +0 -0
  16. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/compat.py +0 -0
  17. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/daemon.py +0 -0
  18. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/doctor.py +0 -0
  19. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/error_hints.py +0 -0
  20. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/exceptions.py +0 -0
  21. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/invites.py +0 -0
  22. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/launcher.py +0 -0
  23. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/launcher_install.py +0 -0
  24. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/__init__.py +0 -0
  25. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/__main__.py +0 -0
  26. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/realtime.py +0 -0
  27. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/sleep_signals.py +0 -0
  28. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/test_backend.py +0 -0
  29. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/test_boot_timing.py +0 -0
  30. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/test_install_guard.py +0 -0
  31. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/test_prefs_claude_version.py +0 -0
  32. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/test_realtime.py +0 -0
  33. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  34. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/preferences.py +0 -0
  35. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/protocol_handler.py +0 -0
  36. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/protocol_v2.py +0 -0
  37. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/quickstart.py +0 -0
  38. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/run_agent.py +0 -0
  39. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/scripts/check_secrets.py +0 -0
  40. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/scripts/race_rate_harness.py +0 -0
  41. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/secrets.py +0 -0
  42. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/self_update.py +0 -0
  43. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/setup_clients.py +0 -0
  44. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/supervisor.py +0 -0
  45. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode/upload.py +0 -0
  46. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/comms_v4.py +0 -0
  47. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/__init__.py +0 -0
  48. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/ascii_art.py +0 -0
  49. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/cli.py +0 -0
  50. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/comms_v4.py +0 -0
  51. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/compat.py +0 -0
  52. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/error_hints.py +0 -0
  53. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/exceptions.py +0 -0
  54. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/invites.py +0 -0
  55. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/launcher.py +0 -0
  56. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/launcher_install.py +0 -0
  57. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  58. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  59. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/meshcode_mcp/backend.py +0 -0
  60. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  61. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/meshcode_mcp/server.py +0 -0
  62. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  63. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  64. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  65. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/preferences.py +0 -0
  66. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/protocol_v2.py +0 -0
  67. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/quickstart.py +0 -0
  68. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/run_agent.py +0 -0
  69. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/secrets.py +0 -0
  70. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/self_update.py +0 -0
  71. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/setup_clients.py +0 -0
  72. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/supervisor.py +0 -0
  73. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/meshcode/upload.py +0 -0
  74. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/scripts/sentinel.py +0 -0
  75. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_core.py +0 -0
  76. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_cross_agent_messaging.py +0 -0
  77. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_esc_deaf_state.py +0 -0
  78. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_exceptions.py +0 -0
  79. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_mark_read_batch.py +0 -0
  80. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_migration_integrity.py +0 -0
  81. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_realtime_event_freshness.py +0 -0
  82. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_rls_cross_tenant.py +0 -0
  83. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_rpc_migrations.py +0 -0
  84. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_security_regressions.py +0 -0
  85. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_sentinel.py +0 -0
  86. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-backend-wt/tests/test_status_enum_coverage.py +0 -0
  87. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/__init__.py +0 -0
  88. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/ascii_art.py +0 -0
  89. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/cli.py +0 -0
  90. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/comms_v4.py +0 -0
  91. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/compat.py +0 -0
  92. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/error_hints.py +0 -0
  93. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/exceptions.py +0 -0
  94. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/invites.py +0 -0
  95. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/launcher.py +0 -0
  96. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/launcher_install.py +0 -0
  97. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__init__.py +0 -0
  98. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/__main__.py +0 -0
  99. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/backend.py +0 -0
  100. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/realtime.py +0 -0
  101. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/server.py +0 -0
  102. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_backend.py +0 -0
  103. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_realtime.py +0 -0
  104. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  105. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/preferences.py +0 -0
  106. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/protocol_v2.py +0 -0
  107. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/quickstart.py +0 -0
  108. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/run_agent.py +0 -0
  109. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/secrets.py +0 -0
  110. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/self_update.py +0 -0
  111. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/setup_clients.py +0 -0
  112. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/supervisor.py +0 -0
  113. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/build/lib/meshcode/upload.py +0 -0
  114. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/comms_v4.py +0 -0
  115. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/__init__.py +0 -0
  116. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/ascii_art.py +0 -0
  117. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/cli.py +0 -0
  118. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/comms_v4.py +0 -0
  119. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/compat.py +0 -0
  120. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/error_hints.py +0 -0
  121. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/exceptions.py +0 -0
  122. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/invites.py +0 -0
  123. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/launcher.py +0 -0
  124. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/launcher_install.py +0 -0
  125. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  126. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  127. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/meshcode_mcp/backend.py +0 -0
  128. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  129. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/meshcode_mcp/server.py +0 -0
  130. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  131. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  132. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  133. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/preferences.py +0 -0
  134. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/protocol_v2.py +0 -0
  135. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/quickstart.py +0 -0
  136. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/run_agent.py +0 -0
  137. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/secrets.py +0 -0
  138. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/self_update.py +0 -0
  139. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/setup_clients.py +0 -0
  140. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/supervisor.py +0 -0
  141. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/meshcode/upload.py +0 -0
  142. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/scripts/sentinel.py +0 -0
  143. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_core.py +0 -0
  144. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_cross_agent_messaging.py +0 -0
  145. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_esc_deaf_state.py +0 -0
  146. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_exceptions.py +0 -0
  147. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_mark_read_batch.py +0 -0
  148. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_migration_integrity.py +0 -0
  149. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_realtime_event_freshness.py +0 -0
  150. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_rls_cross_tenant.py +0 -0
  151. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_rpc_migrations.py +0 -0
  152. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_security_regressions.py +0 -0
  153. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_sentinel.py +0 -0
  154. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-noun-wt/tests/test_status_enum_coverage.py +0 -0
  155. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/comms_v4.py +0 -0
  156. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/__init__.py +0 -0
  157. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/ascii_art.py +0 -0
  158. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/cli.py +0 -0
  159. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/comms_v4.py +0 -0
  160. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/compat.py +0 -0
  161. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/error_hints.py +0 -0
  162. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/exceptions.py +0 -0
  163. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/invites.py +0 -0
  164. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/launcher.py +0 -0
  165. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/launcher_install.py +0 -0
  166. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/meshcode_mcp/__init__.py +0 -0
  167. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/meshcode_mcp/__main__.py +0 -0
  168. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/meshcode_mcp/backend.py +0 -0
  169. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/meshcode_mcp/realtime.py +0 -0
  170. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/meshcode_mcp/server.py +0 -0
  171. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_backend.py +0 -0
  172. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_realtime.py +0 -0
  173. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/meshcode_mcp/test_server_wrapper.py +0 -0
  174. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/preferences.py +0 -0
  175. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/protocol_v2.py +0 -0
  176. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/quickstart.py +0 -0
  177. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/run_agent.py +0 -0
  178. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/secrets.py +0 -0
  179. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/self_update.py +0 -0
  180. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/setup_clients.py +0 -0
  181. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/supervisor.py +0 -0
  182. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/meshcode/upload.py +0 -0
  183. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/scripts/sentinel.py +0 -0
  184. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_core.py +0 -0
  185. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_cross_agent_messaging.py +0 -0
  186. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_esc_deaf_state.py +0 -0
  187. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_exceptions.py +0 -0
  188. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_mark_read_batch.py +0 -0
  189. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_migration_integrity.py +0 -0
  190. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_realtime_event_freshness.py +0 -0
  191. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_rls_cross_tenant.py +0 -0
  192. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_rpc_migrations.py +0 -0
  193. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_security_regressions.py +0 -0
  194. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_sentinel.py +0 -0
  195. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode-tasks-wt/tests/test_status_enum_coverage.py +0 -0
  196. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode.egg-info/dependency_links.txt +0 -0
  197. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode.egg-info/entry_points.txt +0 -0
  198. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode.egg-info/requires.txt +0 -0
  199. {meshcode-2.11.12 → meshcode-2.11.14}/meshcode.egg-info/top_level.txt +0 -0
  200. {meshcode-2.11.12 → meshcode-2.11.14}/setup.cfg +0 -0
  201. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_auto_update_hardening.py +0 -0
  202. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_boot_bug_regression.py +0 -0
  203. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_color_truecolor.py +0 -0
  204. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_core.py +0 -0
  205. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_cross_agent_messaging.py +0 -0
  206. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_doctor.py +0 -0
  207. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_epistemic_v1_python_sdk.py +0 -0
  208. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_epistemic_v1_stop_conditions.py +0 -0
  209. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_esc_deaf_state.py +0 -0
  210. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_exceptions.py +0 -0
  211. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_init_device_code.py +0 -0
  212. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_install_guard.py +0 -0
  213. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_lease_sigterm_release.py +0 -0
  214. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_mark_read_batch.py +0 -0
  215. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_migration_integrity.py +0 -0
  216. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_realtime_event_freshness.py +0 -0
  217. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_rls_cross_tenant.py +0 -0
  218. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_rpc_migrations.py +0 -0
  219. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_security_regressions.py +0 -0
  220. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_sentinel.py +0 -0
  221. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_setup_path.py +0 -0
  222. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_sleep_signals.py +0 -0
  223. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_status_enum_coverage.py +0 -0
  224. {meshcode-2.11.12 → meshcode-2.11.14}/tests/test_stay_on_loop_hook.py +0 -0
  225. {meshcode-2.11.12 → meshcode-2.11.14}/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.12
3
+ Version: 2.11.14
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.12"
2
+ __version__ = "2.11.14"
3
3
 
4
4
  # Exception hierarchy — eagerly imported (lightweight, no deps)
5
5
  from meshcode.exceptions import ( # noqa: F401
@@ -0,0 +1,154 @@
1
+ """Natural-language date parsing for `meshcode_task_create(due=...)`.
2
+
3
+ Goal: accept human inputs ("by Thursday", "in 3 days", "tomorrow",
4
+ "next week", "May 25") and ISO 8601 strict pass-through. No external
5
+ dependency — manual rules keep the install lean.
6
+
7
+ API:
8
+ parse_due(text: str, now: datetime|None = None)
9
+ -> (parsed_dt: datetime|None, label: str)
10
+
11
+ Returned datetime is timezone-aware UTC. Labels are normalized lowercase
12
+ human strings. Unknown input returns (None, original_text).
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import re
17
+ from datetime import datetime, timedelta, timezone
18
+ from typing import Optional, Tuple
19
+
20
+ _WEEKDAYS = {
21
+ "monday": 0, "mon": 0,
22
+ "tuesday": 1, "tue": 1, "tues": 1,
23
+ "wednesday": 2, "wed": 2,
24
+ "thursday": 3, "thu": 3, "thur": 3, "thurs": 3,
25
+ "friday": 4, "fri": 4,
26
+ "saturday": 5, "sat": 5,
27
+ "sunday": 6, "sun": 6,
28
+ }
29
+
30
+ _MONTHS = {
31
+ "january": 1, "jan": 1,
32
+ "february": 2, "feb": 2,
33
+ "march": 3, "mar": 3,
34
+ "april": 4, "apr": 4,
35
+ "may": 5,
36
+ "june": 6, "jun": 6,
37
+ "july": 7, "jul": 7,
38
+ "august": 8, "aug": 8,
39
+ "september": 9, "sep": 9, "sept": 9,
40
+ "october": 10, "oct": 10,
41
+ "november": 11, "nov": 11,
42
+ "december": 12, "dec": 12,
43
+ }
44
+
45
+
46
+ def _eod(dt: datetime) -> datetime:
47
+ """End of day, UTC-aware."""
48
+ return dt.replace(hour=23, minute=59, second=0, microsecond=0, tzinfo=timezone.utc)
49
+
50
+
51
+ def _next_weekday(now: datetime, target_dow: int) -> datetime:
52
+ """Closest upcoming weekday match (today counts if it's in the future)."""
53
+ days_ahead = (target_dow - now.weekday()) % 7
54
+ if days_ahead == 0:
55
+ # "Thursday" said on Thursday → next Thursday (week out), unless
56
+ # caller explicitly said "today". Convention: same-day → next week.
57
+ days_ahead = 7
58
+ return _eod(now + timedelta(days=days_ahead))
59
+
60
+
61
+ def parse_due(text: str, now: Optional[datetime] = None) -> Tuple[Optional[datetime], str]:
62
+ """Parse a natural-language due-date string. Returns (datetime|None, label).
63
+
64
+ The label is what we'll persist as due_label (mig 329) — preserves the
65
+ user's voice when shown back ("by Thursday" stays human, not "2026-05-21").
66
+ """
67
+ if not text:
68
+ return None, ""
69
+ raw = text.strip()
70
+ s = raw.lower()
71
+ if now is None:
72
+ now = datetime.now(tz=timezone.utc)
73
+ elif now.tzinfo is None:
74
+ now = now.replace(tzinfo=timezone.utc)
75
+
76
+ # ISO 8601 strict pass-through (e.g. "2026-05-25T15:00:00Z" or "2026-05-25")
77
+ try:
78
+ iso = raw.replace("Z", "+00:00")
79
+ dt = datetime.fromisoformat(iso)
80
+ if dt.tzinfo is None:
81
+ dt = dt.replace(tzinfo=timezone.utc)
82
+ return dt, raw
83
+ except ValueError:
84
+ pass
85
+
86
+ # "today" / "tonight"
87
+ if s in ("today", "eod", "end of day", "tonight"):
88
+ return _eod(now), raw
89
+
90
+ # "tomorrow"
91
+ if s in ("tomorrow", "tmrw"):
92
+ return _eod(now + timedelta(days=1)), raw
93
+
94
+ # "in N days"|"in N hours"|"in N weeks"
95
+ m = re.match(r"^in\s+(\d+)\s+(day|days|hour|hours|hr|hrs|week|weeks|wk|wks|minute|minutes|min|mins)$", s)
96
+ if m:
97
+ n = int(m.group(1))
98
+ unit = m.group(2)
99
+ if unit.startswith("d"):
100
+ dt = _eod(now + timedelta(days=n))
101
+ elif unit.startswith("h") or unit.startswith("hr"):
102
+ dt = now + timedelta(hours=n)
103
+ elif unit.startswith("w"):
104
+ dt = _eod(now + timedelta(weeks=n))
105
+ elif unit.startswith("m"):
106
+ dt = now + timedelta(minutes=n)
107
+ else:
108
+ dt = None
109
+ return dt, raw
110
+
111
+ # "next week"
112
+ if s in ("next week", "nextweek"):
113
+ return _eod(now + timedelta(weeks=1)), raw
114
+
115
+ # "next <weekday>"
116
+ m = re.match(r"^next\s+([a-z]+)$", s)
117
+ if m and m.group(1) in _WEEKDAYS:
118
+ return _next_weekday(now + timedelta(days=7), _WEEKDAYS[m.group(1)]), raw
119
+
120
+ # "by <weekday>" / "<weekday>" / "this <weekday>"
121
+ m = re.match(r"^(?:by\s+|this\s+)?([a-z]+)$", s)
122
+ if m and m.group(1) in _WEEKDAYS:
123
+ return _next_weekday(now, _WEEKDAYS[m.group(1)]), raw
124
+
125
+ # "<month> <day>" or "<day> <month>" — assumes current year (or next year
126
+ # if the date already passed). Examples: "May 25", "25 May", "May 25 2026".
127
+ m = re.match(r"^(?:by\s+)?([a-z]+)\s+(\d{1,2})(?:[,\s]+(\d{4}))?$", s)
128
+ if m and m.group(1) in _MONTHS:
129
+ month = _MONTHS[m.group(1)]
130
+ day = int(m.group(2))
131
+ year = int(m.group(3)) if m.group(3) else now.year
132
+ try:
133
+ dt = _eod(datetime(year, month, day, tzinfo=timezone.utc))
134
+ if dt < now and not m.group(3):
135
+ dt = _eod(datetime(year + 1, month, day, tzinfo=timezone.utc))
136
+ return dt, raw
137
+ except ValueError:
138
+ pass
139
+
140
+ m = re.match(r"^(?:by\s+)?(\d{1,2})\s+([a-z]+)(?:[,\s]+(\d{4}))?$", s)
141
+ if m and m.group(2) in _MONTHS:
142
+ day = int(m.group(1))
143
+ month = _MONTHS[m.group(2)]
144
+ year = int(m.group(3)) if m.group(3) else now.year
145
+ try:
146
+ dt = _eod(datetime(year, month, day, tzinfo=timezone.utc))
147
+ if dt < now and not m.group(3):
148
+ dt = _eod(datetime(year + 1, month, day, tzinfo=timezone.utc))
149
+ return dt, raw
150
+ except ValueError:
151
+ pass
152
+
153
+ # Fallback: not understood. Return None so caller can decide.
154
+ return None, raw
@@ -1190,8 +1190,13 @@ def set_status(project_id: str, agent: str, status: str, task: str = "", api_key
1190
1190
  return {"ok": True, "status": status}
1191
1191
 
1192
1192
 
1193
- def task_create(api_key, project_id, creator_agent, title, description="", assignee="*", priority="normal", parent_task_id=None):
1194
- return sb_rpc("mc_task_create", {
1193
+ def task_create(api_key, project_id, creator_agent, title, description="",
1194
+ assignee="*", priority="normal", parent_task_id=None,
1195
+ goal_id=None, strict=False,
1196
+ due_at=None, due_label=None, depends_on=None):
1197
+ """RPC wrapper. due_at must be an ISO 8601 string or None — the MCP tool
1198
+ layer parses natural-language input via meshcode.date_parse first."""
1199
+ args = {
1195
1200
  "p_api_key": api_key,
1196
1201
  "p_project_id": project_id,
1197
1202
  "p_creator_agent": creator_agent,
@@ -1200,7 +1205,19 @@ def task_create(api_key, project_id, creator_agent, title, description="", assig
1200
1205
  "p_assignee": assignee,
1201
1206
  "p_priority": priority,
1202
1207
  "p_parent_task_id": parent_task_id,
1203
- })
1208
+ "p_strict": strict,
1209
+ "p_goal_id": goal_id,
1210
+ }
1211
+ # CAL-1/CAL-2: extra fields. Only pass when set so we stay back-compat
1212
+ # with projects where mig 329 isn't applied yet (PostgREST will
1213
+ # 404 on extras when the new signature isn't in the schema cache).
1214
+ if due_at is not None:
1215
+ args["p_due_at"] = due_at
1216
+ if due_label is not None:
1217
+ args["p_due_label"] = due_label
1218
+ if depends_on:
1219
+ args["p_depends_on"] = depends_on
1220
+ return sb_rpc("mc_task_create", args)
1204
1221
 
1205
1222
 
1206
1223
  # Client-side debounce for task_list to neutralize runaway pollers without a
@@ -3720,6 +3720,11 @@ async def _meshcode_wait_inner(actual_timeout: int, include_acks: bool) -> Dict[
3720
3720
  out["pending_tasks"] = pending_tasks
3721
3721
  else:
3722
3722
  out["no_work"] = True
3723
+ # CAL-3 calendar context — surface due/overdue/next_due on timeouts so the
3724
+ # agent knows whether to nudge work even when no new msg arrived.
3725
+ _ctx = _calendar_context_for_self()
3726
+ if _ctx is not None:
3727
+ out["calendar_context"] = _ctx
3723
3728
  return out
3724
3729
 
3725
3730
 
@@ -3811,9 +3816,53 @@ def meshcode_check(include_acks: bool = False, since: Optional[str] = None, mark
3811
3816
  _hints = be.take_memory_hints()
3812
3817
  if _hints:
3813
3818
  result["memory_hints"] = _hints
3819
+ # CAL-3 calendar context — same shape as meshcode_boot, computed lazily
3820
+ # so the LLM sees due/overdue counts inline with every check/wait return.
3821
+ _ctx = _calendar_context_for_self()
3822
+ if _ctx is not None:
3823
+ result["calendar_context"] = _ctx
3814
3824
  return result
3815
3825
 
3816
3826
 
3827
+ def _calendar_context_for_self() -> Optional[Dict[str, Any]]:
3828
+ """CAL-3: lightweight calendar snapshot for the local agent. Soft-fail
3829
+ when mig 329 isn't deployed on the project — caller drops the field."""
3830
+ try:
3831
+ api_key = _get_api_key()
3832
+ if not api_key:
3833
+ return None
3834
+ resp = be.sb_rpc("mc_tasks_due_soon", {
3835
+ "p_api_key": api_key,
3836
+ "p_project_id": _PROJECT_ID,
3837
+ "p_agent_name": AGENT_NAME,
3838
+ "p_window_hours": 24,
3839
+ })
3840
+ if not (isinstance(resp, dict) and resp.get("ok")):
3841
+ return None
3842
+ tasks = resp.get("tasks") or []
3843
+ from datetime import datetime as _dt_dt, timezone as _dt_tz
3844
+ now_dt = _dt_dt.now(tz=_dt_tz.utc)
3845
+ due_today, overdue = [], []
3846
+ for t in tasks:
3847
+ d = t.get("due_at")
3848
+ if not d:
3849
+ continue
3850
+ try:
3851
+ dt = _dt_dt.fromisoformat(str(d).replace("Z", "+00:00"))
3852
+ except Exception:
3853
+ continue
3854
+ (overdue if dt < now_dt else due_today).append(t)
3855
+ if not (due_today or overdue):
3856
+ return None
3857
+ return {
3858
+ "pending_due_today": len(due_today),
3859
+ "overdue": len(overdue),
3860
+ "next_due": (due_today[0] if due_today else (overdue[0] if overdue else None)),
3861
+ }
3862
+ except Exception:
3863
+ return None
3864
+
3865
+
3817
3866
  @mcp.tool()
3818
3867
  @with_working_status
3819
3868
  def meshcode_status() -> Dict[str, Any]:
@@ -3914,26 +3963,54 @@ def meshcode_init(project: str, agent: str, role: str = "") -> Dict[str, Any]:
3914
3963
  @mcp.tool()
3915
3964
  @with_working_status
3916
3965
  def meshcode_task_create(title: str, description: str = "", assignee: Optional[str] = None,
3917
- priority: str = "normal", parent_task_id: Optional[str] = None) -> Dict[str, Any]:
3966
+ priority: str = "normal", parent_task_id: Optional[str] = None,
3967
+ due: Optional[str] = None,
3968
+ depends_on: Optional[List[str]] = None) -> Dict[str, Any]:
3918
3969
  """Create task. assignee = explicit agent name OR 'auto' for round-robin
3919
3970
  onto the least-loaded live agent in this meshwork. priority: low/normal/high/urgent.
3920
3971
 
3921
3972
  Per Samuel rule (mig 213): wildcard '*' / NULL / '' assignees are forbidden —
3922
3973
  every task must have a concrete owner or opt into 'auto' resolution.
3974
+
3975
+ CAL-2 (mig 329):
3976
+ due: natural-language deadline ("by Thursday", "in 3 days",
3977
+ "tomorrow", "May 25") OR ISO 8601 ("2026-05-25T15:00Z").
3978
+ Stored as both due_at (parsed datetime) and due_label
3979
+ (the original human string, for round-trip display).
3980
+ depends_on: list of task UUIDs that must reach status=done/cancelled
3981
+ before this task is unblocked.
3923
3982
  """
3924
3983
  if not title or not title.strip():
3925
3984
  return {"error": "title cannot be empty"}
3926
- # Mirror the RPC validation client-side so the LLM gets a clear hint
3927
- # before round-tripping. Do not silently coerce — the caller must choose.
3928
3985
  if assignee is None or not str(assignee).strip() or str(assignee).strip() == "*":
3929
3986
  return {
3930
3987
  "error": "assignee_required",
3931
3988
  "hint": "specify an explicit agent name OR pass assignee='auto' for round-robin",
3932
3989
  }
3990
+ # CAL-2: parse natural-language due dates client-side. The label round-trips
3991
+ # so the dashboard can show "by Thursday" verbatim instead of an ISO blob.
3992
+ parsed_due_at = None
3993
+ parsed_due_label = None
3994
+ if due:
3995
+ try:
3996
+ from meshcode import date_parse
3997
+ _dt, _lbl = date_parse.parse_due(due)
3998
+ parsed_due_label = _lbl
3999
+ if _dt is not None:
4000
+ parsed_due_at = _dt.isoformat()
4001
+ else:
4002
+ return {
4003
+ "error": "due_unparseable",
4004
+ "hint": f"could not parse {due!r}; try ISO 8601 or 'by <day>' / 'in N days' / 'tomorrow'",
4005
+ }
4006
+ except Exception as _de:
4007
+ return {"error": f"due_parse_failed: {_de}"}
3933
4008
  api_key = _get_api_key()
3934
4009
  result = be.task_create(api_key, _PROJECT_ID, AGENT_NAME, title.strip(),
3935
4010
  description=description, assignee=assignee,
3936
- priority=priority, parent_task_id=parent_task_id)
4011
+ priority=priority, parent_task_id=parent_task_id,
4012
+ due_at=parsed_due_at, due_label=parsed_due_label,
4013
+ depends_on=depends_on or None)
3937
4014
  # Auto-notify the resolved assignee so they wake from meshcode_wait.
3938
4015
  # Read result["assignee"] (server may have resolved 'auto' to a real name).
3939
4016
  if isinstance(result, dict) and result.get("ok"):
@@ -4829,6 +4906,78 @@ def meshcode_boot() -> Dict[str, Any]:
4829
4906
  _PERSONA = ph
4830
4907
  _PERSONA_INJECTED = False # let with_working_status surface it once
4831
4908
 
4909
+ # CAL-3 calendar context (mig 329): surface upcoming due tasks +
4910
+ # overdue count + newly-unblocked so the agent knows the cadence
4911
+ # backlog without a second tool call. Soft-fail if mig 329 not
4912
+ # applied on this project.
4913
+ try:
4914
+ _due_resp = be.sb_rpc("mc_tasks_due_soon", {
4915
+ "p_api_key": api_key,
4916
+ "p_project_id": _PROJECT_ID,
4917
+ "p_agent_name": AGENT_NAME,
4918
+ "p_window_hours": 24,
4919
+ })
4920
+ if isinstance(_due_resp, dict) and _due_resp.get("ok"):
4921
+ _due_tasks = _due_resp.get("tasks") or []
4922
+ _now_iso = _time.time()
4923
+ _due_today = []
4924
+ _overdue = []
4925
+ from datetime import datetime as _dt_dt, timezone as _dt_tz
4926
+ _now_dt = _dt_dt.now(tz=_dt_tz.utc)
4927
+ for _t in _due_tasks:
4928
+ _due_at_str = _t.get("due_at")
4929
+ if not _due_at_str:
4930
+ continue
4931
+ try:
4932
+ _due_dt = _dt_dt.fromisoformat(str(_due_at_str).replace("Z", "+00:00"))
4933
+ except Exception:
4934
+ continue
4935
+ if _due_dt < _now_dt:
4936
+ _overdue.append(_t)
4937
+ else:
4938
+ _due_today.append(_t)
4939
+ calendar_block = {
4940
+ "pending_due_today": len(_due_today),
4941
+ "overdue": len(_overdue),
4942
+ "next_due": (_due_today[0] if _due_today else (_overdue[0] if _overdue else None)),
4943
+ "due_tasks": _due_today + _overdue,
4944
+ }
4945
+ resp["calendar_context"] = calendar_block
4946
+ except Exception:
4947
+ resp.setdefault("calendar_context_available", False)
4948
+
4949
+ # UX-time-aware (mig 328 / task 4056b216): augment mesh_status rows with
4950
+ # cadence hints so the LLM can adjust tone (less pushy on deep-silence
4951
+ # peers, fine to ping on warm ones). Soft-fail if RPC unavailable on the
4952
+ # current project — mc_boot already gave us mesh_status without it.
4953
+ try:
4954
+ tc = be.sb_rpc("mc_agent_time_context_all", {
4955
+ "p_api_key": api_key,
4956
+ "p_project_id": _PROJECT_ID,
4957
+ })
4958
+ if isinstance(tc, dict) and tc.get("ok") and isinstance(tc.get("agents"), list):
4959
+ ctx_by_name: Dict[str, Dict[str, Any]] = {}
4960
+ for entry in tc["agents"]:
4961
+ # The bulk RPC wraps each context in a single-item list per row;
4962
+ # be lenient about both shapes.
4963
+ if isinstance(entry, list) and entry:
4964
+ entry = entry[0]
4965
+ if isinstance(entry, dict) and entry.get("agent"):
4966
+ ctx_by_name[entry["agent"]] = entry
4967
+ mesh = resp.get("mesh_status") or []
4968
+ if isinstance(mesh, list):
4969
+ for row in mesh:
4970
+ n = row.get("name") if isinstance(row, dict) else None
4971
+ if n and n in ctx_by_name:
4972
+ c = ctx_by_name[n]
4973
+ row["idle_bucket"] = c.get("idle_bucket")
4974
+ row["idle_seconds"] = c.get("idle_seconds")
4975
+ row["cadence_hint"] = c.get("cadence_hint")
4976
+ resp.setdefault("time_context_available", True)
4977
+ except Exception:
4978
+ # Non-fatal: surface that the hints are unavailable but boot succeeds.
4979
+ resp.setdefault("time_context_available", False)
4980
+
4832
4981
  return resp
4833
4982
 
4834
4983
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshcode
3
- Version: 2.11.12
3
+ Version: 2.11.14
4
4
  Summary: Real-time communication between AI agents — Supabase-backed CLI
5
5
  Author-email: MeshCode <hello@meshcode.io>
6
6
  License: MIT
@@ -8,6 +8,7 @@ meshcode/cli.py
8
8
  meshcode/comms_v4.py
9
9
  meshcode/compat.py
10
10
  meshcode/daemon.py
11
+ meshcode/date_parse.py
11
12
  meshcode/doctor.py
12
13
  meshcode/error_hints.py
13
14
  meshcode/exceptions.py
@@ -199,6 +200,7 @@ tests/test_boot_bug_regression.py
199
200
  tests/test_color_truecolor.py
200
201
  tests/test_core.py
201
202
  tests/test_cross_agent_messaging.py
203
+ tests/test_date_parse.py
202
204
  tests/test_doctor.py
203
205
  tests/test_epistemic_v1_python_sdk.py
204
206
  tests/test_epistemic_v1_stop_conditions.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "meshcode"
7
- version = "2.11.12"
7
+ version = "2.11.14"
8
8
  description = "Real-time communication between AI agents — Supabase-backed CLI"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -0,0 +1,112 @@
1
+ """Tests for meshcode/date_parse.py."""
2
+ from datetime import datetime, timedelta, timezone
3
+
4
+ import pytest
5
+
6
+ from meshcode.date_parse import parse_due
7
+
8
+
9
+ @pytest.fixture
10
+ def now():
11
+ # Pin "now" to a Wednesday 2026-05-20 12:00 UTC for deterministic tests.
12
+ return datetime(2026, 5, 20, 12, 0, tzinfo=timezone.utc)
13
+
14
+
15
+ def test_empty_returns_none(now):
16
+ assert parse_due("", now) == (None, "")
17
+
18
+
19
+ def test_unknown_returns_none(now):
20
+ dt, label = parse_due("yesterday-ish", now)
21
+ assert dt is None
22
+ assert label == "yesterday-ish"
23
+
24
+
25
+ def test_iso_passthrough_utc(now):
26
+ dt, label = parse_due("2026-05-25T15:00:00Z", now)
27
+ assert dt == datetime(2026, 5, 25, 15, 0, tzinfo=timezone.utc)
28
+ assert label == "2026-05-25T15:00:00Z"
29
+
30
+
31
+ def test_iso_date_only(now):
32
+ dt, label = parse_due("2026-05-25", now)
33
+ assert dt.year == 2026 and dt.month == 5 and dt.day == 25
34
+
35
+
36
+ def test_today_returns_eod(now):
37
+ dt, _ = parse_due("today", now)
38
+ assert dt.date() == now.date()
39
+ assert dt.hour == 23 and dt.minute == 59
40
+
41
+
42
+ def test_tomorrow(now):
43
+ dt, _ = parse_due("tomorrow", now)
44
+ assert dt.date() == (now + timedelta(days=1)).date()
45
+
46
+
47
+ def test_in_3_days(now):
48
+ dt, _ = parse_due("in 3 days", now)
49
+ assert (dt.date() - now.date()).days == 3
50
+
51
+
52
+ def test_in_2_hours(now):
53
+ dt, _ = parse_due("in 2 hours", now)
54
+ delta = dt - now
55
+ assert 1.9 * 3600 <= delta.total_seconds() <= 2.1 * 3600
56
+
57
+
58
+ def test_in_1_week(now):
59
+ dt, _ = parse_due("in 1 week", now)
60
+ assert (dt.date() - now.date()).days == 7
61
+
62
+
63
+ def test_weekday_alone_returns_next_match(now):
64
+ """Wed → 'thursday' is tomorrow."""
65
+ dt, _ = parse_due("thursday", now)
66
+ assert dt.weekday() == 3
67
+ assert (dt.date() - now.date()).days == 1
68
+
69
+
70
+ def test_by_weekday(now):
71
+ dt, _ = parse_due("by friday", now)
72
+ assert dt.weekday() == 4
73
+
74
+
75
+ def test_next_weekday(now):
76
+ """'next thursday' said on Wed → not tomorrow, but the one a week later."""
77
+ dt, _ = parse_due("next thursday", now)
78
+ assert dt.weekday() == 3
79
+ assert (dt.date() - now.date()).days >= 7
80
+
81
+
82
+ def test_same_day_weekday_means_next_week(now):
83
+ """Wed → 'wednesday' is NOT today (convention)."""
84
+ dt, _ = parse_due("wednesday", now)
85
+ assert dt.weekday() == 2
86
+ assert (dt.date() - now.date()).days == 7
87
+
88
+
89
+ def test_month_day_current_year(now):
90
+ dt, _ = parse_due("May 25", now)
91
+ assert dt.year == 2026 and dt.month == 5 and dt.day == 25
92
+
93
+
94
+ def test_month_day_past_rolls_to_next_year(now):
95
+ """May 1 already passed in pinned now=May 20 → roll to next year."""
96
+ dt, _ = parse_due("May 1", now)
97
+ assert dt.year == 2027 and dt.month == 5 and dt.day == 1
98
+
99
+
100
+ def test_day_month_reverse(now):
101
+ dt, _ = parse_due("25 May", now)
102
+ assert dt.year == 2026 and dt.month == 5 and dt.day == 25
103
+
104
+
105
+ def test_explicit_year_month_day(now):
106
+ dt, _ = parse_due("May 25, 2030", now)
107
+ assert dt.year == 2030
108
+
109
+
110
+ def test_label_preserves_original(now):
111
+ _, label = parse_due("by Thursday", now)
112
+ assert label == "by Thursday"
File without changes
File without changes