by-framework 0.2.2.dev9__tar.gz → 0.2.2.dev10__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.dev9 → by_framework-0.2.2.dev10}/PKG-INFO +1 -1
  2. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/pyproject.toml +1 -1
  3. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/__init__.py +2 -0
  4. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/client/client.py +124 -3
  5. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/common/emitter.py +16 -0
  6. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/__init__.py +2 -0
  7. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/__init__.py +2 -0
  8. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/content_type.py +1 -0
  9. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/responses.py +11 -0
  10. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/registry.py +2 -1
  11. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/runner.py +10 -0
  12. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/client/test_client.py +345 -0
  13. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/common/test_config.py +38 -0
  14. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/common/test_constants.py +6 -0
  15. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/test_registry.py +32 -0
  16. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_emitter.py +41 -1
  17. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_runner.py +76 -0
  18. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/uv.lock +1 -1
  19. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.dockerignore +0 -0
  20. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  21. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  22. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  23. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.github/RELEASING.md +0 -0
  24. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.github/workflows/ci.yml +0 -0
  25. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.github/workflows/publish.yml +0 -0
  26. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.github/workflows/stale.yml +0 -0
  27. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.gitignore +0 -0
  28. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/.pre-commit-config.yaml +0 -0
  29. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/AGENTS.md +0 -0
  30. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/CHANGELOG.md +0 -0
  31. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/CLAUDE.md +0 -0
  32. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/CODE_OF_CONDUCT.md +0 -0
  33. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/CONTRIBUTING.md +0 -0
  34. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/LICENSE +0 -0
  35. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/Makefile +0 -0
  36. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/README.md +0 -0
  37. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/README_zh.md +0 -0
  38. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/SECURITY.md +0 -0
  39. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/assets/img/architecture_en.png +0 -0
  40. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/assets/img/architecture_zh.png +0 -0
  41. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/autoformat.sh +0 -0
  42. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/docs/README.md +0 -0
  43. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/docs/by-framework-trace-change-usage.html +0 -0
  44. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/docs/by-framework-trace-complete-guide.html +0 -0
  45. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/docs/plans/2026-05-15-worker-task-state-stats.md +0 -0
  46. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/docs/plans/2026-06-10-trace-metrics-correlation.md +0 -0
  47. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/docs/worker_management.html +0 -0
  48. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-adk/README.md +0 -0
  49. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-adk/pyproject.toml +0 -0
  50. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-adk/src/by_framework_adk/__init__.py +0 -0
  51. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-adk/src/by_framework_adk/_utils.py +0 -0
  52. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-adk/src/by_framework_adk/adapter.py +0 -0
  53. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-adk/src/by_framework_adk/worker.py +0 -0
  54. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-adk/tests/test_worker.py +0 -0
  55. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/Dockerfile +0 -0
  56. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/README.md +0 -0
  57. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/frontend/index.html +0 -0
  58. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/frontend/package-lock.json +0 -0
  59. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/frontend/package.json +0 -0
  60. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/frontend/src/main.jsx +0 -0
  61. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/frontend/src/styles.css +0 -0
  62. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/frontend/vite.config.js +0 -0
  63. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/pyproject.toml +0 -0
  64. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/src/by_framework_dashboard/__init__.py +0 -0
  65. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/src/by_framework_dashboard/adapters.py +0 -0
  66. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/src/by_framework_dashboard/dashboard.py +0 -0
  67. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/src/by_framework_dashboard/static/__init__.py +0 -0
  68. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/src/by_framework_dashboard/static/app.js +0 -0
  69. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/src/by_framework_dashboard/static/index.html +0 -0
  70. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/src/by_framework_dashboard/static/styles.css +0 -0
  71. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-dashboard/tests/test_dashboard_server.py +0 -0
  72. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-byclaw/README.md +0 -0
  73. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-byclaw/pyproject.toml +0 -0
  74. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/__init__.py +0 -0
  75. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-byclaw/src/by_framework_history_byclaw/byclaw_history.py +0 -0
  76. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-byclaw/tests/test_byclaw_history.py +0 -0
  77. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-postgres/README.md +0 -0
  78. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-postgres/pyproject.toml +0 -0
  79. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-postgres/src/by_framework_history_postgres/__init__.py +0 -0
  80. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-postgres/src/by_framework_history_postgres/postgres.py +0 -0
  81. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-history-postgres/tests/test_postgres_history_storage.py +0 -0
  82. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/README.md +0 -0
  83. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/pyproject.toml +0 -0
  84. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/src/by_framework_langgraph/__init__.py +0 -0
  85. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/src/by_framework_langgraph/_utils.py +0 -0
  86. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/src/by_framework_langgraph/adapter.py +0 -0
  87. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/src/by_framework_langgraph/tools.py +0 -0
  88. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/src/by_framework_langgraph/worker.py +0 -0
  89. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/tests/test_adapter.py +0 -0
  90. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/tests/test_tools.py +0 -0
  91. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-langgraph/tests/test_utils.py +0 -0
  92. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-langfuse/README.md +0 -0
  93. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-langfuse/pyproject.toml +0 -0
  94. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/__init__.py +0 -0
  95. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-langfuse/src/by_framework_trace_langfuse/langfuse.py +0 -0
  96. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-langfuse/tests/test_langfuse_import.py +0 -0
  97. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-phoenix/README.md +0 -0
  98. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-phoenix/pyproject.toml +0 -0
  99. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/__init__.py +0 -0
  100. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-phoenix/src/by_framework_trace_phoenix/phoenix.py +0 -0
  101. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-phoenix/tests/test_phoenix_import.py +0 -0
  102. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-phoenix/tests/test_phoenix_plugin.py +0 -0
  103. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-query/pyproject.toml +0 -0
  104. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-query/src/by_framework_trace_query/__init__.py +0 -0
  105. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-query/src/by_framework_trace_query/client.py +0 -0
  106. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-query/src/by_framework_trace_query/merger.py +0 -0
  107. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-query/src/by_framework_trace_query/models.py +0 -0
  108. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-query/src/by_framework_trace_query/redis_source.py +0 -0
  109. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/libs/by-framework-trace-query/tests/test_trace_query_sdk.py +0 -0
  110. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/pylintrc +0 -0
  111. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/scripts/python_quality.sh +0 -0
  112. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/__main__.py +0 -0
  113. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/admin/__init__.py +0 -0
  114. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/admin/cli.py +0 -0
  115. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/admin/worker_manager.py +0 -0
  116. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/client/__init__.py +0 -0
  117. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/client/byai_client.py +0 -0
  118. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/common/__init__.py +0 -0
  119. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/common/config.py +0 -0
  120. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/common/constants.py +0 -0
  121. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/common/exceptions.py +0 -0
  122. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/common/logger.py +0 -0
  123. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/common/redis_client.py +0 -0
  124. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/availability.py +0 -0
  125. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/delivery_gate.py +0 -0
  126. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/discovery.py +0 -0
  127. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/extensions/__init__.py +0 -0
  128. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/extensions/agent_config.py +0 -0
  129. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/extensions/agent_config_audit.py +0 -0
  130. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/extensions/plugin.py +0 -0
  131. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/extensions/registry.py +0 -0
  132. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/extensions/trace_provider.py +0 -0
  133. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/action_type.py +0 -0
  134. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/agent_state.py +0 -0
  135. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/byai_codec.py +0 -0
  136. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/byai_command.py +0 -0
  137. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/byai_types.py +0 -0
  138. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/commands.py +0 -0
  139. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/content_codec.py +0 -0
  140. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/data_message.py +0 -0
  141. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/data_shapes.py +0 -0
  142. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/event_type.py +0 -0
  143. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/events.py +0 -0
  144. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/message.py +0 -0
  145. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/message_header.py +0 -0
  146. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/protocol/results.py +0 -0
  147. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/__init__.py +0 -0
  148. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/agent_config_manager.py +0 -0
  149. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/agent_runtime_state.py +0 -0
  150. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/file_manager.py +0 -0
  151. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/file_paths.py +0 -0
  152. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/file_permissions.py +0 -0
  153. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/filestore/__init__.py +0 -0
  154. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/filestore/base.py +0 -0
  155. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/filestore/local.py +0 -0
  156. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/history/__init__.py +0 -0
  157. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/history/base.py +0 -0
  158. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/history/history_manager.py +0 -0
  159. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/history/in_memory.py +0 -0
  160. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/runtime/session_manager.py +0 -0
  161. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/wakeup_controller.py +0 -0
  162. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/core/workspace.py +0 -0
  163. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/errors/__init__.py +0 -0
  164. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/errors/base.py +0 -0
  165. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/errors/common.py +0 -0
  166. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/errors/execution.py +0 -0
  167. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/errors/http.py +0 -0
  168. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/errors/protocol.py +0 -0
  169. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/errors/registry.py +0 -0
  170. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/metrics/__init__.py +0 -0
  171. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/metrics/catalog.py +0 -0
  172. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/metrics/collector.py +0 -0
  173. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/metrics/read_client.py +0 -0
  174. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/metrics/snapshot.py +0 -0
  175. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/trace/__init__.py +0 -0
  176. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/trace/external_trace.py +0 -0
  177. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/trace/span_recorder.py +0 -0
  178. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/trace/trace_schema.py +0 -0
  179. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/trace/trace_writer.py +0 -0
  180. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/util/__init__.py +0 -0
  181. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/util/discovery_http_client.py +0 -0
  182. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/util/generate_message_id.py +0 -0
  183. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/util/http_client.py +0 -0
  184. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/__init__.py +0 -0
  185. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/_control_handling.py +0 -0
  186. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/_execution_tracking.py +0 -0
  187. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/_message_processing.py +0 -0
  188. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/app.py +0 -0
  189. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/byai_context.py +0 -0
  190. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/byai_worker.py +0 -0
  191. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/context.py +0 -0
  192. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/heartbeat.py +0 -0
  193. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/processor.py +0 -0
  194. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/sandbox/__init__.py +0 -0
  195. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/sandbox/hook_sandbox.py +0 -0
  196. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/src/by_framework/worker/worker.py +0 -0
  197. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/admin/__init__.py +0 -0
  198. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/admin/test_cli.py +0 -0
  199. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/client/__init__.py +0 -0
  200. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/common/__init__.py +0 -0
  201. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/common/test_exceptions.py +0 -0
  202. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/common/test_logger.py +0 -0
  203. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/common/test_redis_client.py +0 -0
  204. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/conftest.py +0 -0
  205. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/__init__.py +0 -0
  206. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/protocol/__init__.py +0 -0
  207. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/protocol/test_byai_codec.py +0 -0
  208. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/protocol/test_command_wire.py +0 -0
  209. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/protocol/test_protocol.py +0 -0
  210. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/protocol/test_results.py +0 -0
  211. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/history/__init__.py +0 -0
  212. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/history/test_history_persistence.py +0 -0
  213. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/test_file_access_context.py +0 -0
  214. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/test_file_manager_default_storage.py +0 -0
  215. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/test_file_paths.py +0 -0
  216. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/test_file_permissions.py +0 -0
  217. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/test_filestore_local_collection.py +0 -0
  218. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/test_filestore_local_mutation.py +0 -0
  219. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/test_filestore_local_read.py +0 -0
  220. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/runtime/test_filestore_local_search.py +0 -0
  221. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/test_agent_config_audit.py +0 -0
  222. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/test_availability.py +0 -0
  223. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/test_discovery.py +0 -0
  224. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/core/test_worker_manager.py +0 -0
  225. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/integration/__init__.py +0 -0
  226. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/integration/test_ask_user_flow.py +0 -0
  227. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/integration/test_callback_flow.py +0 -0
  228. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/integration/test_logger_integration.py +0 -0
  229. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/integration/test_scatter_gather.py +0 -0
  230. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/metrics/test_catalog.py +0 -0
  231. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/metrics/test_collector.py +0 -0
  232. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/metrics/test_metrics.py +0 -0
  233. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/metrics/test_read_client.py +0 -0
  234. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/metrics/test_snapshot.py +0 -0
  235. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/plugin/__init__.py +0 -0
  236. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/plugin/test_langfuse_plugin.py +0 -0
  237. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/plugin/test_plugin_discovery.py +0 -0
  238. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/plugin/test_plugin_improvements.py +0 -0
  239. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/plugin/test_plugin_registry.py +0 -0
  240. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/plugin/test_plugin_system.py +0 -0
  241. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/trace/test_external_trace.py +0 -0
  242. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/trace/test_logger_correlation.py +0 -0
  243. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/trace/test_otel_exporter.py +0 -0
  244. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/trace/test_span_recorder.py +0 -0
  245. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/trace/test_trace_writer.py +0 -0
  246. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/util/test_discovery_http_client.py +0 -0
  247. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/util/test_discovery_http_client_download.py +0 -0
  248. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/util/test_discovery_http_client_upload.py +0 -0
  249. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/util/test_http_client.py +0 -0
  250. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/__init__.py +0 -0
  251. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_admin_control_handling.py +0 -0
  252. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_app.py +0 -0
  253. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_byai_worker.py +0 -0
  254. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_context.py +0 -0
  255. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_control_handling.py +0 -0
  256. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_fetch_messages.py +0 -0
  257. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_gateway_worker.py +0 -0
  258. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_heartbeat.py +0 -0
  259. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_message_processing.py +0 -0
  260. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_processor.py +0 -0
  261. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/tests/worker/test_sandbox.py +0 -0
  262. {by_framework-0.2.2.dev9 → by_framework-0.2.2.dev10}/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.dev9
3
+ Version: 0.2.2.dev10
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.dev9"
3
+ version = "0.2.2.dev10"
4
4
  description = "分布式 Agent 调度框架"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -8,6 +8,7 @@ from `GatewayWorker` and running `run_worker`.
8
8
  from .admin import WorkerManager
9
9
  from .client.byai_client import ByaiGatewayClient
10
10
  from .client.client import (
11
+ CancelSessionResponse,
11
12
  CancelTaskResponse,
12
13
  DataStreamEntry,
13
14
  GatewayClient,
@@ -148,6 +149,7 @@ __all__ = [
148
149
  "DataStreamEntry",
149
150
  "SendMessageResponse",
150
151
  "CancelTaskResponse",
152
+ "CancelSessionResponse",
151
153
  "run_worker",
152
154
  "logger",
153
155
  "setup_logging",
@@ -37,6 +37,7 @@ from by_framework.core.protocol.commands import (
37
37
  from by_framework.core.protocol.data_message import DataMessage
38
38
  from by_framework.core.protocol.message_header import MessageHeader
39
39
  from by_framework.core.protocol.responses import (
40
+ CancelSessionResponse,
40
41
  CancelTaskResponse,
41
42
  ExecutionStatus,
42
43
  SendMessageResponse,
@@ -581,6 +582,99 @@ class GatewayClient:
581
582
  cancelled_count=len(to_cancel),
582
583
  )
583
584
 
585
+ async def cancel_session(
586
+ self,
587
+ session_id: str,
588
+ reason: str = "",
589
+ requested_by: str = "client",
590
+ cancel_mode: str = CancelMode.GRACEFUL,
591
+ ) -> CancelSessionResponse:
592
+ """Cancel every active execution registered under a session.
593
+
594
+ Unlike cancel_task, this does not require a starting message_id:
595
+ it cancels the whole session's active executions in one call.
596
+ """
597
+ if self.registry is None:
598
+ raise ValueError("GatewayClient requires a WorkerRegistry to cancel tasks")
599
+
600
+ all_executions = await self.registry.get_all_session_executions(session_id)
601
+
602
+ if not all_executions:
603
+ return CancelSessionResponse(
604
+ success=False,
605
+ session_id=session_id,
606
+ status=ExecutionStatus.NOT_FOUND,
607
+ timestamp=int(time.time() * 1000),
608
+ cancelled_count=0,
609
+ already_finished_count=0,
610
+ )
611
+
612
+ terminal_states = {"COMPLETED", "FAILED", "CANCELLED"}
613
+ to_cancel = [
614
+ node
615
+ for node in all_executions
616
+ if node.get("status", "") not in terminal_states
617
+ ]
618
+ terminal_nodes = [
619
+ node for node in all_executions if node.get("status", "") in terminal_states
620
+ ]
621
+
622
+ # Flag terminal executions so a still-running descendant can't wake
623
+ # them back up via callback, without changing their status.
624
+ for node in terminal_nodes:
625
+ await self.registry.mark_cancel_requested(
626
+ node.get("execution_id", ""), session_id, reason
627
+ )
628
+
629
+ if not to_cancel:
630
+ return CancelSessionResponse(
631
+ success=False,
632
+ session_id=session_id,
633
+ status=ExecutionStatus.ALREADY_FINISHED,
634
+ timestamp=int(time.time() * 1000),
635
+ cancelled_count=0,
636
+ already_finished_count=len(terminal_nodes),
637
+ )
638
+
639
+ for node in to_cancel:
640
+ node_execution_id = node.get("execution_id", "")
641
+ node_worker_id = node.get("worker_id", "")
642
+ node_message_id = node.get("message_id", "")
643
+
644
+ await self.registry.mark_execution_cancelling(
645
+ node_execution_id, session_id, reason
646
+ )
647
+
648
+ if node_worker_id:
649
+ cancel_command = CancelTaskCommand(
650
+ header=MessageHeader(
651
+ message_id=f"{CANCEL_MESSAGE_ID_PREFIX}{uuid.uuid4().hex[:8]}",
652
+ session_id=session_id,
653
+ trace_id=node.get("trace_id") or uuid.uuid4().hex,
654
+ target_agent_type=node.get("target_agent_type", ""),
655
+ parent_message_id=node_message_id,
656
+ ),
657
+ target_message_id=node_message_id,
658
+ target_execution_id=node_execution_id,
659
+ target_worker_id=node_worker_id,
660
+ reason=reason,
661
+ requested_by=requested_by,
662
+ cancel_mode=cancel_mode,
663
+ )
664
+ await self.redis.xadd(
665
+ RedisKeys.worker_ctrl_stream(node_worker_id),
666
+ cancel_command.to_redis_payload(),
667
+ )
668
+
669
+ return CancelSessionResponse(
670
+ success=True,
671
+ session_id=session_id,
672
+ status=ExecutionStatus.CANCEL_REQUESTED,
673
+ timestamp=int(time.time() * 1000),
674
+ cancelled_count=len(to_cancel),
675
+ already_finished_count=len(terminal_nodes),
676
+ )
677
+
584
678
  async def send_message(
585
679
  self,
586
680
  target_agent_type: str,
@@ -691,7 +785,28 @@ class GatewayClient:
691
785
  content=params["content"],
692
786
  extra_payload=params["extra_payload"],
693
787
  )
694
- execution_id = f"{EXECUTION_ID_PREFIX}{uuid.uuid4().hex[:8]}"
788
+
789
+ # A RESUME reuses the message_id of the original AskAgentCommand so the
790
+ # worker can look the suspended execution back up. Reuse its
791
+ # execution_id too, and skip re-initializing the registry record for
792
+ # it below -- initialize_execution() would otherwise overwrite the
793
+ # message_id -> execution_id mapping and detach this resume from the
794
+ # execution it's meant to continue.
795
+ resumed_execution = None
796
+ if (
797
+ params["action_type"] == ActionType.RESUME.value
798
+ and self.registry
799
+ and hasattr(self.registry, "get_execution_by_message_id")
800
+ ):
801
+ resumed_execution = await self.registry.get_execution_by_message_id(
802
+ message_id, session_id=params["session_id"]
803
+ )
804
+
805
+ execution_id = (
806
+ resumed_execution["execution_id"]
807
+ if resumed_execution
808
+ else f"{EXECUTION_ID_PREFIX}{uuid.uuid4().hex[:8]}"
809
+ )
695
810
 
696
811
  # 3. Resolve route and optionally probe agent type/liveness
697
812
  should_dispatch_control = True
@@ -837,8 +952,14 @@ class GatewayClient:
837
952
  error_code=ExecutionStatus.ERR_AGENT_TYPE_UNAVAILABLE,
838
953
  )
839
954
 
840
- # Initialize execution tracking
841
- if self.registry and hasattr(self.registry, "initialize_execution"):
955
+ # Initialize execution tracking. Skipped for a RESUME that reattached
956
+ # to an existing execution -- that execution is already tracked, and
957
+ # re-initializing it here would overwrite its message_id mapping.
958
+ if (
959
+ not resumed_execution
960
+ and self.registry
961
+ and hasattr(self.registry, "initialize_execution")
962
+ ):
842
963
  try:
843
964
  await self.registry.initialize_execution(
844
965
  {
@@ -107,6 +107,22 @@ class DefaultSseLayoutBuilder(DataLayoutBuilder):
107
107
  parent_order_id: Optional[str] = None,
108
108
  ) -> Dict[str, Any]:
109
109
  """Build default BaiYing-compatible user input form payload."""
110
+ try:
111
+ parsed_prompt = json.loads(prompt)
112
+ except (json.JSONDecodeError, TypeError):
113
+ parsed_prompt = None
114
+
115
+ # Structured question prompts are already in the format expected by clients.
116
+ if isinstance(parsed_prompt, dict) and "questions" in parsed_prompt:
117
+ return self.build(
118
+ content=prompt,
119
+ role="assistant",
120
+ content_type=SseReasonMessageType.ask_user_question.value,
121
+ source_agent_type=source_agent_type,
122
+ order_id=order_id,
123
+ parent_order_id=parent_order_id,
124
+ )
125
+
110
126
  input_form = {
111
127
  "formStatus": 0,
112
128
  "pluginMachineFields": [
@@ -26,6 +26,7 @@ from .protocol import (
26
26
  BaiYingMessage,
27
27
  BaiYingMessageRole,
28
28
  BaseCommand,
29
+ CancelSessionResponse,
29
30
  CancelTaskCommand,
30
31
  CancelTaskResponse,
31
32
  DataMessage,
@@ -53,6 +54,7 @@ from .workspace import WorkspaceManager
53
54
  __all__ = [
54
55
  "SendMessageResponse",
55
56
  "CancelTaskResponse",
57
+ "CancelSessionResponse",
56
58
  "BaiYingMessage",
57
59
  "BaiYingMessageRole",
58
60
  "MessageContent",
@@ -65,6 +65,7 @@ from .message import (
65
65
  )
66
66
  from .message_header import MessageHeader
67
67
  from .responses import (
68
+ CancelSessionResponse,
68
69
  CancelTaskResponse,
69
70
  CancelTaskResponseDict,
70
71
  SendMessageResponse,
@@ -81,6 +82,7 @@ from .results import (
81
82
  __all__ = [
82
83
  "SendMessageResponse",
83
84
  "CancelTaskResponse",
85
+ "CancelSessionResponse",
84
86
  "SendMessageResponseDict",
85
87
  "CancelTaskResponseDict",
86
88
  "AgentTaskResult",
@@ -33,6 +33,7 @@ class SseReasonMessageType(str, Enum):
33
33
  think_code_result = "3007" # thinking process code execution result
34
34
  task_finished = "3009" # task finished
35
35
  task_user_input = "3013" # user input
36
+ ask_user_question = "3014" # ask user question
36
37
  task_create_file = "3010" # create file
37
38
  task_title = "3011" # task title
38
39
  agent_card = "2015" # agent card
@@ -92,3 +92,14 @@ class CancelTaskResponse:
92
92
  timestamp: int
93
93
  error: str = ""
94
94
  cancelled_count: int = 0
95
+
96
+
97
+ @dataclass(frozen=True)
98
+ class CancelSessionResponse:
99
+ success: bool
100
+ session_id: str
101
+ status: str
102
+ timestamp: int
103
+ cancelled_count: int = 0
104
+ already_finished_count: int = 0
105
+ error: str = ""
@@ -1011,7 +1011,8 @@ class WorkerRegistry:
1011
1011
  old_execution = dict(current)
1012
1012
  current["status"] = status
1013
1013
  now = int(time.time() * 1000)
1014
- current["finished_at"] = now
1014
+ if is_terminal_state(status):
1015
+ current["finished_at"] = now
1015
1016
  current["updated_at"] = now
1016
1017
  if completion:
1017
1018
  for key, value in completion.items():
@@ -505,6 +505,16 @@ class WorkerRunner:
505
505
  header.message_id, session_id=header.session_id
506
506
  )
507
507
 
508
+ if isinstance(command, ResumeCommand) and existing_execution is None:
509
+ logger.warning(
510
+ "[%s] ResumeCommand did not resolve to an existing execution "
511
+ "(message_id=%s, session_id=%s); starting a new, disconnected "
512
+ "execution instead of continuing the suspended one.",
513
+ self.worker.worker_id,
514
+ header.message_id,
515
+ header.session_id,
516
+ )
517
+
508
518
  # Skip terminal state replays
509
519
  if (
510
520
  existing_execution
@@ -1022,3 +1022,348 @@ async def test_client_cascading_cancel_root_completed_children_running():
1022
1022
  registry.mark_cancel_requested.assert_called_once_with(
1023
1023
  "exec-a", "sess-1", "user abort"
1024
1024
  )
1025
+
1026
+
1027
+ @pytest.mark.asyncio
1028
+ async def test_client_cancel_session_cancels_all_non_terminal_executions():
1029
+ """Test that cancel_session cancels every non-terminal execution in a
1030
+ session in one call, without needing a starting message_id."""
1031
+ exec_a = {
1032
+ "execution_id": "exec-a",
1033
+ "message_id": "msg-a",
1034
+ "session_id": "sess-1",
1035
+ "worker_id": "worker-1",
1036
+ "target_agent_type": "agent_a",
1037
+ "status": "RUNNING",
1038
+ "parent_message_id": "",
1039
+ }
1040
+ exec_b = {
1041
+ "execution_id": "exec-b",
1042
+ "message_id": "msg-b",
1043
+ "session_id": "sess-1",
1044
+ "worker_id": "worker-2",
1045
+ "target_agent_type": "agent_b",
1046
+ "status": "QUEUED",
1047
+ "parent_message_id": "msg-a",
1048
+ }
1049
+
1050
+ registry = AsyncMock()
1051
+ registry.get_all_session_executions.return_value = [exec_a, exec_b]
1052
+ redis = AsyncMock()
1053
+ client = GatewayClient(registry=registry, redis_client=redis)
1054
+
1055
+ result = await client.cancel_session(session_id="sess-1", reason="user abort")
1056
+
1057
+ assert result.success is True
1058
+ assert result.status == "CANCEL_REQUESTED"
1059
+ assert result.cancelled_count == 2
1060
+ assert result.already_finished_count == 0
1061
+
1062
+ assert registry.mark_execution_cancelling.call_count == 2
1063
+ cancel_ids = [
1064
+ call.args[0] for call in registry.mark_execution_cancelling.call_args_list
1065
+ ]
1066
+ assert "exec-a" in cancel_ids
1067
+ assert "exec-b" in cancel_ids
1068
+
1069
+ assert redis.xadd.call_count == 2
1070
+ target_streams = [call.args[0] for call in redis.xadd.call_args_list]
1071
+ assert any("worker-1" in s for s in target_streams)
1072
+ assert any("worker-2" in s for s in target_streams)
1073
+
1074
+
1075
+ @pytest.mark.asyncio
1076
+ async def test_client_cancel_session_flags_terminal_executions_without_cancelling():
1077
+ """Terminal executions in the session should only be flagged with
1078
+ cancel_requested (not re-marked cancelling, no CancelTaskCommand sent),
1079
+ so a still-running descendant can't wake them back up via callback."""
1080
+ exec_a = {
1081
+ "execution_id": "exec-a",
1082
+ "message_id": "msg-a",
1083
+ "session_id": "sess-1",
1084
+ "worker_id": "worker-1",
1085
+ "target_agent_type": "agent_a",
1086
+ "status": "COMPLETED",
1087
+ "parent_message_id": "",
1088
+ }
1089
+ exec_b = {
1090
+ "execution_id": "exec-b",
1091
+ "message_id": "msg-b",
1092
+ "session_id": "sess-1",
1093
+ "worker_id": "worker-2",
1094
+ "target_agent_type": "agent_b",
1095
+ "status": "RUNNING",
1096
+ "parent_message_id": "msg-a",
1097
+ }
1098
+
1099
+ registry = AsyncMock()
1100
+ registry.get_all_session_executions.return_value = [exec_a, exec_b]
1101
+ redis = AsyncMock()
1102
+ client = GatewayClient(registry=registry, redis_client=redis)
1103
+
1104
+ result = await client.cancel_session(session_id="sess-1", reason="user abort")
1105
+
1106
+ assert result.success is True
1107
+ assert result.status == "CANCEL_REQUESTED"
1108
+ assert result.cancelled_count == 1
1109
+ assert result.already_finished_count == 1
1110
+
1111
+ # Only the non-terminal execution gets mark_execution_cancelling
1112
+ registry.mark_execution_cancelling.assert_called_once_with(
1113
+ "exec-b", "sess-1", "user abort"
1114
+ )
1115
+ # The terminal execution only gets flagged, not re-marked cancelling
1116
+ registry.mark_cancel_requested.assert_called_once_with(
1117
+ "exec-a", "sess-1", "user abort"
1118
+ )
1119
+
1120
+ # Only one CancelTaskCommand sent, to the non-terminal execution's worker
1121
+ assert redis.xadd.call_count == 1
1122
+ target_streams = [call.args[0] for call in redis.xadd.call_args_list]
1123
+ assert any("worker-2" in s for s in target_streams)
1124
+ assert not any("worker-1" in s for s in target_streams)
1125
+
1126
+
1127
+ @pytest.mark.asyncio
1128
+ async def test_client_cancel_session_all_terminal_returns_already_finished():
1129
+ """If every execution in the session is already terminal, cancel_session
1130
+ should report failure with ALREADY_FINISHED and not dispatch anything."""
1131
+ exec_a = {
1132
+ "execution_id": "exec-a",
1133
+ "message_id": "msg-a",
1134
+ "session_id": "sess-1",
1135
+ "worker_id": "worker-1",
1136
+ "target_agent_type": "agent_a",
1137
+ "status": "COMPLETED",
1138
+ "parent_message_id": "",
1139
+ }
1140
+ exec_b = {
1141
+ "execution_id": "exec-b",
1142
+ "message_id": "msg-b",
1143
+ "session_id": "sess-1",
1144
+ "worker_id": "worker-2",
1145
+ "target_agent_type": "agent_b",
1146
+ "status": "FAILED",
1147
+ "parent_message_id": "msg-a",
1148
+ }
1149
+
1150
+ registry = AsyncMock()
1151
+ registry.get_all_session_executions.return_value = [exec_a, exec_b]
1152
+ redis = AsyncMock()
1153
+ client = GatewayClient(registry=registry, redis_client=redis)
1154
+
1155
+ result = await client.cancel_session(session_id="sess-1", reason="user abort")
1156
+
1157
+ assert result.success is False
1158
+ assert result.status == "ALREADY_FINISHED"
1159
+ assert result.cancelled_count == 0
1160
+ assert result.already_finished_count == 2
1161
+
1162
+ registry.mark_execution_cancelling.assert_not_called()
1163
+ assert registry.mark_cancel_requested.call_count == 2
1164
+ redis.xadd.assert_not_called()
1165
+
1166
+
1167
+ @pytest.mark.asyncio
1168
+ async def test_client_cancel_session_empty_session_returns_not_found():
1169
+ """A session with no executions at all should return NOT_FOUND, not be
1170
+ confused with a session whose executions are all already finished."""
1171
+ registry = AsyncMock()
1172
+ registry.get_all_session_executions.return_value = []
1173
+ redis = AsyncMock()
1174
+ client = GatewayClient(registry=registry, redis_client=redis)
1175
+
1176
+ result = await client.cancel_session(session_id="sess-unknown", reason="")
1177
+
1178
+ assert result.success is False
1179
+ assert result.status == "NOT_FOUND"
1180
+ assert result.cancelled_count == 0
1181
+ assert result.already_finished_count == 0
1182
+
1183
+ registry.mark_execution_cancelling.assert_not_called()
1184
+ registry.mark_cancel_requested.assert_not_called()
1185
+ redis.xadd.assert_not_called()
1186
+
1187
+
1188
+ @pytest.mark.asyncio
1189
+ async def test_client_cancel_session_marks_registry_when_worker_not_assigned():
1190
+ """A non-terminal execution that hasn't been claimed by a worker yet
1191
+ (no worker_id) should still be marked cancelling, but no
1192
+ CancelTaskCommand can be routed anywhere for it yet."""
1193
+ exec_a = {
1194
+ "execution_id": "exec-a",
1195
+ "message_id": "msg-a",
1196
+ "session_id": "sess-1",
1197
+ "worker_id": "",
1198
+ "target_agent_type": "agent_a",
1199
+ "status": "QUEUED",
1200
+ "parent_message_id": "",
1201
+ }
1202
+
1203
+ registry = AsyncMock()
1204
+ registry.get_all_session_executions.return_value = [exec_a]
1205
+ redis = AsyncMock()
1206
+ client = GatewayClient(registry=registry, redis_client=redis)
1207
+
1208
+ result = await client.cancel_session(session_id="sess-1", reason="user abort")
1209
+
1210
+ assert result.success is True
1211
+ assert result.status == "CANCEL_REQUESTED"
1212
+ assert result.cancelled_count == 1
1213
+
1214
+ registry.mark_execution_cancelling.assert_called_once_with(
1215
+ "exec-a", "sess-1", "user abort"
1216
+ )
1217
+ redis.xadd.assert_not_called()
1218
+
1219
+
1220
+ @pytest.mark.asyncio
1221
+ async def test_client_cancel_session_without_registry_raises():
1222
+ """cancel_session should fail the same way cancel_task does when the
1223
+ client was constructed without a WorkerRegistry."""
1224
+ redis = AsyncMock()
1225
+ client = GatewayClient(registry=None, redis_client=redis)
1226
+
1227
+ with pytest.raises(ValueError):
1228
+ await client.cancel_session(session_id="sess-1")
1229
+
1230
+
1231
+ @pytest.mark.asyncio
1232
+ async def test_byai_client_cancel_session_inherited_unmodified():
1233
+ """ByaiGatewayClient only overrides send_message; cancel_session should
1234
+ be inherited from GatewayClient and behave identically."""
1235
+ exec_a = {
1236
+ "execution_id": "exec-a",
1237
+ "message_id": "msg-a",
1238
+ "session_id": "sess-1",
1239
+ "worker_id": "worker-1",
1240
+ "target_agent_type": "agent_a",
1241
+ "status": "RUNNING",
1242
+ "parent_message_id": "",
1243
+ }
1244
+
1245
+ registry = AsyncMock()
1246
+ registry.get_all_session_executions.return_value = [exec_a]
1247
+ redis = AsyncMock()
1248
+ client = ByaiGatewayClient(registry=registry, redis_client=redis)
1249
+
1250
+ result = await client.cancel_session(session_id="sess-1", reason="user abort")
1251
+
1252
+ assert result.success is True
1253
+ assert result.status == "CANCEL_REQUESTED"
1254
+ assert result.cancelled_count == 1
1255
+ registry.mark_execution_cancelling.assert_called_once_with(
1256
+ "exec-a", "sess-1", "user abort"
1257
+ )
1258
+ assert redis.xadd.call_count == 1
1259
+
1260
+
1261
+ @pytest.mark.asyncio
1262
+ async def test_client_send_message_resume_reuses_existing_execution_id():
1263
+ """Resuming a suspended execution must reuse its execution_id and must
1264
+ not re-initialize the registry record for it. initialize_execution()
1265
+ unconditionally overwrites the message_id -> execution_id mapping, which
1266
+ would silently detach the ResumeCommand from the original (still
1267
+ WAITING_USER) execution and orphan it."""
1268
+ mock_redis = AsyncMock()
1269
+ mock_registry = AsyncMock()
1270
+ mock_registry.get_execution_by_message_id.return_value = {
1271
+ "execution_id": "exec-original",
1272
+ "message_id": "msg-1",
1273
+ "session_id": "s1",
1274
+ "status": "WAITING_USER",
1275
+ }
1276
+
1277
+ client = GatewayClient(redis_client=mock_redis, registry=mock_registry)
1278
+ await client.send_message(
1279
+ target_agent_type="langgraph_agent",
1280
+ session_id="s1",
1281
+ content="user reply",
1282
+ action_type="RESUME",
1283
+ message_id="msg-1",
1284
+ target_worker_id="worker-42",
1285
+ )
1286
+
1287
+ mock_registry.get_execution_by_message_id.assert_awaited_once_with(
1288
+ "msg-1", session_id="s1"
1289
+ )
1290
+ mock_registry.initialize_execution.assert_not_called()
1291
+
1292
+
1293
+ @pytest.mark.asyncio
1294
+ async def test_client_send_message_resume_without_match_falls_back_to_new_execution():
1295
+ """A RESUME whose message_id doesn't resolve to an existing execution
1296
+ (e.g. a genuinely new resume-shaped message) falls back to today's
1297
+ behavior: mint a fresh execution_id and initialize it in the registry."""
1298
+ mock_redis = AsyncMock()
1299
+ mock_registry = AsyncMock()
1300
+ mock_registry.get_execution_by_message_id.return_value = None
1301
+
1302
+ client = GatewayClient(redis_client=mock_redis, registry=mock_registry)
1303
+ await client.send_message(
1304
+ target_agent_type="langgraph_agent",
1305
+ session_id="s1",
1306
+ content="user reply",
1307
+ action_type="RESUME",
1308
+ message_id="msg-unmatched",
1309
+ target_worker_id="worker-42",
1310
+ )
1311
+
1312
+ mock_registry.get_execution_by_message_id.assert_awaited_once_with(
1313
+ "msg-unmatched", session_id="s1"
1314
+ )
1315
+ mock_registry.initialize_execution.assert_awaited_once()
1316
+ initialized = mock_registry.initialize_execution.await_args.args[0]
1317
+ assert initialized["message_id"] == "msg-unmatched"
1318
+ assert initialized["execution_id"]
1319
+
1320
+
1321
+ @pytest.mark.asyncio
1322
+ async def test_client_send_message_ask_agent_does_not_query_registry_for_resume_match():
1323
+ """A fresh ASK_AGENT dispatch always gets a new message_id, so there is
1324
+ nothing to reattach to -- it should skip the resume lookup entirely and
1325
+ keep minting+initializing a new execution as before."""
1326
+ mock_redis = AsyncMock()
1327
+ mock_registry = AsyncMock()
1328
+
1329
+ client = GatewayClient(redis_client=mock_redis, registry=mock_registry)
1330
+ await client.send_message(
1331
+ target_agent_type="langgraph_agent",
1332
+ session_id="s1",
1333
+ content="hello",
1334
+ action_type="ASK_AGENT",
1335
+ message_id="msg-new",
1336
+ target_worker_id="worker-42",
1337
+ )
1338
+
1339
+ mock_registry.get_execution_by_message_id.assert_not_called()
1340
+ mock_registry.initialize_execution.assert_awaited_once()
1341
+
1342
+
1343
+ @pytest.mark.asyncio
1344
+ async def test_client_send_message_resume_falls_back_when_registry_lacks_lookup():
1345
+ """A registry double/implementation without get_execution_by_message_id
1346
+ must not blow up a RESUME send -- mirror the defensive hasattr() guard
1347
+ already used for initialize_execution() and fall back to minting a new
1348
+ execution_id."""
1349
+ mock_redis = AsyncMock()
1350
+
1351
+ class RegistryWithoutLookup:
1352
+ async def initialize_execution(self, execution):
1353
+ return None
1354
+
1355
+ async def is_worker_online(self, worker_id):
1356
+ return True
1357
+
1358
+ client = GatewayClient(redis_client=mock_redis, registry=RegistryWithoutLookup())
1359
+
1360
+ response = await client.send_message(
1361
+ target_agent_type="langgraph_agent",
1362
+ session_id="s1",
1363
+ content="user reply",
1364
+ action_type="RESUME",
1365
+ message_id="msg-1",
1366
+ target_worker_id="worker-42",
1367
+ )
1368
+
1369
+ assert response.success is True