by-framework 0.2.2.dev7__tar.gz → 0.2.2.dev8__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 (262) hide show
  1. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/PKG-INFO +1 -1
  2. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/pyproject.toml +1 -1
  3. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/admin/cli.py +24 -8
  4. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/config.py +13 -3
  5. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/redis_client.py +2 -2
  6. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/app.py +7 -4
  7. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/admin/test_cli.py +112 -0
  8. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_config.py +47 -0
  9. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_redis_client.py +160 -1
  10. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/uv.lock +1 -1
  11. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.dockerignore +0 -0
  12. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  13. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  14. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  15. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/RELEASING.md +0 -0
  16. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/workflows/ci.yml +0 -0
  17. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/workflows/publish.yml +0 -0
  18. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.github/workflows/stale.yml +0 -0
  19. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.gitignore +0 -0
  20. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/.pre-commit-config.yaml +0 -0
  21. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/AGENTS.md +0 -0
  22. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/CHANGELOG.md +0 -0
  23. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/CLAUDE.md +0 -0
  24. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/CODE_OF_CONDUCT.md +0 -0
  25. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/CONTRIBUTING.md +0 -0
  26. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/LICENSE +0 -0
  27. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/Makefile +0 -0
  28. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/README.md +0 -0
  29. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/README_zh.md +0 -0
  30. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/SECURITY.md +0 -0
  31. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/assets/img/architecture_en.png +0 -0
  32. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/assets/img/architecture_zh.png +0 -0
  33. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/autoformat.sh +0 -0
  34. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/README.md +0 -0
  35. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/by-framework-trace-change-usage.html +0 -0
  36. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/by-framework-trace-complete-guide.html +0 -0
  37. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
  38. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
  39. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/docs/worker_management.html +0 -0
  40. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/README.md +0 -0
  41. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/pyproject.toml +0 -0
  42. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
  43. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
  44. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/src/by_framework_adk/adapter.py +0 -0
  45. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
  46. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-adk/tests/test_worker.py +0 -0
  47. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/Dockerfile +0 -0
  48. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/README.md +0 -0
  49. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/index.html +0 -0
  50. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
  51. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/package.json +0 -0
  52. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
  53. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
  54. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
  55. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/pyproject.toml +0 -0
  56. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
  57. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
  58. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +0 -0
  59. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/static/__init__.py +0 -0
  60. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/static/app.js +0 -0
  61. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/static/index.html +0 -0
  62. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/src/by_framework_dashboard/static/styles.css +0 -0
  63. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
  64. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/README.md +0 -0
  65. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
  66. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
  67. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
  68. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
  69. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/README.md +0 -0
  70. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/pyproject.toml +0 -0
  71. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
  72. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
  73. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
  74. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/README.md +0 -0
  75. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/pyproject.toml +0 -0
  76. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
  77. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
  78. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +0 -0
  79. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
  80. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
  81. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/tests/test_adapter.py +0 -0
  82. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
  83. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
  84. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/README.md +0 -0
  85. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
  86. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +0 -0
  87. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +0 -0
  88. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
  89. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/README.md +0 -0
  90. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
  91. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
  92. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
  93. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
  94. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
  95. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/pyproject.toml +0 -0
  96. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/__init__.py +0 -0
  97. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/client.py +0 -0
  98. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/merger.py +0 -0
  99. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/models.py +0 -0
  100. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/src/by_framework_trace_query/redis_source.py +0 -0
  101. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/libs/by-framework-trace-query/tests/test_trace_query_sdk.py +0 -0
  102. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/pylintrc +0 -0
  103. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/scripts/python_quality.sh +0 -0
  104. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/__init__.py +0 -0
  105. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/__main__.py +0 -0
  106. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/admin/__init__.py +0 -0
  107. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/admin/worker_manager.py +0 -0
  108. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/client/__init__.py +0 -0
  109. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/client/byai_client.py +0 -0
  110. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/client/client.py +0 -0
  111. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/__init__.py +0 -0
  112. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/constants.py +0 -0
  113. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/emitter.py +0 -0
  114. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/exceptions.py +0 -0
  115. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/common/logger.py +0 -0
  116. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/__init__.py +0 -0
  117. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/availability.py +0 -0
  118. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/delivery_gate.py +0 -0
  119. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/discovery.py +0 -0
  120. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/__init__.py +0 -0
  121. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/agent_config.py +0 -0
  122. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/agent_config_audit.py +0 -0
  123. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/plugin.py +0 -0
  124. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/registry.py +0 -0
  125. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/extensions/trace_provider.py +0 -0
  126. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/__init__.py +0 -0
  127. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/action_type.py +0 -0
  128. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/agent_state.py +0 -0
  129. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/byai_codec.py +0 -0
  130. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/byai_command.py +0 -0
  131. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/byai_types.py +0 -0
  132. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/commands.py +0 -0
  133. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/content_codec.py +0 -0
  134. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/content_type.py +0 -0
  135. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/data_message.py +0 -0
  136. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/data_shapes.py +0 -0
  137. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/event_type.py +0 -0
  138. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/events.py +0 -0
  139. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/message.py +0 -0
  140. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/message_header.py +0 -0
  141. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/responses.py +0 -0
  142. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/protocol/results.py +0 -0
  143. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/registry.py +0 -0
  144. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/__init__.py +0 -0
  145. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
  146. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
  147. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/file_manager.py +0 -0
  148. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/file_paths.py +0 -0
  149. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/file_permissions.py +0 -0
  150. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
  151. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/filestore/base.py +0 -0
  152. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/filestore/local.py +0 -0
  153. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/history/__init__.py +0 -0
  154. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/history/base.py +0 -0
  155. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/history/history_manager.py +0 -0
  156. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/history/in_memory.py +0 -0
  157. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/runtime/session_manager.py +0 -0
  158. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/wakeup_controller.py +0 -0
  159. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/core/workspace.py +0 -0
  160. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/__init__.py +0 -0
  161. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/base.py +0 -0
  162. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/common.py +0 -0
  163. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/execution.py +0 -0
  164. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/http.py +0 -0
  165. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/protocol.py +0 -0
  166. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/errors/registry.py +0 -0
  167. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/__init__.py +0 -0
  168. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/catalog.py +0 -0
  169. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/collector.py +0 -0
  170. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/read_client.py +0 -0
  171. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/metrics/snapshot.py +0 -0
  172. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/__init__.py +0 -0
  173. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/external_trace.py +0 -0
  174. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/span_recorder.py +0 -0
  175. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/trace_schema.py +0 -0
  176. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/trace/trace_writer.py +0 -0
  177. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/util/__init__.py +0 -0
  178. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/util/discovery_http_client.py +0 -0
  179. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/util/generate_message_id.py +0 -0
  180. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/util/http_client.py +0 -0
  181. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/__init__.py +0 -0
  182. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/_control_handling.py +0 -0
  183. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/_execution_tracking.py +0 -0
  184. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/_message_processing.py +0 -0
  185. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/byai_context.py +0 -0
  186. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/byai_worker.py +0 -0
  187. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/context.py +0 -0
  188. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/heartbeat.py +0 -0
  189. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/processor.py +0 -0
  190. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/runner.py +0 -0
  191. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/sandbox/__init__.py +0 -0
  192. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
  193. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/src/by_framework/worker/worker.py +0 -0
  194. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/admin/__init__.py +0 -0
  195. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/client/__init__.py +0 -0
  196. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/client/test_client.py +0 -0
  197. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/__init__.py +0 -0
  198. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_constants.py +0 -0
  199. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_exceptions.py +0 -0
  200. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/common/test_logger.py +0 -0
  201. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/conftest.py +0 -0
  202. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/__init__.py +0 -0
  203. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/__init__.py +0 -0
  204. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/test_byai_codec.py +0 -0
  205. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/test_command_wire.py +0 -0
  206. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/test_protocol.py +0 -0
  207. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/protocol/test_results.py +0 -0
  208. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/history/__init__.py +0 -0
  209. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/history/test_history_persistence.py +0 -0
  210. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_file_access_context.py +0 -0
  211. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
  212. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_file_paths.py +0 -0
  213. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_file_permissions.py +0 -0
  214. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_filestore_local_collection.py +0 -0
  215. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
  216. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_filestore_local_read.py +0 -0
  217. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/runtime/test_filestore_local_search.py +0 -0
  218. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_agent_config_audit.py +0 -0
  219. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_availability.py +0 -0
  220. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_discovery.py +0 -0
  221. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_registry.py +0 -0
  222. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/core/test_worker_manager.py +0 -0
  223. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/__init__.py +0 -0
  224. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/test_ask_user_flow.py +0 -0
  225. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/test_callback_flow.py +0 -0
  226. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/test_logger_integration.py +0 -0
  227. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/integration/test_scatter_gather.py +0 -0
  228. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_catalog.py +0 -0
  229. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_collector.py +0 -0
  230. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_metrics.py +0 -0
  231. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_read_client.py +0 -0
  232. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/metrics/test_snapshot.py +0 -0
  233. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/__init__.py +0 -0
  234. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_langfuse_plugin.py +0 -0
  235. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_plugin_discovery.py +0 -0
  236. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_plugin_improvements.py +0 -0
  237. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_plugin_registry.py +0 -0
  238. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/plugin/test_plugin_system.py +0 -0
  239. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_external_trace.py +0 -0
  240. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_logger_correlation.py +0 -0
  241. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_otel_exporter.py +0 -0
  242. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_span_recorder.py +0 -0
  243. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/trace/test_trace_writer.py +0 -0
  244. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/util/test_discovery_http_client.py +0 -0
  245. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/util/test_discovery_http_client_download.py +0 -0
  246. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/util/test_discovery_http_client_upload.py +0 -0
  247. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/util/test_http_client.py +0 -0
  248. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/__init__.py +0 -0
  249. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_admin_control_handling.py +0 -0
  250. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_app.py +0 -0
  251. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_byai_worker.py +0 -0
  252. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_context.py +0 -0
  253. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_control_handling.py +0 -0
  254. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_emitter.py +0 -0
  255. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_fetch_messages.py +0 -0
  256. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_gateway_worker.py +0 -0
  257. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_heartbeat.py +0 -0
  258. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_message_processing.py +0 -0
  259. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_processor.py +0 -0
  260. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_runner.py +0 -0
  261. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_sandbox.py +0 -0
  262. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev8}/tests/worker/test_workspace.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: by-framework
3
- Version: 0.2.2.dev7
3
+ Version: 0.2.2.dev8
4
4
  Summary: 分布式 Agent 调度框架
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "by-framework"
3
- version = "0.2.2.dev7"
3
+ version = "0.2.2.dev8"
4
4
  description = "分布式 Agent 调度框架"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -13,7 +13,8 @@ from rich.console import Console
13
13
  from rich.table import Table
14
14
 
15
15
  from by_framework.admin.worker_manager import WorkerManager
16
- from by_framework.common.redis_client import init_redis_from_url
16
+ from by_framework.common.config import RedisConfig
17
+ from by_framework.common.redis_client import init_redis, init_redis_from_url
17
18
  from by_framework.core.registry import WorkerRegistry
18
19
  from by_framework.metrics.snapshot import (
19
20
  build_observability_snapshot,
@@ -27,7 +28,12 @@ from by_framework.metrics.snapshot import (
27
28
  app = typer.Typer(
28
29
  name="by-admin",
29
30
  no_args_is_help=True,
30
- help="by-framework cluster admin CLI",
31
+ help=(
32
+ "by-framework cluster admin CLI. For Redis Cluster, omit --redis-url "
33
+ "and set REDIS_CLUSTER_HOST (comma-separated host:port list; implies "
34
+ "cluster mode) and REDIS_KEY_SCHEMA_VERSION=v2. REDIS_MODE=cluster + "
35
+ "REDIS_CLUSTER_NODES also still works."
36
+ ),
31
37
  )
32
38
  worker_app = typer.Typer(no_args_is_help=True)
33
39
  type_app = typer.Typer(no_args_is_help=True)
@@ -43,7 +49,8 @@ app.add_typer(metrics_app, name="metrics", help="Metrics and observability")
43
49
 
44
50
  console = Console()
45
51
  err_console = Console(stderr=True)
46
- _redis_url = "redis://localhost:6379/0"
52
+ _DEFAULT_REDIS_URL = "redis://localhost:6379/0"
53
+ _redis_url: Optional[str] = None
47
54
 
48
55
 
49
56
  @app.callback()
@@ -52,13 +59,16 @@ def _global(
52
59
  None,
53
60
  "--redis-url",
54
61
  envvar=["BYAI_REDIS_URL", "REDIS_URL"],
55
- help="Redis connection URL [env: BYAI_REDIS_URL]",
62
+ help=(
63
+ "Standalone Redis connection URL [env: BYAI_REDIS_URL]. "
64
+ "For Redis Cluster, omit this and set REDIS_CLUSTER_HOST "
65
+ "(comma-separated host:port list)."
66
+ ),
56
67
  ),
57
68
  ):
58
69
  """by-framework cluster admin CLI."""
59
70
  global _redis_url
60
- if redis_url:
61
- _redis_url = redis_url
71
+ _redis_url = redis_url
62
72
 
63
73
 
64
74
  def _run(coro): # type: ignore[no-untyped-def]
@@ -70,8 +80,14 @@ def _die(msg: str, code: int = 1) -> None:
70
80
  raise typer.Exit(code)
71
81
 
72
82
 
73
- def _get_redis():
74
- return init_redis_from_url(_redis_url)
83
+ def _get_redis(redis_url: Optional[str] = None):
84
+ configured_url = redis_url if redis_url is not None else _redis_url
85
+ if configured_url:
86
+ return init_redis_from_url(configured_url)
87
+ config = RedisConfig.from_env()
88
+ if config.mode == "cluster":
89
+ return init_redis(config=config)
90
+ return init_redis_from_url(_DEFAULT_REDIS_URL)
75
91
 
76
92
 
77
93
  # --------------------------------------------------------------------------- #
@@ -29,12 +29,22 @@ class RedisConfig:
29
29
 
30
30
  @classmethod
31
31
  def from_env(cls) -> "RedisConfig":
32
- """Load Redis configuration from environment variables."""
32
+ """Load Redis configuration from environment variables.
33
+
34
+ REDIS_CLUSTER_HOST (comma-separated "host:port" list) is the
35
+ preferred way to configure Cluster mode: just setting it is enough
36
+ to switch to cluster mode, no separate REDIS_MODE=cluster needed.
37
+ An explicit REDIS_MODE still takes precedence when set, so existing
38
+ explicit REDIS_MODE=cluster + REDIS_CLUSTER_NODES setups keep working.
39
+ """
33
40
  password = os.environ.get("REDIS_PASSWORD", "")
34
41
  username = os.environ.get("REDIS_USERNAME") or None
35
42
  max_connections = os.environ.get("REDIS_MAX_CONNECTIONS")
36
- mode = os.environ.get("REDIS_MODE", "standalone")
37
- cluster_nodes_str = os.environ.get("REDIS_CLUSTER_NODES")
43
+ cluster_host_str = os.environ.get("REDIS_CLUSTER_HOST")
44
+ cluster_nodes_str = cluster_host_str or os.environ.get("REDIS_CLUSTER_NODES")
45
+ mode = os.environ.get("REDIS_MODE") or (
46
+ "cluster" if cluster_host_str else "standalone"
47
+ )
38
48
  cluster_nodes = None
39
49
  if cluster_nodes_str:
40
50
  cluster_nodes = []
@@ -112,11 +112,11 @@ def init_redis_from_url(url: str) -> Redis:
112
112
  def get_redis() -> Redis:
113
113
  """Get the initialized global Redis client.
114
114
 
115
- If not initialized, will automatically initialize with default config.
115
+ If not initialized, automatically initialize from environment config.
116
116
  """
117
117
  global _redis_client # pylint: disable=global-variable-not-assigned
118
118
  if _redis_client is None:
119
- return init_redis()
119
+ return init_redis(config=RedisConfig.from_env())
120
120
  return _redis_client
121
121
 
122
122
 
@@ -276,11 +276,14 @@ def run_worker(
276
276
  Redis configuration: every redis_* argument defaults to None, meaning
277
277
  "not specified" - when not passed, the value is read from the
278
278
  corresponding REDIS_* environment variable (REDIS_HOST, REDIS_PORT,
279
- REDIS_DB, REDIS_PASSWORD, REDIS_USERNAME, REDIS_MODE,
279
+ REDIS_DB, REDIS_PASSWORD, REDIS_USERNAME, REDIS_MODE, REDIS_CLUSTER_HOST,
280
280
  REDIS_CLUSTER_NODES), falling back to "localhost"/6379/standalone if
281
- neither is set. An explicitly-passed argument always takes precedence
282
- over its env var, for every field, in both standalone and cluster mode
283
- - so either mode can be configured purely programmatically (e.g.
281
+ none is set. Setting REDIS_CLUSTER_HOST (comma-separated "host:port"
282
+ list) alone is enough to switch to cluster mode - no separate
283
+ REDIS_MODE=cluster required, though an explicit REDIS_MODE still wins if
284
+ set. An explicitly-passed argument always takes precedence over its env
285
+ var, for every field, in both standalone and cluster mode - so either
286
+ mode can be configured purely programmatically (e.g.
284
287
  ``redis_mode="cluster", redis_cluster_nodes=[("h1", 6379)]``) or purely
285
288
  via env vars, without the two modes behaving inconsistently.
286
289
 
@@ -12,7 +12,9 @@ from unittest.mock import AsyncMock, MagicMock, patch
12
12
 
13
13
  from typer.testing import CliRunner
14
14
 
15
+ from by_framework.admin import cli
15
16
  from by_framework.admin.cli import app
17
+ from by_framework.common.config import RedisConfig
16
18
 
17
19
  runner = CliRunner()
18
20
 
@@ -43,6 +45,17 @@ def _mock_redis():
43
45
  return MagicMock()
44
46
 
45
47
 
48
+ def _cluster_env(monkeypatch):
49
+ cli._redis_url = None # pylint: disable=protected-access
50
+ monkeypatch.setenv("REDIS_MODE", "cluster")
51
+ monkeypatch.setenv("REDIS_CLUSTER_NODES", "h1:6379,h2:6380")
52
+ monkeypatch.setenv("REDIS_USERNAME", "cluster-user")
53
+ monkeypatch.setenv("REDIS_PASSWORD", "cluster-secret")
54
+ monkeypatch.setenv("REDIS_KEY_SCHEMA_VERSION", "v2")
55
+ monkeypatch.delenv("BYAI_REDIS_URL", raising=False)
56
+ monkeypatch.delenv("REDIS_URL", raising=False)
57
+
58
+
46
59
  # --------------------------------------------------------------------------- #
47
60
  # worker list
48
61
  # --------------------------------------------------------------------------- #
@@ -373,3 +386,102 @@ def test_redis_url_env_var(mock_init, mock_registry_cls, monkeypatch):
373
386
 
374
387
  runner.invoke(app, ["worker", "list"])
375
388
  mock_init.assert_called_with("redis://envhost:6379/2")
389
+
390
+
391
+ def test_help_mentions_cluster_env_configuration():
392
+ result = runner.invoke(app, ["--help"])
393
+
394
+ assert result.exit_code == 0
395
+ assert "REDIS_MODE=cluster" in result.output
396
+ assert "REDIS_CLUSTER_NODES" in result.output
397
+ assert "REDIS_KEY_SCHEMA_VERSION=v2" in result.output
398
+
399
+
400
+ @patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
401
+ @patch("by_framework.admin.cli.init_redis_from_url", return_value=_mock_redis())
402
+ def test_get_redis_uses_cluster_config_from_env(
403
+ mock_init_from_url, mock_init, monkeypatch
404
+ ):
405
+ _cluster_env(monkeypatch)
406
+
407
+ redis = cli._get_redis() # pylint: disable=protected-access
408
+
409
+ assert redis is mock_init.return_value
410
+ mock_init_from_url.assert_not_called()
411
+ mock_init.assert_called_once()
412
+ config = mock_init.call_args.kwargs["config"]
413
+ assert isinstance(config, RedisConfig)
414
+ assert config.mode == "cluster"
415
+ assert config.cluster_nodes == [("h1", 6379), ("h2", 6380)]
416
+ assert config.username == "cluster-user"
417
+ assert config.password == "cluster-secret"
418
+
419
+
420
+ @patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
421
+ @patch("by_framework.admin.cli.init_redis_from_url", return_value=_mock_redis())
422
+ def test_get_redis_prefers_explicit_url_over_cluster_env(
423
+ mock_init_from_url, mock_init, monkeypatch
424
+ ):
425
+ _cluster_env(monkeypatch)
426
+
427
+ redis = cli._get_redis("redis://operator-host:6379/0") # pylint: disable=protected-access
428
+
429
+ assert redis is mock_init_from_url.return_value
430
+ mock_init_from_url.assert_called_once_with("redis://operator-host:6379/0")
431
+ mock_init.assert_not_called()
432
+
433
+
434
+ @patch("by_framework.admin.cli.init_redis", side_effect=RuntimeError("v2 required"))
435
+ def test_get_redis_cluster_mode_surfaces_schema_error(mock_init, monkeypatch):
436
+ monkeypatch.setenv("REDIS_MODE", "cluster")
437
+ monkeypatch.setenv("REDIS_CLUSTER_NODES", "h1:6379,h2:6380")
438
+ monkeypatch.setenv("REDIS_KEY_SCHEMA_VERSION", "v1")
439
+
440
+ try:
441
+ cli._get_redis() # pylint: disable=protected-access
442
+ except RuntimeError as err:
443
+ assert "v2 required" in str(err)
444
+ else:
445
+ raise AssertionError("cluster schema error was not surfaced")
446
+
447
+ mock_init.assert_called_once()
448
+
449
+
450
+ @patch("by_framework.admin.cli.WorkerRegistry")
451
+ @patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
452
+ def test_worker_list_uses_cluster_config(mock_init, mock_registry_cls, monkeypatch):
453
+ _cluster_env(monkeypatch)
454
+ mock_registry = MagicMock()
455
+ mock_registry.get_all_workers = AsyncMock(return_value={})
456
+ mock_registry_cls.return_value = mock_registry
457
+
458
+ result = runner.invoke(app, ["worker", "list"])
459
+
460
+ assert result.exit_code == 0
461
+ assert mock_init.call_args.kwargs["config"].mode == "cluster"
462
+
463
+
464
+ @patch("by_framework.admin.cli.WorkerManager")
465
+ @patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
466
+ def test_type_deny_uses_cluster_config(mock_init, mock_mgr_cls, monkeypatch):
467
+ _cluster_env(monkeypatch)
468
+ mock_mgr = MagicMock()
469
+ mock_mgr.deny_worker_for_type = AsyncMock()
470
+ mock_mgr_cls.return_value = mock_mgr
471
+
472
+ result = runner.invoke(app, ["type", "deny", "chat", "worker-a"])
473
+
474
+ assert result.exit_code == 0
475
+ assert mock_init.call_args.kwargs["config"].mode == "cluster"
476
+
477
+
478
+ @patch("by_framework.admin.cli.build_observability_snapshot", new_callable=AsyncMock)
479
+ @patch("by_framework.admin.cli.init_redis", return_value=_mock_redis())
480
+ def test_metrics_snapshot_uses_cluster_config(mock_init, mock_snapshot, monkeypatch):
481
+ _cluster_env(monkeypatch)
482
+ mock_snapshot.return_value = _SNAPSHOT
483
+
484
+ result = runner.invoke(app, ["metrics", "snapshot"])
485
+
486
+ assert result.exit_code == 0
487
+ assert mock_init.call_args.kwargs["config"].mode == "cluster"
@@ -101,6 +101,53 @@ class TestRedisConfig(unittest.TestCase):
101
101
  else:
102
102
  os.environ.pop("REDIS_CLUSTER_NODES", None)
103
103
 
104
+ def test_from_env_cluster_host_implies_cluster_mode(self):
105
+ """Setting REDIS_CLUSTER_HOST alone (no REDIS_MODE) switches to cluster mode."""
106
+ env_vars = ["REDIS_MODE", "REDIS_CLUSTER_HOST", "REDIS_CLUSTER_NODES"]
107
+ old_values = {k: os.environ.get(k) for k in env_vars}
108
+ try:
109
+ for k in env_vars:
110
+ os.environ.pop(k, None)
111
+
112
+ config = RedisConfig.from_env()
113
+ self.assertEqual(config.mode, "standalone")
114
+ self.assertIsNone(config.cluster_nodes)
115
+
116
+ os.environ["REDIS_CLUSTER_HOST"] = (
117
+ "10.10.168.203:6371,10.10.168.203:6372,10.10.168.203:6373"
118
+ )
119
+ config = RedisConfig.from_env()
120
+ self.assertEqual(config.mode, "cluster")
121
+ self.assertEqual(
122
+ config.cluster_nodes,
123
+ [
124
+ ("10.10.168.203", 6371),
125
+ ("10.10.168.203", 6372),
126
+ ("10.10.168.203", 6373),
127
+ ],
128
+ )
129
+ finally:
130
+ for k, v in old_values.items():
131
+ if v is not None:
132
+ os.environ[k] = v
133
+ else:
134
+ os.environ.pop(k, None)
135
+
136
+ def test_from_env_explicit_redis_mode_overrides_cluster_host(self):
137
+ """An explicit REDIS_MODE still wins over REDIS_CLUSTER_HOST's implied mode."""
138
+ env_vars = ["REDIS_MODE", "REDIS_CLUSTER_HOST"]
139
+ old_values = {k: os.environ.get(k) for k in env_vars}
140
+ try:
141
+ os.environ["REDIS_MODE"] = "standalone"
142
+ os.environ["REDIS_CLUSTER_HOST"] = "h1:6379"
143
+ self.assertEqual(RedisConfig.from_env().mode, "standalone")
144
+ finally:
145
+ for k, v in old_values.items():
146
+ if v is not None:
147
+ os.environ[k] = v
148
+ else:
149
+ os.environ.pop(k, None)
150
+
104
151
  def test_from_env_with_custom_values(self):
105
152
  """Test from_env with custom environment variables."""
106
153
  old_values = {
@@ -1,10 +1,26 @@
1
1
  import os
2
2
  import unittest
3
- from unittest.mock import patch
3
+ from unittest.mock import MagicMock, patch
4
4
 
5
+ from by_framework.client.client import GatewayClient
5
6
  from by_framework.common.config import RedisConfig
6
7
  from by_framework.common.exceptions import RedisConnectionError
7
8
  from by_framework.common.redis_client import close_redis, get_redis, init_redis
9
+ from by_framework.core.discovery import DiscoveryClient, ServiceRegistry
10
+ from by_framework.core.registry import WorkerRegistry
11
+
12
+ REDIS_ENV_VARS = (
13
+ "REDIS_HOST",
14
+ "REDIS_PORT",
15
+ "REDIS_DB",
16
+ "REDIS_PASSWORD",
17
+ "REDIS_USERNAME",
18
+ "REDIS_MAX_CONNECTIONS",
19
+ "REDIS_MODE",
20
+ "REDIS_CLUSTER_NODES",
21
+ "REDIS_CLUSTER_HOST",
22
+ "REDIS_KEY_SCHEMA_VERSION",
23
+ )
8
24
 
9
25
 
10
26
  class TestRedisClient(unittest.IsolatedAsyncioTestCase):
@@ -135,6 +151,149 @@ class TestRedisClient(unittest.IsolatedAsyncioTestCase):
135
151
  self.assertEqual(mock_redis_cls.call_count, 1)
136
152
  self.assertIsNotNone(client)
137
153
 
154
+ async def test_get_redis_uses_standalone_defaults_without_env(self):
155
+ """The default singleton path remains localhost standalone compatible."""
156
+ with patch.dict(os.environ, {}, clear=False):
157
+ for name in REDIS_ENV_VARS:
158
+ os.environ.pop(name, None)
159
+
160
+ with patch("by_framework.common.redis_client.Redis") as mock_redis_cls:
161
+ get_redis()
162
+
163
+ _, kwargs = mock_redis_cls.call_args
164
+ self.assertEqual(kwargs["host"], "localhost")
165
+ self.assertEqual(kwargs["port"], 6379)
166
+ self.assertEqual(kwargs["db"], 0)
167
+ self.assertTrue(kwargs["decode_responses"])
168
+
169
+ async def test_get_redis_uses_cluster_config_from_env(self):
170
+ """The shared default lookup honors REDIS_MODE=cluster env config."""
171
+ with patch.dict(
172
+ os.environ,
173
+ {
174
+ "REDIS_MODE": "cluster",
175
+ "REDIS_CLUSTER_NODES": "h1:6379,h2:6380",
176
+ "REDIS_USERNAME": "cluster-user",
177
+ "REDIS_PASSWORD": "cluster-secret",
178
+ "REDIS_KEY_SCHEMA_VERSION": "v2",
179
+ },
180
+ clear=False,
181
+ ):
182
+ with (
183
+ patch("by_framework.common.redis_client.Redis") as mock_redis_cls,
184
+ patch(
185
+ "by_framework.common.redis_client.RedisCluster"
186
+ ) as mock_cluster_cls,
187
+ ):
188
+ get_redis()
189
+
190
+ mock_redis_cls.assert_not_called()
191
+ mock_cluster_cls.assert_called_once()
192
+ _, kwargs = mock_cluster_cls.call_args
193
+ self.assertEqual(
194
+ [(n.host, n.port) for n in kwargs["startup_nodes"]],
195
+ [("h1", 6379), ("h2", 6380)],
196
+ )
197
+ self.assertEqual(kwargs["username"], "cluster-user")
198
+ self.assertEqual(kwargs["password"], "cluster-secret")
199
+ self.assertTrue(kwargs["decode_responses"])
200
+
201
+ async def test_get_redis_uses_cluster_host_env_without_explicit_mode(self):
202
+ """Setting only REDIS_CLUSTER_HOST (no REDIS_MODE) is enough to
203
+ switch the shared default lookup to cluster mode."""
204
+ with patch.dict(
205
+ os.environ,
206
+ {
207
+ "REDIS_CLUSTER_HOST": "10.10.168.203:6371,10.10.168.203:6372",
208
+ "REDIS_KEY_SCHEMA_VERSION": "v2",
209
+ },
210
+ clear=False,
211
+ ):
212
+ os.environ.pop("REDIS_MODE", None)
213
+ with (
214
+ patch("by_framework.common.redis_client.Redis") as mock_redis_cls,
215
+ patch(
216
+ "by_framework.common.redis_client.RedisCluster"
217
+ ) as mock_cluster_cls,
218
+ ):
219
+ get_redis()
220
+
221
+ mock_redis_cls.assert_not_called()
222
+ mock_cluster_cls.assert_called_once()
223
+ _, kwargs = mock_cluster_cls.call_args
224
+ self.assertEqual(
225
+ [(n.host, n.port) for n in kwargs["startup_nodes"]],
226
+ [("10.10.168.203", 6371), ("10.10.168.203", 6372)],
227
+ )
228
+
229
+ async def test_get_redis_cluster_env_requires_v2_schema(self):
230
+ """Cluster env config fails fast before constructing a client."""
231
+ with patch.dict(
232
+ os.environ,
233
+ {
234
+ "REDIS_MODE": "cluster",
235
+ "REDIS_CLUSTER_NODES": "h1:6379,h2:6380",
236
+ },
237
+ clear=False,
238
+ ):
239
+ os.environ.pop("REDIS_KEY_SCHEMA_VERSION", None)
240
+ with (
241
+ patch("by_framework.common.redis_client.Redis") as mock_redis_cls,
242
+ patch(
243
+ "by_framework.common.redis_client.RedisCluster"
244
+ ) as mock_cluster_cls,
245
+ ):
246
+ with self.assertRaises(RedisConnectionError):
247
+ get_redis()
248
+
249
+ mock_redis_cls.assert_not_called()
250
+ mock_cluster_cls.assert_not_called()
251
+
252
+ async def test_default_component_construction_uses_cluster_env(self):
253
+ """Public constructors using the shared default get a cluster client."""
254
+ with patch.dict(
255
+ os.environ,
256
+ {
257
+ "REDIS_MODE": "cluster",
258
+ "REDIS_CLUSTER_NODES": "h1:6379,h2:6380",
259
+ "REDIS_KEY_SCHEMA_VERSION": "v2",
260
+ },
261
+ clear=False,
262
+ ):
263
+ with patch(
264
+ "by_framework.common.redis_client.RedisCluster"
265
+ ) as mock_cluster_cls:
266
+ service_registry = ServiceRegistry()
267
+ worker_registry = WorkerRegistry()
268
+ gateway_client = GatewayClient()
269
+
270
+ mock_cluster_cls.assert_called_once()
271
+ self.assertIs(service_registry.redis, mock_cluster_cls.return_value)
272
+ self.assertIs(worker_registry.redis, mock_cluster_cls.return_value)
273
+ self.assertIs(gateway_client.redis, mock_cluster_cls.return_value)
274
+
275
+ async def test_explicit_redis_clients_take_precedence(self):
276
+ """Injected Redis clients are not replaced by the shared default lookup."""
277
+ redis = MagicMock()
278
+
279
+ with (
280
+ patch("by_framework.core.discovery.get_redis") as discovery_get_redis,
281
+ patch("by_framework.core.registry.get_redis") as registry_get_redis,
282
+ patch("by_framework.client.client.get_redis") as client_get_redis,
283
+ ):
284
+ service_registry = ServiceRegistry(redis_client=redis)
285
+ discovery_client = DiscoveryClient(redis_client=redis)
286
+ worker_registry = WorkerRegistry(redis_client=redis)
287
+ gateway_client = GatewayClient(redis_client=redis)
288
+
289
+ discovery_get_redis.assert_not_called()
290
+ registry_get_redis.assert_not_called()
291
+ client_get_redis.assert_not_called()
292
+ self.assertIs(service_registry.redis, redis)
293
+ self.assertIs(discovery_client.redis, redis)
294
+ self.assertIs(worker_registry.redis, redis)
295
+ self.assertIs(gateway_client.redis, redis)
296
+
138
297
  async def test_close_redis(self):
139
298
  """Verify that close_redis closes the connection and clears the singleton."""
140
299
  with patch("by_framework.common.redis_client.Redis") as mock_redis_cls:
@@ -314,7 +314,7 @@ wheels = [
314
314
 
315
315
  [[package]]
316
316
  name = "by-framework"
317
- version = "0.2.2.dev7"
317
+ version = "0.2.2.dev8"
318
318
  source = { editable = "." }
319
319
  dependencies = [
320
320
  { name = "dill" },