bazaar-compute-node 0.2.0rc5__tar.gz → 0.2.1rc2__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 (204) hide show
  1. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/PKG-INFO +1 -1
  2. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/pyproject.toml +1 -1
  3. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/pyproject.toml.orig +1 -1
  4. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/agent.py +53 -22
  5. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/agent_management.py +11 -5
  6. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/application.py +10 -6
  7. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/command.py +78 -40
  8. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/config.py +60 -25
  9. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/registry.py +6 -3
  10. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/resource_dispatch.py +0 -10
  11. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/transport.py +1 -1
  12. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcc/_client.py +6 -4
  13. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcc/message.py +0 -2
  14. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcc/node.py +1 -3
  15. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcn/node.py +4 -1
  16. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/claude/runtime.py +1 -1
  17. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/codex/runtime.py +1 -1
  18. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/channel.py +7 -2
  19. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/codec.py +2 -1
  20. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/registry.py +6 -0
  21. bazaar_compute_node-0.2.1rc2/src/bazaar_compute_node/contrib/sqlite/migrations/v28_channel_session_identity.py +13 -0
  22. bazaar_compute_node-0.2.1rc2/src/bazaar_compute_node/contrib/sqlite/migrations/v29_backfill_channel_identity.py +29 -0
  23. bazaar_compute_node-0.2.1rc2/src/bazaar_compute_node/contrib/sqlite/migrations/v30_remove_command_id.py +140 -0
  24. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/repository/facade.py +2 -0
  25. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/repository/messages.py +58 -54
  26. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/repository/sessions.py +56 -15
  27. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/storage.py +3 -0
  28. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/telegram/channel.py +7 -1
  29. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/wecom/channel.py +23 -11
  30. bazaar_compute_node-0.2.1rc2/src/bazaar_compute_node/contrib/wecom/identity.py +23 -0
  31. bazaar_compute_node-0.2.1rc2/src/bazaar_compute_node/core/channel.py +610 -0
  32. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/command.py +14 -2
  33. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/correlation.py +0 -1
  34. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/instruction.py +8 -7
  35. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/models/entities.py +2 -3
  36. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/command.py +193 -159
  37. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/error_feedback.py +2 -0
  38. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/orchestrator.py +4 -0
  39. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/turn.py +28 -4
  40. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/runtime.py +1 -1
  41. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/storage.py +20 -12
  42. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/developer_instructions.md +1 -1
  43. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/system_service/systemd.service +0 -1
  44. bazaar_compute_node-0.2.0rc5/src/bazaar_compute_node/core/channel.py +0 -307
  45. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/LICENSE +0 -0
  46. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/README.md +0 -0
  47. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/__init__.py +0 -0
  48. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/__init__.py +0 -0
  49. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/attachments.py +0 -0
  50. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/system_service.py +0 -0
  51. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/upgrade.py +0 -0
  52. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/usage.py +0 -0
  53. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/version_check.py +0 -0
  54. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
  55. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/app/wrapper.py +0 -0
  56. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/bcc.py +0 -0
  57. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cli.py +0 -0
  58. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/__init__.py +0 -0
  59. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcc/__init__.py +0 -0
  60. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcc/_format.py +0 -0
  61. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcc/inbox.py +0 -0
  62. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcc/reminder.py +0 -0
  63. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcc/thread.py +0 -0
  64. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcn/__init__.py +0 -0
  65. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcn/_options.py +0 -0
  66. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcn/_runner.py +0 -0
  67. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcn/agent.py +0 -0
  68. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/cmd/bcn/service.py +0 -0
  69. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/__init__.py +0 -0
  70. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/claude/__init__.py +0 -0
  71. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/claude/approval.py +0 -0
  72. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/claude/client.py +0 -0
  73. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/claude/events.py +0 -0
  74. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/claude/plugin.py +0 -0
  75. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/claude/process.py +0 -0
  76. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/claude/protocol.py +0 -0
  77. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
  78. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/codex/approval.py +0 -0
  79. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/codex/client.py +0 -0
  80. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/codex/events.py +0 -0
  81. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
  82. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/codex/process.py +0 -0
  83. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/codex/protocol.py +0 -0
  84. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/__init__.py +0 -0
  85. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/api.py +0 -0
  86. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/approval.py +0 -0
  87. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/attachments.py +0 -0
  88. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/frame.py +0 -0
  89. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/identity.py +0 -0
  90. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/outbound.py +0 -0
  91. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/plugin.py +0 -0
  92. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/lark/transport.py +0 -0
  93. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
  94. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
  95. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
  96. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
  97. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
  98. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/executor.py +0 -0
  99. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/__init__.py +0 -0
  100. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/model.py +0 -0
  101. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v01_initial_node_schema.py +0 -0
  102. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v02_session_mapping_indexes.py +0 -0
  103. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v03_message_log_indexes.py +0 -0
  104. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v04_runtime_attempt_facts.py +0 -0
  105. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v05_inbound_message_references.py +0 -0
  106. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v06_inbound_message_reference_integrity.py +0 -0
  107. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v07_inbound_provider_identity.py +0 -0
  108. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v08_transient_stream_events.py +0 -0
  109. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v09_remove_runtime_events.py +0 -0
  110. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v10_add_outbound_attachments.py +0 -0
  111. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v11_remove_runtime_session_mapping.py +0 -0
  112. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v12_add_reminders.py +0 -0
  113. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v13_add_agent_ownership.py +0 -0
  114. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v14_add_inbox_discovery_indexes.py +0 -0
  115. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v15_add_handoffs.py +0 -0
  116. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v16_remove_outbound_drafts.py +0 -0
  117. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v17_remove_agent_identity_triggers.py +0 -0
  118. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v18_unify_messages.py +0 -0
  119. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v19_reminder_system_messages.py +0 -0
  120. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v20_remove_freshness_persistence.py +0 -0
  121. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v21_remove_handoffs.py +0 -0
  122. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v22_readable_targets.py +0 -0
  123. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v23_persist_sender_identity.py +0 -0
  124. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v24_persist_sender_display_name.py +0 -0
  125. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v25_rename_sessions_to_threads.py +0 -0
  126. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v26_remove_handoff_messages.py +0 -0
  127. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/migrations/v27_name_dms_by_the_peer_id.py +0 -0
  128. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
  129. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -0
  130. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/repository/__init__.py +0 -0
  131. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/repository/base.py +0 -0
  132. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/sqlite/repository/reminders.py +0 -0
  133. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/telegram/__init__.py +0 -0
  134. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/telegram/api.py +0 -0
  135. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/telegram/approval.py +0 -0
  136. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/telegram/attachments.py +0 -0
  137. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/telegram/identity.py +0 -0
  138. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/telegram/markdown.py +0 -0
  139. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/telegram/outbound.py +0 -0
  140. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/telegram/plugin.py +0 -0
  141. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
  142. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
  143. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
  144. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/__init__.py +0 -0
  145. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/actor.py +0 -0
  146. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/agent.py +0 -0
  147. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/approval.py +0 -0
  148. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/audit.py +0 -0
  149. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/client.py +0 -0
  150. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/concurrency.py +0 -0
  151. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/inbox.py +0 -0
  152. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/lifecycle.py +0 -0
  153. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/models/__init__.py +0 -0
  154. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/models/events.py +0 -0
  155. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/models/reminder.py +0 -0
  156. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/models/reminder_owner.py +0 -0
  157. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/models/states.py +0 -0
  158. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/observability.py +0 -0
  159. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
  160. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/delivery.py +0 -0
  161. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/reminder.py +0 -0
  162. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/reminder_command.py +0 -0
  163. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/services.py +0 -0
  164. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/orchestration/upgrade_notice.py +0 -0
  165. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/outcomes.py +0 -0
  166. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/paths.py +0 -0
  167. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/reminder.py +0 -0
  168. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/reminder_schedule.py +0 -0
  169. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/restart.py +0 -0
  170. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/timerwheel.py +0 -0
  171. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/utils/__init__.py +0 -0
  172. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/utils/clock.py +0 -0
  173. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/utils/line_reader.py +0 -0
  174. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/utils/markdown.py +0 -0
  175. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/utils/sanitization.py +0 -0
  176. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/core/utils/text.py +0 -0
  177. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/i18n/__init__.py +0 -0
  178. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/i18n/catalog.py +0 -0
  179. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/rendering.py +0 -0
  180. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/approval_no_person.tpl +0 -0
  181. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/check.tpl +0 -0
  182. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/error.tpl +0 -0
  183. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/inbox_check.tpl +0 -0
  184. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/read.tpl +0 -0
  185. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/reminder_list.tpl +0 -0
  186. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/reminder_mutation.tpl +0 -0
  187. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/reminder_schedule.tpl +0 -0
  188. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/unfollow.tpl +0 -0
  189. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/upgrade.tpl +0 -0
  190. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/bcc/version.tpl +0 -0
  191. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/command/attachment_suffix.tpl +0 -0
  192. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/command/check_message.tpl +0 -0
  193. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/command/freshness_hold.tpl +0 -0
  194. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/command/read_message.tpl +0 -0
  195. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/command/send_result.tpl +0 -0
  196. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/command/sender.tpl +0 -0
  197. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/inbox_notice.tpl +0 -0
  198. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/locales/en.toml +0 -0
  199. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/locales/zh-CN.toml +0 -0
  200. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/system_service/launchd.plist +0 -0
  201. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/system_service/launchd.sh +0 -0
  202. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/system_service/windows.ps1 +0 -0
  203. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/system_service/windows.vbs +0 -0
  204. {bazaar_compute_node-0.2.0rc5 → bazaar_compute_node-0.2.1rc2}/src/bazaar_compute_node/resources/system_service/windows.xml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bazaar-compute-node
3
- Version: 0.2.0rc5
3
+ Version: 0.2.1rc2
4
4
  Summary: Runtime-agnostic computer node daemon for agents and channels.
5
5
  License-Expression: AGPL-3.0-only
6
6
  License-File: LICENSE
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.2.0rc5"
3
+ version = "0.2.1rc2"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.2.0rc5"
3
+ version = "0.2.1rc2"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -14,7 +14,7 @@ from pathlib import Path
14
14
 
15
15
  from ..core.actor import Actor, Actors, Agent, Thread
16
16
  from ..core.audit import AuditRecorder
17
- from ..core.channel import Channel, ChannelContext, IChannel
17
+ from ..core.channel import Channel, ChannelContext, Channels, IChannel
18
18
  from ..core.concurrency import IThreadConcurrency, ThreadLockRegistry
19
19
  from ..core.lifecycle import TimeoutBudget
20
20
  from ..core.models import (
@@ -111,18 +111,25 @@ class AgentApplication:
111
111
  timeout_budget=timeout_budget,
112
112
  clock=now_ms,
113
113
  )
114
- provider_channel = factories.channel.build(
115
- ChannelContext(
116
- agent_id=self.agent_id,
117
- attachments=self._attachment_materializer,
118
- options=dict(configuration.channel.options),
119
- workspace=self.workspace_path,
120
- translator=self.translator,
121
- timer_wheel=self.timer_wheel,
122
- audit=self._audit_recorder,
114
+ self.channel: IChannel = Channels(
115
+ tuple(
116
+ Channel(
117
+ self.agent_id,
118
+ factories.channels[channel_configuration.kind].build(
119
+ ChannelContext(
120
+ agent_id=self.agent_id,
121
+ attachments=self._attachment_materializer,
122
+ options=dict(channel_configuration.options),
123
+ workspace=self.workspace_path,
124
+ translator=self.translator,
125
+ timer_wheel=self.timer_wheel,
126
+ audit=self._audit_recorder,
127
+ )
128
+ ),
129
+ )
130
+ for channel_configuration in configuration.channels
123
131
  )
124
132
  )
125
- self.channel: IChannel = Channel(self.agent_id, provider_channel)
126
133
  runtime_contexts: list[RuntimeCommandContext] = []
127
134
  runtimes: list[IRuntime] = []
128
135
  for index, runtime_configuration in enumerate(configuration.runtimes):
@@ -142,7 +149,7 @@ class AgentApplication:
142
149
  environment_for_session=partial(self._runtime_environment, index),
143
150
  agent_id=self.agent_id,
144
151
  agent_name=self.name,
145
- bot_name=self._bot_name,
152
+ bot_names=self._bot_names,
146
153
  runtime_options=runtime_options,
147
154
  mode=configuration.mode,
148
155
  sandbox_mode=runtime_configuration.sandbox_mode,
@@ -194,7 +201,6 @@ class AgentApplication:
194
201
  self.orchestrator.command_service,
195
202
  actors=self._actors,
196
203
  reminder_service=self.reminder_service,
197
- timeout_budget=self.timeout_budget,
198
204
  session_binding_validator=self._validate_actor_binding,
199
205
  upgrade_service=upgrade_service,
200
206
  )
@@ -228,6 +234,7 @@ class AgentApplication:
228
234
  mode=self._actors.mode,
229
235
  )
230
236
  await self._attachment_materializer.reconcile()
237
+ await self._backfill_channel_identity()
231
238
  await self.orchestrator.start(
232
239
  timeout=self.timeout_budget.startup_seconds,
233
240
  )
@@ -237,14 +244,38 @@ class AgentApplication:
237
244
  self._started = True
238
245
  self.command_dispatcher.start_accepting()
239
246
 
240
- def _bot_name(self) -> str | None:
241
- identity = self.channel.get_identity()
242
- if identity is not None:
243
- if identity.name is not None:
244
- return identity.name
245
- if identity.id is not None:
246
- return identity.id
247
- return None
247
+ async def _backfill_channel_identity(self) -> None:
248
+ """Give conversations written before bots were told apart to this one.
249
+
250
+ Only a channel that knows its bot before it starts can do this here;
251
+ the kinds that carry the bot in the thread id were filled by migration.
252
+ A kind with several bots hands them to the first, in configuration
253
+ order.
254
+ """
255
+
256
+ for member in self.channel.members:
257
+ identity = member.get_identity()
258
+ if identity is None:
259
+ continue
260
+ for session in await self.storage.list_channel_sessions_without_identity(
261
+ member.name
262
+ ):
263
+ await self.storage.backfill_channel_identity(
264
+ session.id,
265
+ channel_identity=identity.id,
266
+ provider_thread_id=member.backfill_provider_thread_id(
267
+ session.provider_thread_id
268
+ ),
269
+ )
270
+
271
+ def _bot_names(self) -> tuple[str, ...]:
272
+ names: dict[str, None] = {}
273
+ for member in self.channel.members:
274
+ identity = member.get_identity()
275
+ if identity is None:
276
+ continue
277
+ names[identity.name or identity.id] = None
278
+ return tuple(names)
248
279
 
249
280
  async def stop(self) -> None:
250
281
  if self._stopping:
@@ -291,7 +322,7 @@ class AgentApplication:
291
322
  "agent_id": self.agent_id,
292
323
  "name": self.name,
293
324
  "status": "started" if self.started else "stopped",
294
- "channel": self.channel.name,
325
+ "channels": tuple(member.name for member in self.channel.members),
295
326
  "runtimes": tuple(runtime.name for runtime in self.runtimes),
296
327
  "channel_health": dict(self.channel.health),
297
328
  "orchestrator_health": self.orchestrator.health,
@@ -101,7 +101,7 @@ def _list_agents(configuration: NodeConfiguration) -> int:
101
101
  for agent in configuration.agents:
102
102
  print(
103
103
  f"id={agent.id} name={agent.name} "
104
- f"channel={agent.channel.kind} runtime={_runtime_kinds(agent)}",
104
+ f"channel={_channel_kinds(agent)} runtime={_runtime_kinds(agent)}",
105
105
  flush=True,
106
106
  )
107
107
  return 0
@@ -123,9 +123,11 @@ def _add_agent(
123
123
  agent = AgentConfiguration(
124
124
  id=str(uuid7()),
125
125
  name=args.name,
126
- channel=ChannelConfiguration(
127
- kind=args.channel,
128
- options=MappingProxyType(options.channel),
126
+ channels=(
127
+ ChannelConfiguration(
128
+ kind=args.channel,
129
+ options=MappingProxyType(options.channel),
130
+ ),
129
131
  ),
130
132
  runtimes=(_runtime_configuration(args.runtime, options, parser),),
131
133
  idle_timeout_seconds=idle_timeout,
@@ -139,7 +141,7 @@ def _add_agent(
139
141
  parser.error(str(error))
140
142
  print(
141
143
  f"Agent added id={agent.id} name={agent.name} "
142
- f"channel={agent.channel.kind} runtime={_runtime_kinds(agent)}",
144
+ f"channel={_channel_kinds(agent)} runtime={_runtime_kinds(agent)}",
143
145
  flush=True,
144
146
  )
145
147
  print("Run `bcn restart` to apply.", flush=True)
@@ -206,6 +208,10 @@ def run_agent_command(
206
208
  raise AssertionError(f"unsupported Agent command: {unsupported}")
207
209
 
208
210
 
211
+ def _channel_kinds(agent: AgentConfiguration) -> str:
212
+ return ",".join(channel.kind for channel in agent.channels)
213
+
214
+
209
215
  def _runtime_kinds(agent: AgentConfiguration) -> str:
210
216
  return ",".join(runtime.kind for runtime in agent.runtimes)
211
217
 
@@ -33,7 +33,7 @@ from .version_check import VersionWatcher
33
33
  class AgentStartupResult:
34
34
  agent_id: str
35
35
  name: str
36
- channel: str
36
+ channels: tuple[str, ...]
37
37
  runtimes: tuple[str, ...]
38
38
  status: str
39
39
  error_type: str | None = None
@@ -44,7 +44,7 @@ class AgentStartupResult:
44
44
  "agent_id": self.agent_id,
45
45
  "name": self.name,
46
46
  "status": self.status,
47
- "channel": self.channel,
47
+ "channels": self.channels,
48
48
  "runtimes": self.runtimes,
49
49
  }
50
50
  if self.error_type is not None:
@@ -54,6 +54,10 @@ class AgentStartupResult:
54
54
  return record
55
55
 
56
56
 
57
+ def _channel_kinds(configuration: AgentConfiguration) -> tuple[str, ...]:
58
+ return tuple(channel.kind for channel in configuration.channels)
59
+
60
+
57
61
  def _runtime_kinds(configuration: AgentConfiguration) -> tuple[str, ...]:
58
62
  return tuple(runtime.kind for runtime in configuration.runtimes)
59
63
 
@@ -182,7 +186,7 @@ class NodeApplication:
182
186
  async with asyncio.timeout(self.timeout_budget.startup_seconds):
183
187
  factories = await asyncio.to_thread(
184
188
  self._registry.load_agent,
185
- channel=configuration.channel.kind,
189
+ channels=_channel_kinds(configuration),
186
190
  runtimes=_runtime_kinds(configuration),
187
191
  )
188
192
  storage_scope = self.storage.scope(configuration.id, configuration.name)
@@ -211,7 +215,7 @@ class NodeApplication:
211
215
  result = AgentStartupResult(
212
216
  agent_id=configuration.id,
213
217
  name=configuration.name,
214
- channel=configuration.channel.kind,
218
+ channels=_channel_kinds(configuration),
215
219
  runtimes=_runtime_kinds(configuration),
216
220
  status="failed",
217
221
  error_type=type(error).__name__,
@@ -225,7 +229,7 @@ class NodeApplication:
225
229
  result = AgentStartupResult(
226
230
  agent_id=configuration.id,
227
231
  name=configuration.name,
228
- channel=configuration.channel.kind,
232
+ channels=_channel_kinds(configuration),
229
233
  runtimes=_runtime_kinds(configuration),
230
234
  status="started",
231
235
  )
@@ -438,7 +442,7 @@ class NodeApplication:
438
442
  "agent_id": configuration.id,
439
443
  "name": configuration.name,
440
444
  "status": "pending",
441
- "channel": configuration.channel.kind,
445
+ "channels": _channel_kinds(configuration),
442
446
  "runtimes": _runtime_kinds(configuration),
443
447
  }
444
448
  )
@@ -20,11 +20,12 @@ from pydantic import (
20
20
  from ..core.actor import Actor, Actors
21
21
  from ..core.command import (
22
22
  ICommandService,
23
+ MessageBroadcast,
23
24
  MessageSendFreshnessHold,
25
+ MessageSendSuccess,
24
26
  TargetProjection,
25
27
  ThreadNotFoundError,
26
28
  )
27
- from ..core.lifecycle import TimeoutBudget
28
29
  from ..core.models import (
29
30
  InboundAttachment,
30
31
  InboxTargetSummary,
@@ -210,7 +211,6 @@ class _MessageSendRequest(_CommandRequest):
210
211
  command: Literal["send"]
211
212
  target: NonEmptyText
212
213
  body: StrictStr
213
- command_id: NonEmptyText
214
214
  attachment_paths: list[NonEmptyText] = Field(default_factory=list)
215
215
  reply_to_message_id: NonEmptyText | None = None
216
216
  send_draft: StrictBool = False
@@ -240,6 +240,16 @@ _REQUEST_MODELS: dict[tuple[str, str], _RequestModel] = {
240
240
  ("thread", "unfollow"): _ThreadUnfollowRequest,
241
241
  }
242
242
 
243
+ # the states in which some of the message is with the peer, which is when
244
+ # a resend would put it there twice
245
+ _REACHED_STATES = frozenset(
246
+ {
247
+ OutboundDeliveryState.SENT,
248
+ OutboundDeliveryState.QUEUED,
249
+ OutboundDeliveryState.PARTIAL,
250
+ }
251
+ )
252
+
243
253
  _SEND_FAILURES: dict[OutboundDeliveryState, tuple[str, str, str]] = {
244
254
  OutboundDeliveryState.PARTIAL: (
245
255
  "SEND_PARTIAL",
@@ -271,10 +281,6 @@ _REQUEST_ERRORS: dict[str, tuple[str, str]] = {
271
281
  "around_message_id must be a string",
272
282
  ),
273
283
  "body": ("BODY_REQUIRED", "body must be text"),
274
- "command_id": (
275
- "COMMAND_ID_REQUIRED",
276
- "command_id must be a non-empty string",
277
- ),
278
284
  "attachment_paths": (
279
285
  "INVALID_ATTACHMENTS",
280
286
  "attachment_paths must be a list of non-empty strings",
@@ -328,12 +334,10 @@ class CommandDispatcher:
328
334
  service: ICommandService,
329
335
  *,
330
336
  actors: Actors,
331
- timeout_budget: TimeoutBudget,
332
337
  session_binding_validator: SessionBindingValidator | None = None,
333
338
  ) -> None:
334
339
  self._actors = actors
335
340
  self._service = service
336
- self._timeout_budget = timeout_budget
337
341
  self._session_binding_validator = session_binding_validator
338
342
  self._accepting = False
339
343
  self._in_flight: set[asyncio.Task[object]] = set()
@@ -381,10 +385,6 @@ class CommandDispatcher:
381
385
  if pending:
382
386
  await asyncio.gather(*pending, return_exceptions=True)
383
387
 
384
- def _command_timeout(self, request: Mapping[str, object]) -> float | None:
385
- del request
386
- return self._timeout_budget.command_seconds
387
-
388
388
  async def __call__(self, request: Mapping[str, object]) -> Mapping[str, object]:
389
389
  if not self._accepting:
390
390
  return {
@@ -398,9 +398,10 @@ class CommandDispatcher:
398
398
  self._drained.clear()
399
399
  try:
400
400
  kind = request.get("kind")
401
+ # a command takes as long as its work takes: giving up on one
402
+ # whose work carries on regardless only hides what became of it
401
403
  if kind == "command":
402
- async with asyncio.timeout(self._command_timeout(request)):
403
- return await self._dispatch_command(request)
404
+ return await self._dispatch_command(request)
404
405
  raise CommandDispatchError(
405
406
  "INVALID_COMMAND", "request kind is not supported"
406
407
  )
@@ -561,7 +562,6 @@ class CommandDispatcher:
561
562
  ) -> Mapping[str, object]:
562
563
  result = await self._service.send(
563
564
  actor=actor,
564
- command_id=request.command_id,
565
565
  raw_target=request.target,
566
566
  body=request.body,
567
567
  created_at_ms=request.created_at_ms,
@@ -571,33 +571,35 @@ class CommandDispatcher:
571
571
  )
572
572
  if isinstance(result, MessageSendFreshnessHold):
573
573
  return {"ok": True, "result": {"text": format_freshness_hold(result)}}
574
- message = result.message
575
- delivery_state = message.delivery_state
576
- if delivery_state is None:
577
- raise RuntimeError("outbound message has no delivery state")
578
- if delivery_state not in {
579
- OutboundDeliveryState.SENT,
580
- OutboundDeliveryState.QUEUED,
581
- }:
582
- failure = _SEND_FAILURES.get(delivery_state)
583
- if failure is None:
584
- raise AssertionError(
585
- f"message send returned unsupported state: {delivery_state.value}"
586
- )
587
- code, fallback, next_action = failure
574
+ # the handle may have named one peer on several bots; to the caller
575
+ # that is one conversation, so it hears about one delivery, and a
576
+ # failure on any of them is the answer for all
577
+ sent = result.deliveries if isinstance(result, MessageBroadcast) else (result,)
578
+ reached: list[str] = []
579
+ failure: CommandDispatchError | None = None
580
+ for delivery in sent:
581
+ refused = _send_failure(delivery)
582
+ if refused is not None and failure is None:
583
+ failure = refused
584
+ if delivery.message.delivery_state in _REACHED_STATES:
585
+ reached.append(delivery.target)
586
+ if failure is None:
587
+ return {"ok": True, "result": {"text": _format_send_success(sent[0])}}
588
+ if reached:
589
+ # a resend would reach these again, so the caller is told which
588
590
  raise CommandDispatchError(
589
- code,
590
- message.error_message or fallback,
591
- next_action=next_action,
591
+ failure.code,
592
+ f"{failure.message} Already reached {', '.join(reached)}.",
593
+ next_action=" ".join(
594
+ part
595
+ for part in (
596
+ failure.next_action,
597
+ "A resend reaches the conversations already reached again.",
598
+ )
599
+ if part is not None
600
+ ),
592
601
  )
593
- text = _SEND_RESULT.render(
594
- {
595
- "delivery_state": delivery_state.value,
596
- "target": result.target,
597
- "message_id": message.message_id,
598
- }
599
- )
600
- return {"ok": True, "result": {"text": text}}
602
+ raise failure
601
603
 
602
604
  async def _unfollow_thread(
603
605
  self, actor: Actor, request: _ThreadUnfollowRequest
@@ -621,6 +623,42 @@ _ATTACHMENT_SUFFIX = TextTemplate.from_resource("command/attachment_suffix.tpl")
621
623
  _SENDER = TextTemplate.from_resource("command/sender.tpl")
622
624
  _CHECK_MESSAGE = TextTemplate.from_resource("command/check_message.tpl")
623
625
  _READ_MESSAGE = TextTemplate.from_resource("command/read_message.tpl")
626
+
627
+
628
+ def _send_failure(result: MessageSendSuccess) -> CommandDispatchError | None:
629
+ message = result.message
630
+ delivery_state = message.delivery_state
631
+ if delivery_state is None:
632
+ raise RuntimeError("outbound message has no delivery state")
633
+ if delivery_state in {OutboundDeliveryState.SENT, OutboundDeliveryState.QUEUED}:
634
+ return None
635
+ failure = _SEND_FAILURES.get(delivery_state)
636
+ if failure is None:
637
+ raise AssertionError(
638
+ f"message send returned unsupported state: {delivery_state.value}"
639
+ )
640
+ code, fallback, next_action = failure
641
+ return CommandDispatchError(
642
+ code,
643
+ message.error_message or fallback,
644
+ next_action=next_action,
645
+ )
646
+
647
+
648
+ def _format_send_success(result: MessageSendSuccess) -> str:
649
+ message = result.message
650
+ delivery_state = message.delivery_state
651
+ if delivery_state is None:
652
+ raise RuntimeError("outbound message has no delivery state")
653
+ return _SEND_RESULT.render(
654
+ {
655
+ "delivery_state": delivery_state.value,
656
+ "target": result.target,
657
+ "message_id": message.message_id,
658
+ }
659
+ )
660
+
661
+
624
662
  _SEND_RESULT = TextTemplate.from_resource("command/send_result.tpl")
625
663
  _FRESHNESS_HOLD = TextTemplate.from_resource("command/freshness_hold.tpl")
626
664
 
@@ -17,7 +17,7 @@ from ..core.paths import resolve_data_dir
17
17
  from ..core.runtime import RuntimeSandboxMode
18
18
 
19
19
  CONFIG_FILENAME = "config.toml"
20
- CONFIG_VERSION = "3"
20
+ CONFIG_VERSION = "4"
21
21
  DEFAULT_AUDIT = "logging"
22
22
  DEFAULT_STORAGE = "sqlite"
23
23
  DEFAULT_DATABASE_FILENAME = "bcn.sqlite3"
@@ -68,7 +68,7 @@ class RuntimeConfiguration:
68
68
  class AgentConfiguration:
69
69
  id: str
70
70
  name: str
71
- channel: ChannelConfiguration
71
+ channels: tuple[ChannelConfiguration, ...]
72
72
  runtimes: tuple[RuntimeConfiguration, ...]
73
73
  mode: Mode = Mode.SESSION
74
74
  idle_timeout_seconds: float = 0
@@ -76,6 +76,8 @@ class AgentConfiguration:
76
76
  def __post_init__(self) -> None:
77
77
  _validate_agent_id(self.id, "agent.id")
78
78
  _required_text(self.name, "agent.name")
79
+ if not self.channels:
80
+ raise ConfigurationError("agent.channel must define at least one channel")
79
81
  if not self.runtimes:
80
82
  raise ConfigurationError("agent.runtime must define at least one runtime")
81
83
  if (
@@ -91,7 +93,7 @@ class AgentConfiguration:
91
93
 
92
94
  @dataclass(frozen=True, slots=True)
93
95
  class NodeConfiguration:
94
- """Version 3 persistent node configuration."""
96
+ """Version 4 persistent node configuration."""
95
97
 
96
98
  agents: tuple[AgentConfiguration, ...] = ()
97
99
  storage: str = DEFAULT_STORAGE
@@ -173,15 +175,15 @@ def resolve_config_path() -> Path:
173
175
  def load_node_configuration(
174
176
  path: Path | None = None,
175
177
  ) -> NodeConfiguration:
176
- """Load v3 configuration, advancing older input through the upgrade states."""
178
+ """Load v4 configuration, advancing older input through the upgrade states."""
177
179
 
178
180
  path = (path or resolve_config_path()).expanduser()
179
181
  payload = _read_configuration(path)
180
182
  state = _configuration_state(_payload_version(payload))
181
183
  if state.is_current:
182
- return _parse_v3_configuration(payload)
184
+ return _parse_v4_configuration(payload)
183
185
 
184
- configuration = _parse_v3_configuration(state.advance(payload))
186
+ configuration = _parse_v4_configuration(state.advance(payload))
185
187
  _write_configuration(path, configuration)
186
188
  return configuration
187
189
 
@@ -261,17 +263,17 @@ def _read_legacy_workspace_id(
261
263
  connection.close()
262
264
 
263
265
 
264
- def _parse_v3_configuration(payload: Mapping[str, object]) -> NodeConfiguration:
266
+ def _parse_v4_configuration(payload: Mapping[str, object]) -> NodeConfiguration:
265
267
  node = _table(payload.get("node", {}), "[node]")
266
268
  if "channel" in node or "runtime" in node:
267
269
  raise ConfigurationError(
268
- "version 3 configuration cannot define node.channel or node.runtime"
270
+ "version 4 configuration cannot define node.channel or node.runtime"
269
271
  )
270
272
  raw_agents = payload.get("agent", [])
271
273
  if not isinstance(raw_agents, list):
272
274
  raise ConfigurationError("[[agent]] must be an array of TOML tables")
273
275
  agents = tuple(
274
- _parse_v3_agent(item, index=index)
276
+ _parse_v4_agent(item, index=index)
275
277
  for index, item in enumerate(raw_agents, start=1)
276
278
  )
277
279
  version_check = node.get("version_check", True)
@@ -289,10 +291,26 @@ def _parse_v3_configuration(payload: Mapping[str, object]) -> NodeConfiguration:
289
291
  )
290
292
 
291
293
 
292
- def _parse_v3_agent(value: object, *, index: int) -> AgentConfiguration:
294
+ def _parse_v4_agent(value: object, *, index: int) -> AgentConfiguration:
293
295
  table = _table(value, f"agent #{index}")
294
- channel = _table(table.get("channel"), f"agent #{index}.channel")
295
- channel_kind = _required_text(channel.get("kind"), f"agent #{index}.channel.kind")
296
+ raw_channels = table.get("channel")
297
+ if not isinstance(raw_channels, list):
298
+ raise ConfigurationError(
299
+ f"agent #{index}.channel must be an array of TOML tables"
300
+ )
301
+ channels: list[ChannelConfiguration] = []
302
+ for position, item in enumerate(raw_channels, start=1):
303
+ channel = _table(item, f"agent #{index}.channel #{position}")
304
+ channels.append(
305
+ ChannelConfiguration(
306
+ kind=_required_text(
307
+ channel.get("kind"), f"agent #{index}.channel #{position}.kind"
308
+ ),
309
+ options=MappingProxyType(
310
+ {key: option for key, option in channel.items() if key != "kind"}
311
+ ),
312
+ )
313
+ )
296
314
  raw_runtimes = table.get("runtime")
297
315
  if not isinstance(raw_runtimes, list):
298
316
  raise ConfigurationError(
@@ -333,12 +351,7 @@ def _parse_v3_agent(value: object, *, index: int) -> AgentConfiguration:
333
351
  return AgentConfiguration(
334
352
  id=_required_text(table.get("id"), f"agent #{index}.id"),
335
353
  name=_required_text(table.get("name"), f"agent #{index}.name"),
336
- channel=ChannelConfiguration(
337
- kind=channel_kind,
338
- options=MappingProxyType(
339
- {key: item for key, item in channel.items() if key != "kind"}
340
- ),
341
- ),
354
+ channels=tuple(channels),
342
355
  runtimes=tuple(runtimes),
343
356
  mode=mode,
344
357
  idle_timeout_seconds=float(idle_timeout),
@@ -533,10 +546,28 @@ def _v2_to_v3_payload(payload: Mapping[str, object]) -> dict[str, object]:
533
546
  return dict(payload) | {"version": "3", "agent": agents}
534
547
 
535
548
 
549
+ def _v3_to_v4_payload(payload: Mapping[str, object]) -> dict[str, object]:
550
+ raw_agents = payload.get("agent", [])
551
+ if not isinstance(raw_agents, list):
552
+ raise ConfigurationError("[[agent]] must be an array of TOML tables")
553
+
554
+ agents: list[dict[str, object]] = []
555
+ for index, item in enumerate(raw_agents, start=1):
556
+ agent = dict(_table(item, f"agent #{index}"))
557
+ agent["channel"] = [_table(agent.get("channel"), f"agent #{index}.channel")]
558
+ agents.append(agent)
559
+
560
+ return dict(payload) | {"version": "4", "agent": agents}
561
+
562
+
536
563
  # The upgrade states, newest first so each one can name the state it moves to.
537
564
  # Adding a version means writing its upgrade, declaring it as the new terminal
538
565
  # state, and giving the state that used to be terminal an upgrade into it.
539
- _CONFIGURATION_V3 = _ConfigurationState(version="3", upgrade=None)
566
+ _CONFIGURATION_V4 = _ConfigurationState(version="4", upgrade=None)
567
+ _CONFIGURATION_V3 = _ConfigurationState(
568
+ version="3",
569
+ upgrade=_ConfigurationUpgrade(apply=_v3_to_v4_payload, state=_CONFIGURATION_V4),
570
+ )
540
571
  _CONFIGURATION_V2 = _ConfigurationState(
541
572
  version="2",
542
573
  upgrade=_ConfigurationUpgrade(apply=_v2_to_v3_payload, state=_CONFIGURATION_V3),
@@ -549,6 +580,7 @@ _CONFIGURATION_STATES = (
549
580
  _CONFIGURATION_V1,
550
581
  _CONFIGURATION_V2,
551
582
  _CONFIGURATION_V3,
583
+ _CONFIGURATION_V4,
552
584
  )
553
585
 
554
586
 
@@ -614,15 +646,18 @@ def _serialize_configuration(configuration: NodeConfiguration) -> str:
614
646
  f"name = {_toml_value(agent.name)}",
615
647
  f"mode = {_toml_value(agent.mode.value)}",
616
648
  f"idle_timeout = {_toml_value(agent.idle_timeout_seconds)}",
617
- "",
618
- "[agent.channel]",
619
- f"kind = {_toml_value(agent.channel.kind)}",
620
649
  )
621
650
  )
622
- for key in sorted(agent.channel.options):
623
- lines.append(
624
- f"{_toml_key(key)} = {_toml_value(agent.channel.options[key])}"
651
+ for channel in agent.channels:
652
+ lines.extend(
653
+ (
654
+ "",
655
+ "[[agent.channel]]",
656
+ f"kind = {_toml_value(channel.kind)}",
657
+ )
625
658
  )
659
+ for key in sorted(channel.options):
660
+ lines.append(f"{_toml_key(key)} = {_toml_value(channel.options[key])}")
626
661
  for runtime in agent.runtimes:
627
662
  lines.extend(
628
663
  (
@@ -29,7 +29,7 @@ class SharedAdapterFactories:
29
29
 
30
30
  @dataclass(frozen=True, slots=True)
31
31
  class AgentAdapterFactories:
32
- channel: IChannelBuilder
32
+ channels: Mapping[str, IChannelBuilder]
33
33
  runtimes: Mapping[str, RuntimeFactory]
34
34
 
35
35
 
@@ -61,11 +61,14 @@ class AdapterRegistry:
61
61
  def load_agent(
62
62
  self,
63
63
  *,
64
- channel: str,
64
+ channels: Sequence[str],
65
65
  runtimes: Sequence[str],
66
66
  ) -> AgentAdapterFactories:
67
67
  return AgentAdapterFactories(
68
- channel=self._load_channel_builder(channel),
68
+ channels={
69
+ kind: self._load_channel_builder(kind)
70
+ for kind in dict.fromkeys(channels)
71
+ },
69
72
  runtimes={
70
73
  kind: cast(
71
74
  RuntimeFactory,