by-framework 0.2.2.dev7__tar.gz → 0.2.2.dev9__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.dev9}/PKG-INFO +1 -1
  2. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/pyproject.toml +1 -1
  3. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/admin/cli.py +25 -8
  4. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/common/config.py +23 -4
  5. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/common/constants.py +13 -3
  6. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/common/redis_client.py +2 -2
  7. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/app.py +29 -8
  8. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/admin/test_cli.py +112 -0
  9. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/common/test_config.py +94 -0
  10. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/common/test_constants.py +21 -0
  11. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/common/test_redis_client.py +161 -1
  12. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_app.py +69 -0
  13. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/uv.lock +1 -1
  14. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.dockerignore +0 -0
  15. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  16. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  17. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  18. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.github/RELEASING.md +0 -0
  19. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.github/workflows/ci.yml +0 -0
  20. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.github/workflows/publish.yml +0 -0
  21. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.github/workflows/stale.yml +0 -0
  22. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.gitignore +0 -0
  23. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/.pre-commit-config.yaml +0 -0
  24. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/AGENTS.md +0 -0
  25. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/CHANGELOG.md +0 -0
  26. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/CLAUDE.md +0 -0
  27. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/CODE_OF_CONDUCT.md +0 -0
  28. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/CONTRIBUTING.md +0 -0
  29. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/LICENSE +0 -0
  30. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/Makefile +0 -0
  31. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/README.md +0 -0
  32. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/README_zh.md +0 -0
  33. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/SECURITY.md +0 -0
  34. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/assets/img/architecture_en.png +0 -0
  35. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/assets/img/architecture_zh.png +0 -0
  36. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/autoformat.sh +0 -0
  37. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/docs/README.md +0 -0
  38. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/docs/by-framework-trace-change-usage.html +0 -0
  39. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/docs/by-framework-trace-complete-guide.html +0 -0
  40. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
  41. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
  42. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/docs/worker_management.html +0 -0
  43. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-adk/README.md +0 -0
  44. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-adk/pyproject.toml +0 -0
  45. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
  46. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
  47. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-adk/src/by_framework_adk/adapter.py +0 -0
  48. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
  49. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-adk/tests/test_worker.py +0 -0
  50. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/Dockerfile +0 -0
  51. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/README.md +0 -0
  52. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/frontend/index.html +0 -0
  53. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
  54. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/frontend/package.json +0 -0
  55. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
  56. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
  57. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
  58. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/pyproject.toml +0 -0
  59. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
  60. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
  61. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +0 -0
  62. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/src/by_framework_dashboard/static/__init__.py +0 -0
  63. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/src/by_framework_dashboard/static/app.js +0 -0
  64. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/src/by_framework_dashboard/static/index.html +0 -0
  65. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/src/by_framework_dashboard/static/styles.css +0 -0
  66. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
  67. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-byclaw/README.md +0 -0
  68. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
  69. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
  70. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
  71. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
  72. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-postgres/README.md +0 -0
  73. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-postgres/pyproject.toml +0 -0
  74. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
  75. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
  76. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
  77. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/README.md +0 -0
  78. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/pyproject.toml +0 -0
  79. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
  80. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
  81. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +0 -0
  82. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
  83. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
  84. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/tests/test_adapter.py +0 -0
  85. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
  86. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
  87. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-langfuse/README.md +0 -0
  88. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
  89. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +0 -0
  90. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +0 -0
  91. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
  92. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-phoenix/README.md +0 -0
  93. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
  94. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
  95. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
  96. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
  97. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
  98. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-query/pyproject.toml +0 -0
  99. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-query/src/by_framework_trace_query/__init__.py +0 -0
  100. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-query/src/by_framework_trace_query/client.py +0 -0
  101. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-query/src/by_framework_trace_query/merger.py +0 -0
  102. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-query/src/by_framework_trace_query/models.py +0 -0
  103. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-query/src/by_framework_trace_query/redis_source.py +0 -0
  104. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/libs/by-framework-trace-query/tests/test_trace_query_sdk.py +0 -0
  105. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/pylintrc +0 -0
  106. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/scripts/python_quality.sh +0 -0
  107. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/__init__.py +0 -0
  108. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/__main__.py +0 -0
  109. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/admin/__init__.py +0 -0
  110. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/admin/worker_manager.py +0 -0
  111. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/client/__init__.py +0 -0
  112. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/client/byai_client.py +0 -0
  113. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/client/client.py +0 -0
  114. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/common/__init__.py +0 -0
  115. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/common/emitter.py +0 -0
  116. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/common/exceptions.py +0 -0
  117. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/common/logger.py +0 -0
  118. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/__init__.py +0 -0
  119. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/availability.py +0 -0
  120. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/delivery_gate.py +0 -0
  121. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/discovery.py +0 -0
  122. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/extensions/__init__.py +0 -0
  123. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/extensions/agent_config.py +0 -0
  124. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/extensions/agent_config_audit.py +0 -0
  125. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/extensions/plugin.py +0 -0
  126. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/extensions/registry.py +0 -0
  127. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/extensions/trace_provider.py +0 -0
  128. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/__init__.py +0 -0
  129. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/action_type.py +0 -0
  130. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/agent_state.py +0 -0
  131. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/byai_codec.py +0 -0
  132. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/byai_command.py +0 -0
  133. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/byai_types.py +0 -0
  134. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/commands.py +0 -0
  135. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/content_codec.py +0 -0
  136. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/content_type.py +0 -0
  137. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/data_message.py +0 -0
  138. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/data_shapes.py +0 -0
  139. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/event_type.py +0 -0
  140. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/events.py +0 -0
  141. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/message.py +0 -0
  142. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/message_header.py +0 -0
  143. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/responses.py +0 -0
  144. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/protocol/results.py +0 -0
  145. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/registry.py +0 -0
  146. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/__init__.py +0 -0
  147. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
  148. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
  149. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/file_manager.py +0 -0
  150. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/file_paths.py +0 -0
  151. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/file_permissions.py +0 -0
  152. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
  153. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/filestore/base.py +0 -0
  154. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/filestore/local.py +0 -0
  155. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/history/__init__.py +0 -0
  156. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/history/base.py +0 -0
  157. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/history/history_manager.py +0 -0
  158. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/history/in_memory.py +0 -0
  159. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/runtime/session_manager.py +0 -0
  160. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/wakeup_controller.py +0 -0
  161. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/core/workspace.py +0 -0
  162. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/errors/__init__.py +0 -0
  163. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/errors/base.py +0 -0
  164. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/errors/common.py +0 -0
  165. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/errors/execution.py +0 -0
  166. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/errors/http.py +0 -0
  167. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/errors/protocol.py +0 -0
  168. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/errors/registry.py +0 -0
  169. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/metrics/__init__.py +0 -0
  170. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/metrics/catalog.py +0 -0
  171. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/metrics/collector.py +0 -0
  172. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/metrics/read_client.py +0 -0
  173. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/metrics/snapshot.py +0 -0
  174. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/trace/__init__.py +0 -0
  175. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/trace/external_trace.py +0 -0
  176. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/trace/span_recorder.py +0 -0
  177. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/trace/trace_schema.py +0 -0
  178. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/trace/trace_writer.py +0 -0
  179. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/util/__init__.py +0 -0
  180. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/util/discovery_http_client.py +0 -0
  181. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/util/generate_message_id.py +0 -0
  182. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/util/http_client.py +0 -0
  183. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/__init__.py +0 -0
  184. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/_control_handling.py +0 -0
  185. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/_execution_tracking.py +0 -0
  186. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/_message_processing.py +0 -0
  187. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/byai_context.py +0 -0
  188. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/byai_worker.py +0 -0
  189. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/context.py +0 -0
  190. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/heartbeat.py +0 -0
  191. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/processor.py +0 -0
  192. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/runner.py +0 -0
  193. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/sandbox/__init__.py +0 -0
  194. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
  195. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/src/by_framework/worker/worker.py +0 -0
  196. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/admin/__init__.py +0 -0
  197. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/client/__init__.py +0 -0
  198. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/client/test_client.py +0 -0
  199. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/common/__init__.py +0 -0
  200. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/common/test_exceptions.py +0 -0
  201. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/common/test_logger.py +0 -0
  202. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/conftest.py +0 -0
  203. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/__init__.py +0 -0
  204. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/protocol/__init__.py +0 -0
  205. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/protocol/test_byai_codec.py +0 -0
  206. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/protocol/test_command_wire.py +0 -0
  207. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/protocol/test_protocol.py +0 -0
  208. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/protocol/test_results.py +0 -0
  209. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/history/__init__.py +0 -0
  210. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/history/test_history_persistence.py +0 -0
  211. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/test_file_access_context.py +0 -0
  212. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
  213. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/test_file_paths.py +0 -0
  214. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/test_file_permissions.py +0 -0
  215. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/test_filestore_local_collection.py +0 -0
  216. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
  217. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/test_filestore_local_read.py +0 -0
  218. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/runtime/test_filestore_local_search.py +0 -0
  219. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/test_agent_config_audit.py +0 -0
  220. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/test_availability.py +0 -0
  221. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/test_discovery.py +0 -0
  222. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/test_registry.py +0 -0
  223. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/core/test_worker_manager.py +0 -0
  224. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/integration/__init__.py +0 -0
  225. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/integration/test_ask_user_flow.py +0 -0
  226. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/integration/test_callback_flow.py +0 -0
  227. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/integration/test_logger_integration.py +0 -0
  228. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/integration/test_scatter_gather.py +0 -0
  229. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/metrics/test_catalog.py +0 -0
  230. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/metrics/test_collector.py +0 -0
  231. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/metrics/test_metrics.py +0 -0
  232. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/metrics/test_read_client.py +0 -0
  233. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/metrics/test_snapshot.py +0 -0
  234. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/plugin/__init__.py +0 -0
  235. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/plugin/test_langfuse_plugin.py +0 -0
  236. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/plugin/test_plugin_discovery.py +0 -0
  237. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/plugin/test_plugin_improvements.py +0 -0
  238. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/plugin/test_plugin_registry.py +0 -0
  239. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/plugin/test_plugin_system.py +0 -0
  240. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/trace/test_external_trace.py +0 -0
  241. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/trace/test_logger_correlation.py +0 -0
  242. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/trace/test_otel_exporter.py +0 -0
  243. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/trace/test_span_recorder.py +0 -0
  244. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/trace/test_trace_writer.py +0 -0
  245. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/util/test_discovery_http_client.py +0 -0
  246. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/util/test_discovery_http_client_download.py +0 -0
  247. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/util/test_discovery_http_client_upload.py +0 -0
  248. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/util/test_http_client.py +0 -0
  249. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/__init__.py +0 -0
  250. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_admin_control_handling.py +0 -0
  251. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_byai_worker.py +0 -0
  252. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_context.py +0 -0
  253. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_control_handling.py +0 -0
  254. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_emitter.py +0 -0
  255. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_fetch_messages.py +0 -0
  256. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_gateway_worker.py +0 -0
  257. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_heartbeat.py +0 -0
  258. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_message_processing.py +0 -0
  259. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_processor.py +0 -0
  260. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_runner.py +0 -0
  261. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/tests/worker/test_sandbox.py +0 -0
  262. {by_framework-0.2.2.dev7 → by_framework-0.2.2.dev9}/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.dev9
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.dev9"
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,13 @@ 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
+ "both cluster mode and REDIS_KEY_SCHEMA_VERSION=v2 unless set "
35
+ "explicitly). REDIS_MODE=cluster + REDIS_CLUSTER_NODES + "
36
+ "REDIS_KEY_SCHEMA_VERSION=v2 also still works."
37
+ ),
31
38
  )
32
39
  worker_app = typer.Typer(no_args_is_help=True)
33
40
  type_app = typer.Typer(no_args_is_help=True)
@@ -43,7 +50,8 @@ app.add_typer(metrics_app, name="metrics", help="Metrics and observability")
43
50
 
44
51
  console = Console()
45
52
  err_console = Console(stderr=True)
46
- _redis_url = "redis://localhost:6379/0"
53
+ _DEFAULT_REDIS_URL = "redis://localhost:6379/0"
54
+ _redis_url: Optional[str] = None
47
55
 
48
56
 
49
57
  @app.callback()
@@ -52,13 +60,16 @@ def _global(
52
60
  None,
53
61
  "--redis-url",
54
62
  envvar=["BYAI_REDIS_URL", "REDIS_URL"],
55
- help="Redis connection URL [env: BYAI_REDIS_URL]",
63
+ help=(
64
+ "Standalone Redis connection URL [env: BYAI_REDIS_URL]. "
65
+ "For Redis Cluster, omit this and set REDIS_CLUSTER_HOST "
66
+ "(comma-separated host:port list)."
67
+ ),
56
68
  ),
57
69
  ):
58
70
  """by-framework cluster admin CLI."""
59
71
  global _redis_url
60
- if redis_url:
61
- _redis_url = redis_url
72
+ _redis_url = redis_url
62
73
 
63
74
 
64
75
  def _run(coro): # type: ignore[no-untyped-def]
@@ -70,8 +81,14 @@ def _die(msg: str, code: int = 1) -> None:
70
81
  raise typer.Exit(code)
71
82
 
72
83
 
73
- def _get_redis():
74
- return init_redis_from_url(_redis_url)
84
+ def _get_redis(redis_url: Optional[str] = None):
85
+ configured_url = redis_url if redis_url is not None else _redis_url
86
+ if configured_url:
87
+ return init_redis_from_url(configured_url)
88
+ config = RedisConfig.from_env()
89
+ if config.mode == "cluster":
90
+ return init_redis(config=config)
91
+ return init_redis_from_url(_DEFAULT_REDIS_URL)
75
92
 
76
93
 
77
94
  # --------------------------------------------------------------------------- #
@@ -9,6 +9,7 @@ from dataclasses import dataclass, field
9
9
  from typing import Literal, Optional
10
10
 
11
11
  from by_framework.common.constants import RedisKeys
12
+ from by_framework.common.logger import logger
12
13
 
13
14
 
14
15
  @dataclass(frozen=True)
@@ -29,22 +30,40 @@ class RedisConfig:
29
30
 
30
31
  @classmethod
31
32
  def from_env(cls) -> "RedisConfig":
32
- """Load Redis configuration from environment variables."""
33
+ """Load Redis configuration from environment variables.
34
+
35
+ REDIS_CLUSTER_HOST (comma-separated "host:port" list) is the
36
+ preferred way to configure Cluster mode: just setting it is enough
37
+ to switch to cluster mode, no separate REDIS_MODE=cluster needed.
38
+ An explicit REDIS_MODE still takes precedence when set, so existing
39
+ explicit REDIS_MODE=cluster + REDIS_CLUSTER_NODES setups keep working.
40
+
41
+ REDIS_DATABASE replaces REDIS_DB (which still works as a deprecated
42
+ fallback, logging a warning, during the transition period).
43
+ """
33
44
  password = os.environ.get("REDIS_PASSWORD", "")
34
45
  username = os.environ.get("REDIS_USERNAME") or None
35
46
  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")
47
+ cluster_host_str = os.environ.get("REDIS_CLUSTER_HOST")
48
+ cluster_nodes_str = cluster_host_str or os.environ.get("REDIS_CLUSTER_NODES")
49
+ mode = os.environ.get("REDIS_MODE") or (
50
+ "cluster" if cluster_host_str else "standalone"
51
+ )
38
52
  cluster_nodes = None
39
53
  if cluster_nodes_str:
40
54
  cluster_nodes = []
41
55
  for node in cluster_nodes_str.split(","):
42
56
  node_host, node_port = node.rsplit(":", 1)
43
57
  cluster_nodes.append((node_host, int(node_port)))
58
+ db_str = os.environ.get("REDIS_DATABASE")
59
+ if db_str is None:
60
+ db_str = os.environ.get("REDIS_DB")
61
+ if db_str is not None:
62
+ logger.warning("REDIS_DB is deprecated, use REDIS_DATABASE instead")
44
63
  return cls(
45
64
  host=os.environ.get("REDIS_HOST", "localhost"),
46
65
  port=int(os.environ.get("REDIS_PORT", "6379")),
47
- db=int(os.environ.get("REDIS_DB", "0")),
66
+ db=int(db_str) if db_str is not None else 0,
48
67
  password=password,
49
68
  username=username,
50
69
  max_connections=int(max_connections) if max_connections else None,
@@ -12,10 +12,20 @@ from typing import Optional
12
12
  def get_key_schema_version() -> str:
13
13
  """Return the configured Redis key schema version ("v1" or "v2").
14
14
 
15
- Controlled by REDIS_KEY_SCHEMA_VERSION, defaulting to "v1" (the current
16
- unprefixed key format). Cluster mode requires "v2" (see redis_client.init_redis).
15
+ Controlled by REDIS_KEY_SCHEMA_VERSION, which always wins when set
16
+ explicitly. When it isn't set, REDIS_CLUSTER_HOST being configured
17
+ implies "v2" (cluster mode requires v2 - v1 keys have no hash tags and
18
+ hit CROSSSLOT errors under Cluster; see redis_client.init_redis's
19
+ fail-fast check); otherwise it defaults to "v1" (the legacy unprefixed
20
+ key format). This mirrors RedisConfig.from_env()'s REDIS_MODE/
21
+ REDIS_CLUSTER_HOST precedence, but deliberately does NOT infer "v2"
22
+ from an explicit REDIS_MODE=cluster alone (without REDIS_CLUSTER_HOST)
23
+ - that legacy explicit-mode path still requires
24
+ REDIS_KEY_SCHEMA_VERSION=v2 to be set by hand.
17
25
  """
18
- version = os.environ.get("REDIS_KEY_SCHEMA_VERSION", "v1")
26
+ version = os.environ.get("REDIS_KEY_SCHEMA_VERSION")
27
+ if version is None:
28
+ version = "v2" if os.environ.get("REDIS_CLUSTER_HOST") else "v1"
19
29
  if version not in ("v1", "v2"):
20
30
  raise ValueError(
21
31
  f"Invalid REDIS_KEY_SCHEMA_VERSION: {version!r} (must be 'v1' or 'v2')"
@@ -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
 
@@ -131,6 +131,17 @@ async def _run_worker_async(
131
131
  # parties can configure either mode purely programmatically or purely
132
132
  # via env vars, without the two modes behaving inconsistently.
133
133
  env_config = RedisConfig.from_env()
134
+ if redis_mode is not None:
135
+ effective_mode = redis_mode
136
+ elif redis_cluster_nodes:
137
+ # Mirrors REDIS_CLUSTER_HOST's env-var precedence: passing
138
+ # redis_cluster_nodes alone is enough to imply cluster mode, no
139
+ # separate redis_mode="cluster" required. Without this, passing
140
+ # redis_cluster_nodes alone would silently stay in standalone mode
141
+ # and the nodes list would be dropped on the floor.
142
+ effective_mode = "cluster"
143
+ else:
144
+ effective_mode = env_config.mode
134
145
  effective_config = replace(
135
146
  env_config,
136
147
  host=redis_host if redis_host is not None else env_config.host,
@@ -142,7 +153,7 @@ async def _run_worker_async(
142
153
  username=(
143
154
  redis_username if redis_username is not None else env_config.username
144
155
  ),
145
- mode=redis_mode if redis_mode is not None else env_config.mode,
156
+ mode=effective_mode,
146
157
  cluster_nodes=(
147
158
  redis_cluster_nodes
148
159
  if redis_cluster_nodes is not None
@@ -276,13 +287,23 @@ def run_worker(
276
287
  Redis configuration: every redis_* argument defaults to None, meaning
277
288
  "not specified" - when not passed, the value is read from the
278
289
  corresponding REDIS_* environment variable (REDIS_HOST, REDIS_PORT,
279
- REDIS_DB, REDIS_PASSWORD, REDIS_USERNAME, REDIS_MODE,
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.
284
- ``redis_mode="cluster", redis_cluster_nodes=[("h1", 6379)]``) or purely
285
- via env vars, without the two modes behaving inconsistently.
290
+ REDIS_DATABASE, REDIS_PASSWORD, REDIS_USERNAME, REDIS_MODE,
291
+ REDIS_CLUSTER_HOST, REDIS_CLUSTER_NODES), falling back to
292
+ "localhost"/6379/standalone if none is set. REDIS_DATABASE replaces
293
+ REDIS_DB (still works as a deprecated fallback, logging a warning).
294
+ Setting REDIS_CLUSTER_HOST (comma-separated "host:port" list) alone is
295
+ enough to switch to cluster mode - no separate REDIS_MODE=cluster
296
+ required, though an explicit REDIS_MODE still wins if set; it also
297
+ implies REDIS_KEY_SCHEMA_VERSION=v2 unless that's set explicitly. An
298
+ explicitly-passed argument always takes precedence over its env var,
299
+ for every field, in both standalone and cluster mode. The same
300
+ single-argument convenience carries over to the programmatic path:
301
+ passing just ``redis_cluster_nodes=[("h1", 6379)]`` (no ``redis_mode``)
302
+ is enough to switch to cluster mode, mirroring REDIS_CLUSTER_HOST -
303
+ ``redis_mode`` only needs to be passed to explicitly force a mode
304
+ (e.g. pin a script to standalone regardless of REDIS_CLUSTER_HOST).
305
+ So either mode can be configured purely programmatically or purely via
306
+ env vars, without the two paths behaving inconsistently.
286
307
 
287
308
  Plugin support:
288
309
  - **Auto mode**: System automatically discovers and loads all subclasses
@@ -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"
@@ -49,6 +49,7 @@ class TestRedisConfig(unittest.TestCase):
49
49
  env_vars = [
50
50
  "REDIS_HOST",
51
51
  "REDIS_PORT",
52
+ "REDIS_DATABASE",
52
53
  "REDIS_DB",
53
54
  "REDIS_PASSWORD",
54
55
  "REDIS_USERNAME",
@@ -101,6 +102,99 @@ class TestRedisConfig(unittest.TestCase):
101
102
  else:
102
103
  os.environ.pop("REDIS_CLUSTER_NODES", None)
103
104
 
105
+ def test_from_env_reads_redis_database(self):
106
+ """Test that from_env reads db from REDIS_DATABASE."""
107
+ env_vars = ["REDIS_DATABASE", "REDIS_DB"]
108
+ old_values = {k: os.environ.get(k) for k in env_vars}
109
+ try:
110
+ for k in env_vars:
111
+ os.environ.pop(k, None)
112
+ os.environ["REDIS_DATABASE"] = "3"
113
+ self.assertEqual(RedisConfig.from_env().db, 3)
114
+ finally:
115
+ for k, v in old_values.items():
116
+ if v is not None:
117
+ os.environ[k] = v
118
+ else:
119
+ os.environ.pop(k, None)
120
+
121
+ def test_from_env_falls_back_to_deprecated_redis_db(self):
122
+ """Test that from_env falls back to REDIS_DB when REDIS_DATABASE is unset."""
123
+ env_vars = ["REDIS_DATABASE", "REDIS_DB"]
124
+ old_values = {k: os.environ.get(k) for k in env_vars}
125
+ try:
126
+ os.environ.pop("REDIS_DATABASE", None)
127
+ os.environ["REDIS_DB"] = "5"
128
+ self.assertEqual(RedisConfig.from_env().db, 5)
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_redis_database_takes_precedence_over_deprecated_redis_db(self):
137
+ """Test that REDIS_DATABASE wins over REDIS_DB when both are set."""
138
+ env_vars = ["REDIS_DATABASE", "REDIS_DB"]
139
+ old_values = {k: os.environ.get(k) for k in env_vars}
140
+ try:
141
+ os.environ["REDIS_DATABASE"] = "2"
142
+ os.environ["REDIS_DB"] = "9"
143
+ self.assertEqual(RedisConfig.from_env().db, 2)
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
+
151
+ def test_from_env_cluster_host_implies_cluster_mode(self):
152
+ """Setting REDIS_CLUSTER_HOST alone (no REDIS_MODE) switches to cluster mode."""
153
+ env_vars = ["REDIS_MODE", "REDIS_CLUSTER_HOST", "REDIS_CLUSTER_NODES"]
154
+ old_values = {k: os.environ.get(k) for k in env_vars}
155
+ try:
156
+ for k in env_vars:
157
+ os.environ.pop(k, None)
158
+
159
+ config = RedisConfig.from_env()
160
+ self.assertEqual(config.mode, "standalone")
161
+ self.assertIsNone(config.cluster_nodes)
162
+
163
+ os.environ["REDIS_CLUSTER_HOST"] = (
164
+ "10.10.168.203:6371,10.10.168.203:6372,10.10.168.203:6373"
165
+ )
166
+ config = RedisConfig.from_env()
167
+ self.assertEqual(config.mode, "cluster")
168
+ self.assertEqual(
169
+ config.cluster_nodes,
170
+ [
171
+ ("10.10.168.203", 6371),
172
+ ("10.10.168.203", 6372),
173
+ ("10.10.168.203", 6373),
174
+ ],
175
+ )
176
+ finally:
177
+ for k, v in old_values.items():
178
+ if v is not None:
179
+ os.environ[k] = v
180
+ else:
181
+ os.environ.pop(k, None)
182
+
183
+ def test_from_env_explicit_redis_mode_overrides_cluster_host(self):
184
+ """An explicit REDIS_MODE still wins over REDIS_CLUSTER_HOST's implied mode."""
185
+ env_vars = ["REDIS_MODE", "REDIS_CLUSTER_HOST"]
186
+ old_values = {k: os.environ.get(k) for k in env_vars}
187
+ try:
188
+ os.environ["REDIS_MODE"] = "standalone"
189
+ os.environ["REDIS_CLUSTER_HOST"] = "h1:6379"
190
+ self.assertEqual(RedisConfig.from_env().mode, "standalone")
191
+ finally:
192
+ for k, v in old_values.items():
193
+ if v is not None:
194
+ os.environ[k] = v
195
+ else:
196
+ os.environ.pop(k, None)
197
+
104
198
  def test_from_env_with_custom_values(self):
105
199
  """Test from_env with custom environment variables."""
106
200
  old_values = {
@@ -478,12 +478,18 @@ class TestGetKeySchemaVersion(unittest.TestCase):
478
478
 
479
479
  def setUp(self):
480
480
  self._old_value = os.environ.get("REDIS_KEY_SCHEMA_VERSION")
481
+ self._old_cluster_host = os.environ.get("REDIS_CLUSTER_HOST")
482
+ os.environ.pop("REDIS_CLUSTER_HOST", None)
481
483
 
482
484
  def tearDown(self):
483
485
  if self._old_value is not None:
484
486
  os.environ["REDIS_KEY_SCHEMA_VERSION"] = self._old_value
485
487
  else:
486
488
  os.environ.pop("REDIS_KEY_SCHEMA_VERSION", None)
489
+ if self._old_cluster_host is not None:
490
+ os.environ["REDIS_CLUSTER_HOST"] = self._old_cluster_host
491
+ else:
492
+ os.environ.pop("REDIS_CLUSTER_HOST", None)
487
493
 
488
494
  def test_defaults_to_v1(self):
489
495
  """Test that the schema version defaults to v1 when unset."""
@@ -501,6 +507,21 @@ class TestGetKeySchemaVersion(unittest.TestCase):
501
507
  with self.assertRaises(ValueError):
502
508
  get_key_schema_version()
503
509
 
510
+ def test_defaults_to_v2_when_cluster_host_set(self):
511
+ """REDIS_CLUSTER_HOST alone (no explicit schema version) implies v2."""
512
+ os.environ.pop("REDIS_KEY_SCHEMA_VERSION", None)
513
+ os.environ["REDIS_CLUSTER_HOST"] = "h1:6379,h2:6380"
514
+ self.assertEqual(get_key_schema_version(), "v2")
515
+
516
+ def test_explicit_value_wins_over_cluster_host(self):
517
+ """An explicit REDIS_KEY_SCHEMA_VERSION always wins, even if it
518
+ contradicts REDIS_CLUSTER_HOST's implied v2 - redis_client.init_redis's
519
+ fail-fast check is what catches this combination, not silent
520
+ auto-correction here."""
521
+ os.environ["REDIS_CLUSTER_HOST"] = "h1:6379"
522
+ os.environ["REDIS_KEY_SCHEMA_VERSION"] = "v1"
523
+ self.assertEqual(get_key_schema_version(), "v1")
524
+
504
525
 
505
526
  if __name__ == "__main__":
506
527
  unittest.main()