bazaar-compute-node 0.2.0rc2__tar.gz → 0.2.0rc3__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 (198) hide show
  1. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/PKG-INFO +1 -1
  2. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/pyproject.toml +1 -1
  3. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/pyproject.toml.orig +1 -1
  4. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/api.py +33 -6
  5. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/approval.py +14 -4
  6. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/channel.py +26 -1
  7. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/outbound.py +7 -3
  8. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/repository/messages.py +56 -2
  9. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/storage.py +1 -0
  10. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/telegram/api.py +5 -2
  11. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/telegram/approval.py +1 -1
  12. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/telegram/channel.py +32 -0
  13. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/telegram/identity.py +19 -3
  14. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/wecom/channel.py +14 -0
  15. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/audit.py +0 -1
  16. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/channel.py +44 -0
  17. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/command.py +81 -3
  18. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/delivery.py +5 -1
  19. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/orchestrator.py +1 -0
  20. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/storage.py +21 -8
  21. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/LICENSE +0 -0
  22. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/README.md +0 -0
  23. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/__init__.py +0 -0
  24. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/__init__.py +0 -0
  25. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/agent.py +0 -0
  26. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/agent_management.py +0 -0
  27. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/application.py +0 -0
  28. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/attachments.py +0 -0
  29. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/command.py +0 -0
  30. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/config.py +0 -0
  31. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/registry.py +0 -0
  32. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/resource_dispatch.py +0 -0
  33. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/system_service.py +0 -0
  34. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/transport.py +0 -0
  35. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/upgrade.py +0 -0
  36. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/usage.py +0 -0
  37. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/version_check.py +0 -0
  38. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
  39. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/app/wrapper.py +0 -0
  40. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/bcc.py +0 -0
  41. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cli.py +0 -0
  42. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/__init__.py +0 -0
  43. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcc/__init__.py +0 -0
  44. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcc/_client.py +0 -0
  45. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcc/_format.py +0 -0
  46. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcc/inbox.py +0 -0
  47. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcc/message.py +0 -0
  48. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcc/node.py +0 -0
  49. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcc/reminder.py +0 -0
  50. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcc/thread.py +0 -0
  51. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcn/__init__.py +0 -0
  52. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcn/_options.py +0 -0
  53. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcn/_runner.py +0 -0
  54. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcn/agent.py +0 -0
  55. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcn/node.py +0 -0
  56. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/cmd/bcn/service.py +0 -0
  57. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/__init__.py +0 -0
  58. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/claude/__init__.py +0 -0
  59. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/claude/approval.py +0 -0
  60. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/claude/client.py +0 -0
  61. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/claude/events.py +0 -0
  62. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/claude/plugin.py +0 -0
  63. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/claude/process.py +0 -0
  64. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/claude/protocol.py +0 -0
  65. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/claude/runtime.py +0 -0
  66. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
  67. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/codex/approval.py +0 -0
  68. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/codex/client.py +0 -0
  69. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/codex/events.py +0 -0
  70. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
  71. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/codex/process.py +0 -0
  72. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/codex/protocol.py +0 -0
  73. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/codex/runtime.py +0 -0
  74. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/__init__.py +0 -0
  75. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/attachments.py +0 -0
  76. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/frame.py +0 -0
  77. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/identity.py +0 -0
  78. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/plugin.py +0 -0
  79. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/lark/transport.py +0 -0
  80. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
  81. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
  82. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
  83. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
  84. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/codec.py +0 -0
  85. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
  86. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/executor.py +0 -0
  87. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/__init__.py +0 -0
  88. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/model.py +0 -0
  89. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/registry.py +0 -0
  90. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v01_initial_node_schema.py +0 -0
  91. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v02_session_mapping_indexes.py +0 -0
  92. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v03_message_log_indexes.py +0 -0
  93. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v04_runtime_attempt_facts.py +0 -0
  94. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v05_inbound_message_references.py +0 -0
  95. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v06_inbound_message_reference_integrity.py +0 -0
  96. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v07_inbound_provider_identity.py +0 -0
  97. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v08_transient_stream_events.py +0 -0
  98. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v09_remove_runtime_events.py +0 -0
  99. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v10_add_outbound_attachments.py +0 -0
  100. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v11_remove_runtime_session_mapping.py +0 -0
  101. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v12_add_reminders.py +0 -0
  102. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v13_add_agent_ownership.py +0 -0
  103. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v14_add_inbox_discovery_indexes.py +0 -0
  104. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v15_add_handoffs.py +0 -0
  105. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v16_remove_outbound_drafts.py +0 -0
  106. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v17_remove_agent_identity_triggers.py +0 -0
  107. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v18_unify_messages.py +0 -0
  108. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v19_reminder_system_messages.py +0 -0
  109. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v20_remove_freshness_persistence.py +0 -0
  110. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v21_remove_handoffs.py +0 -0
  111. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v22_readable_targets.py +0 -0
  112. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v23_persist_sender_identity.py +0 -0
  113. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v24_persist_sender_display_name.py +0 -0
  114. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v25_rename_sessions_to_threads.py +0 -0
  115. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/migrations/v26_remove_handoff_messages.py +0 -0
  116. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
  117. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -0
  118. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/repository/__init__.py +0 -0
  119. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/repository/base.py +0 -0
  120. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/repository/facade.py +0 -0
  121. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/repository/reminders.py +0 -0
  122. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/sqlite/repository/sessions.py +0 -0
  123. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/telegram/__init__.py +0 -0
  124. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/telegram/attachments.py +0 -0
  125. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/telegram/markdown.py +0 -0
  126. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/telegram/outbound.py +0 -0
  127. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/telegram/plugin.py +0 -0
  128. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
  129. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
  130. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
  131. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/__init__.py +0 -0
  132. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/actor.py +0 -0
  133. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/agent.py +0 -0
  134. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/approval.py +0 -0
  135. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/client.py +0 -0
  136. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/command.py +0 -0
  137. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/concurrency.py +0 -0
  138. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/correlation.py +0 -0
  139. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/inbox.py +0 -0
  140. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/instruction.py +0 -0
  141. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/lifecycle.py +0 -0
  142. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/models/__init__.py +0 -0
  143. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/models/entities.py +0 -0
  144. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/models/events.py +0 -0
  145. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/models/reminder.py +0 -0
  146. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/models/reminder_owner.py +0 -0
  147. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/models/states.py +0 -0
  148. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/observability.py +0 -0
  149. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
  150. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/error_feedback.py +0 -0
  151. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/reminder.py +0 -0
  152. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/reminder_command.py +0 -0
  153. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/services.py +0 -0
  154. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/turn.py +0 -0
  155. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/orchestration/upgrade_notice.py +0 -0
  156. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/outcomes.py +0 -0
  157. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/paths.py +0 -0
  158. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/reminder.py +0 -0
  159. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/reminder_schedule.py +0 -0
  160. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/restart.py +0 -0
  161. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/runtime.py +0 -0
  162. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/timerwheel.py +0 -0
  163. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/utils/__init__.py +0 -0
  164. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/utils/clock.py +0 -0
  165. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/utils/line_reader.py +0 -0
  166. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/utils/markdown.py +0 -0
  167. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/utils/sanitization.py +0 -0
  168. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/core/utils/text.py +0 -0
  169. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/i18n/__init__.py +0 -0
  170. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/i18n/catalog.py +0 -0
  171. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/rendering.py +0 -0
  172. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/approval_no_person.tpl +0 -0
  173. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/check.tpl +0 -0
  174. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/error.tpl +0 -0
  175. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/inbox_check.tpl +0 -0
  176. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/read.tpl +0 -0
  177. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/reminder_list.tpl +0 -0
  178. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/reminder_mutation.tpl +0 -0
  179. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/reminder_schedule.tpl +0 -0
  180. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/unfollow.tpl +0 -0
  181. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/upgrade.tpl +0 -0
  182. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/bcc/version.tpl +0 -0
  183. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/command/attachment_suffix.tpl +0 -0
  184. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/command/check_message.tpl +0 -0
  185. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/command/freshness_hold.tpl +0 -0
  186. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/command/read_message.tpl +0 -0
  187. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/command/send_result.tpl +0 -0
  188. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/command/sender.tpl +0 -0
  189. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/developer_instructions.md +0 -0
  190. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/inbox_notice.tpl +0 -0
  191. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/locales/en.toml +0 -0
  192. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/locales/zh-CN.toml +0 -0
  193. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/system_service/launchd.plist +0 -0
  194. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/system_service/launchd.sh +0 -0
  195. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/system_service/systemd.service +0 -0
  196. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/system_service/windows.ps1 +0 -0
  197. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/src/bazaar_compute_node/resources/system_service/windows.vbs +0 -0
  198. {bazaar_compute_node-0.2.0rc2 → bazaar_compute_node-0.2.0rc3}/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.0rc2
3
+ Version: 0.2.0rc3
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.0rc2"
3
+ version = "0.2.0rc3"
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.0rc2"
3
+ version = "0.2.0rc3"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -15,6 +15,15 @@ import aiohttp
15
15
  from ...core.timerwheel import TimerWheel
16
16
  from ...core.utils.clock import remaining
17
17
 
18
+
19
+ @dataclass(frozen=True, slots=True)
20
+ class LarkSentMessage:
21
+ """What the provider says about a message it just accepted."""
22
+
23
+ message_id: str
24
+ chat_id: str | None
25
+
26
+
18
27
  _CONNECT_TIMEOUT_SECONDS = 10.0
19
28
  _HTTP_METHODS = frozenset({"GET", "POST", "PUT", "DELETE"})
20
29
  _MAX_ERROR_MESSAGE = 256
@@ -412,12 +421,13 @@ class LarkApi:
412
421
  content: str,
413
422
  uuid: str,
414
423
  timeout: float,
415
- ) -> str:
424
+ receive_id_type: str = "chat_id",
425
+ ) -> LarkSentMessage:
416
426
  body = await self._post_json(
417
427
  "message_create",
418
428
  "/open-apis/im/v1/messages",
419
429
  timeout=timeout,
420
- params={"receive_id_type": "chat_id"},
430
+ params={"receive_id_type": receive_id_type},
421
431
  json_body={
422
432
  "receive_id": chat_id,
423
433
  "msg_type": message_type,
@@ -425,7 +435,10 @@ class LarkApi:
425
435
  "uuid": uuid,
426
436
  },
427
437
  )
428
- return _response_id(body, "message_create", "message_id")
438
+ return LarkSentMessage(
439
+ message_id=_response_id(body, "message_create", "message_id"),
440
+ chat_id=_optional_response_text(body, "chat_id"),
441
+ )
429
442
 
430
443
  async def reply_message(
431
444
  self,
@@ -436,7 +449,7 @@ class LarkApi:
436
449
  reply_in_thread: bool,
437
450
  uuid: str,
438
451
  timeout: float,
439
- ) -> str:
452
+ ) -> LarkSentMessage:
440
453
  body = await self._post_json(
441
454
  "message_reply",
442
455
  f"/open-apis/im/v1/messages/{quote(message_id, safe='')}/reply",
@@ -448,7 +461,10 @@ class LarkApi:
448
461
  "uuid": uuid,
449
462
  },
450
463
  )
451
- return _response_id(body, "message_reply", "message_id")
464
+ return LarkSentMessage(
465
+ message_id=_response_id(body, "message_reply", "message_id"),
466
+ chat_id=_optional_response_text(body, "chat_id"),
467
+ )
452
468
 
453
469
  async def reply_card(
454
470
  self,
@@ -458,7 +474,7 @@ class LarkApi:
458
474
  reply_in_thread: bool,
459
475
  uuid: str,
460
476
  timeout: float,
461
- ) -> str:
477
+ ) -> LarkSentMessage:
462
478
  return await self.reply_message(
463
479
  message_id=message_id,
464
480
  message_type="interactive",
@@ -913,6 +929,17 @@ def _provider_message(value: object) -> str:
913
929
  return value[:_MAX_ERROR_MESSAGE]
914
930
 
915
931
 
932
+ def _optional_response_text(
933
+ body: Mapping[str, object],
934
+ field_name: str,
935
+ ) -> str | None:
936
+ data = body.get("data")
937
+ value = data.get(field_name) if isinstance(data, Mapping) else None
938
+ if not isinstance(value, str) or not value or "\r" in value or "\n" in value:
939
+ return None
940
+ return value
941
+
942
+
916
943
  def _response_id(
917
944
  body: Mapping[str, object],
918
945
  method: str,
@@ -36,6 +36,7 @@ class _PendingApproval:
36
36
  token: str
37
37
  thread: LarkThreadIdentity
38
38
  expected_sender_id: str
39
+ callback_chat_id: str
39
40
  prompt_message_id: str | None
40
41
  future: asyncio.Future[ApprovalResult]
41
42
 
@@ -135,6 +136,7 @@ class LarkApprovalChannel(LarkChannel):
135
136
  token=secrets.token_urlsafe(18),
136
137
  thread=thread,
137
138
  expected_sender_id=request.provider_sender_id,
139
+ callback_chat_id=thread.chat_id,
138
140
  prompt_message_id=None,
139
141
  future=loop.create_future(),
140
142
  )
@@ -154,7 +156,7 @@ class LarkApprovalChannel(LarkChannel):
154
156
  )
155
157
  budget = remaining(deadline)
156
158
  if reply_to_message_id is not None:
157
- prompt_message_id = await api.reply_message(
159
+ sent = await api.reply_message(
158
160
  message_id=reply_to_message_id,
159
161
  message_type="interactive",
160
162
  content=content,
@@ -163,14 +165,22 @@ class LarkApprovalChannel(LarkChannel):
163
165
  timeout=budget,
164
166
  )
165
167
  else:
166
- prompt_message_id = await api.send_message(
168
+ sent = await api.send_message(
167
169
  chat_id=thread.chat_id,
170
+ # A one-to-one chat is named by the peer, so the id that
171
+ # addresses it is an `ou_`; card callbacks report the `oc_`
172
+ # the provider gave that chat, which only the send tells us.
173
+ receive_id_type=(
174
+ "open_id" if thread.chat_id.startswith("ou_") else "chat_id"
175
+ ),
168
176
  message_type="interactive",
169
177
  content=content,
170
178
  uuid=uuid4().hex,
171
179
  timeout=budget,
172
180
  )
173
- prompt_message_id = _provider_text(prompt_message_id)
181
+ if sent.chat_id is not None:
182
+ pending.callback_chat_id = sent.chat_id
183
+ prompt_message_id = _provider_text(sent.message_id)
174
184
  if prompt_message_id is None:
175
185
  raise ValueError("Lark approval prompt has no message_id")
176
186
  if (
@@ -237,7 +247,7 @@ class LarkApprovalChannel(LarkChannel):
237
247
  resolved_callback_text(self._translator, state),
238
248
  toast_type="warning",
239
249
  )
240
- if chat_id != pending.thread.chat_id or (
250
+ if chat_id != pending.callback_chat_id or (
241
251
  pending.prompt_message_id is not None
242
252
  and message_id != pending.prompt_message_id
243
253
  ):
@@ -17,6 +17,7 @@ from ...core.channel import (
17
17
  ChannelDeliveryReceipt,
18
18
  ChannelIdentity,
19
19
  ChannelSendRequest,
20
+ DmAddress,
20
21
  IChannel,
21
22
  )
22
23
  from ...core.models import (
@@ -414,7 +415,15 @@ class LarkChannel(IChannel):
414
415
  )
415
416
  thread_identity = LarkThreadIdentity(
416
417
  bot_open_id=fields.identity.open_id,
417
- chat_id=fields.chat_id,
418
+ # A one-to-one chat is named by the peer, not by the `oc_` id the
419
+ # provider gives that chat: the peer is the only part of it we can
420
+ # know before the chat exists, so a conversation opened from
421
+ # `dm:@name` and one the peer starts are the same conversation.
422
+ chat_id=(
423
+ fields.sender_open_id
424
+ if target_kind is ChannelTargetKind.DM
425
+ else fields.chat_id
426
+ ),
418
427
  thread_id=thread_id,
419
428
  )
420
429
  presentation = None
@@ -954,6 +963,20 @@ class LarkChannel(IChannel):
954
963
  except LarkApiError, LarkTransportError, TimeoutError:
955
964
  self._typing_failures += 1
956
965
 
966
+ def dm_address(
967
+ self, sender: SenderIdentity, *, sender_kind: SenderKind
968
+ ) -> DmAddress | None:
969
+ del sender_kind
970
+ identity = self._identity
971
+ if identity is None or sender.id is None:
972
+ return None
973
+ thread = LarkThreadIdentity(bot_open_id=identity.open_id, chat_id=sender.id)
974
+ return DmAddress(
975
+ channel_session_id=thread.channel_session_id,
976
+ thread_id=thread.session_id,
977
+ provider_thread_id=thread.provider_thread_id,
978
+ )
979
+
957
980
  async def send(
958
981
  self,
959
982
  request: ChannelSendRequest,
@@ -1089,6 +1112,7 @@ class _EventFields:
1089
1112
  provider_message_id: str
1090
1113
  chat_id: str
1091
1114
  chat_type: str
1115
+ sender_open_id: str
1092
1116
  sender_type: str
1093
1117
 
1094
1118
 
@@ -1147,6 +1171,7 @@ def _read_event(
1147
1171
  provider_message_id=provider_message_id,
1148
1172
  chat_id=chat_id,
1149
1173
  chat_type=chat_type,
1174
+ sender_open_id=sender_open_id,
1150
1175
  sender_type=sender_type,
1151
1176
  )
1152
1177
 
@@ -375,7 +375,7 @@ async def _send_message_part(
375
375
  return None, TimeoutError("Lark delivery deadline expired")
376
376
  try:
377
377
  if reply_anchor is not None:
378
- provider_message_id = await api.reply_message(
378
+ sent = await api.reply_message(
379
379
  message_id=reply_anchor,
380
380
  message_type=message_type,
381
381
  content=content,
@@ -384,8 +384,12 @@ async def _send_message_part(
384
384
  timeout=timeout,
385
385
  )
386
386
  else:
387
- provider_message_id = await api.send_message(
387
+ sent = await api.send_message(
388
388
  chat_id=thread.chat_id,
389
+ # Lark ids carry their kind in the prefix: `ou_` a user, `oc_` a chat.
390
+ receive_id_type=(
391
+ "open_id" if thread.chat_id.startswith("ou_") else "chat_id"
392
+ ),
389
393
  message_type=message_type,
390
394
  content=content,
391
395
  uuid=str(uuid4()),
@@ -395,7 +399,7 @@ async def _send_message_part(
395
399
  raise
396
400
  except Exception as error: # noqa: BLE001
397
401
  return None, error
398
- return provider_message_id, None
402
+ return sent.message_id, None
399
403
 
400
404
 
401
405
  def _finish_failure(
@@ -21,7 +21,9 @@ from ....core.models import (
21
21
  SenderKind,
22
22
  )
23
23
  from ....core.storage import (
24
+ AmbiguousInboxTargetError,
24
25
  InboxTargetResolutionError,
26
+ KnownSender,
25
27
  ResolvedInboxTarget,
26
28
  UnreadMessageOwner,
27
29
  )
@@ -331,9 +333,13 @@ class MessageOperations(RepositoryBase):
331
333
  f"AND ({predicate}) ORDER BY thread.id",
332
334
  parameters,
333
335
  )
334
- if len(rows) != 1:
336
+ if len(rows) > 1:
337
+ raise AmbiguousInboxTargetError(
338
+ "inbox target resolves to more than one owned session"
339
+ )
340
+ if not rows:
335
341
  raise InboxTargetResolutionError(
336
- "inbox target does not resolve to exactly one owned session"
342
+ "inbox target does not resolve to an owned session"
337
343
  )
338
344
  target = thread_from_row(rows[0])
339
345
  channel_session = cast(
@@ -363,6 +369,54 @@ class MessageOperations(RepositoryBase):
363
369
  handle_is_unique=handle_is_unique,
364
370
  )
365
371
 
372
+ async def find_known_sender(self, token: str) -> KnownSender | None:
373
+ """Find a sender this Agent has heard from, by what its `@` renders as.
374
+
375
+ `sender` carries the handle when the provider offers one and `sender_id`
376
+ the provider identity otherwise, which is the same order the `@` position
377
+ is rendered in. Matching follows that order so the more specific value
378
+ decides first. The display name is deliberately not a key: it is not
379
+ unique, and addressing by it would let one name reach several people.
380
+ """
381
+
382
+ for predicate, parameter in (
383
+ ("LOWER(sender) = LOWER(?)", token),
384
+ ("sender_id = ?", token),
385
+ ):
386
+ row = await self.fetchone(
387
+ "SELECT sender, sender_id, sender_display_name, channel, "
388
+ "metadata_json "
389
+ "FROM messages "
390
+ "WHERE agent_id = /*agent_id*/? "
391
+ "AND direction = ? "
392
+ f"AND {predicate} "
393
+ "ORDER BY seq DESC LIMIT 1",
394
+ (MessageDirection.INBOUND.value, parameter),
395
+ )
396
+ if row is None:
397
+ continue
398
+ sender = cast(str | None, row["sender"])
399
+ sender_id = cast(str | None, row["sender_id"])
400
+ if sender is None and sender_id is None:
401
+ continue
402
+ metadata_json = cast(str | None, row["metadata_json"])
403
+ sender_kind = SenderKind.UNKNOWN
404
+ if metadata_json:
405
+ stored_kind = json.loads(metadata_json).get(
406
+ "sender_kind", SenderKind.UNKNOWN.value
407
+ )
408
+ sender_kind = SenderKind(cast(str, stored_kind))
409
+ return KnownSender(
410
+ sender=SenderIdentity(
411
+ id=sender_id,
412
+ name=sender,
413
+ display_name=cast(str | None, row["sender_display_name"]),
414
+ ),
415
+ channel=cast(str, row["channel"]),
416
+ sender_kind=sender_kind,
417
+ )
418
+ return None
419
+
366
420
  async def find_message(
367
421
  self,
368
422
  channel: str,
@@ -13,6 +13,7 @@ _READ_OPERATIONS = frozenset(
13
13
  "count_messages",
14
14
  "find_thread",
15
15
  "find_channel_session",
16
+ "find_known_sender",
16
17
  "find_message",
17
18
  "get_thread",
18
19
  "get_channel_session",
@@ -87,12 +87,15 @@ class TelegramBotApi:
87
87
  async def send_chat_action(
88
88
  self,
89
89
  *,
90
- chat_id: int,
90
+ chat_id: int | str,
91
91
  action: str,
92
92
  message_thread_id: int | None = None,
93
93
  timeout: float,
94
94
  ) -> None:
95
- if not isinstance(chat_id, int) or isinstance(chat_id, bool) or chat_id == 0:
95
+ if isinstance(chat_id, str):
96
+ if not chat_id:
97
+ raise ValueError("Telegram chat_id text must be non-empty")
98
+ elif not isinstance(chat_id, int) or isinstance(chat_id, bool) or chat_id == 0:
96
99
  raise ValueError("Telegram chat_id must be a non-zero integer")
97
100
  if not isinstance(action, str) or not action:
98
101
  raise ValueError("Telegram chat action must be non-empty")
@@ -27,7 +27,7 @@ _RESOLVED_TOKEN_LIMIT = 256
27
27
  class _PendingApproval:
28
28
  request_id: str
29
29
  token: str
30
- chat_id: int
30
+ chat_id: int | str
31
31
  topic_id: int
32
32
  markdown: str
33
33
  prompt_message_id: int | None
@@ -15,6 +15,7 @@ from ...core.channel import (
15
15
  ChannelDeliveryReceipt,
16
16
  ChannelIdentity,
17
17
  ChannelSendRequest,
18
+ DmAddress,
18
19
  IChannel,
19
20
  )
20
21
  from ...core.correlation import CorrelationContext
@@ -441,6 +442,37 @@ class TelegramChannel(IChannel):
441
442
  exc_info=(type(error), error, error.__traceback__),
442
443
  )
443
444
 
445
+ def dm_address(
446
+ self, sender: SenderIdentity, *, sender_kind: SenderKind
447
+ ) -> DmAddress | None:
448
+ bot_id = self._bot_id
449
+ if bot_id is None:
450
+ return None
451
+ chat_id: int | str
452
+ # sendMessage accepts an @username only for a bot, supergroup or
453
+ # channel; a person is still addressable by numeric id alone.
454
+ if sender_kind is SenderKind.AGENT and sender.name is not None:
455
+ chat_id = f"@{sender.name}"
456
+ # A message a channel or an anonymous admin posted to a group carries
457
+ # that chat's id and no `from`, so its kind is unknown; addressing it
458
+ # would publish the DM back into the group it came from.
459
+ elif (
460
+ sender_kind in {SenderKind.HUMAN, SenderKind.AGENT}
461
+ and sender.id is not None
462
+ ):
463
+ try:
464
+ chat_id = int(sender.id)
465
+ except ValueError:
466
+ return None
467
+ else:
468
+ return None
469
+ identity = TelegramThreadIdentity(bot_id=bot_id, chat_id=chat_id, topic_id=0)
470
+ return DmAddress(
471
+ channel_session_id=identity.channel_session_id,
472
+ thread_id=identity.session_id,
473
+ provider_thread_id=identity.provider_thread_id,
474
+ )
475
+
444
476
  async def send(
445
477
  self,
446
478
  request: ChannelSendRequest,
@@ -8,14 +8,24 @@ _PROVIDER_PREFIX = "telegram"
8
8
 
9
9
  @dataclass(frozen=True, slots=True)
10
10
  class TelegramThreadIdentity:
11
+ """A Telegram conversation, addressed the way Telegram itself addresses it.
12
+
13
+ `chat_id` is the numeric id of a chat this bot has seen, or an `@username`
14
+ for a peer it has not: Bot API accepts either as `chat_id`, and a username
15
+ is the only address available before the first message arrives.
16
+ """
17
+
11
18
  bot_id: int
12
- chat_id: int
19
+ chat_id: int | str
13
20
  topic_id: int
14
21
 
15
22
  def __post_init__(self) -> None:
16
23
  if self.bot_id <= 0:
17
24
  raise ValueError("Telegram bot_id must be positive")
18
- if self.chat_id == 0:
25
+ if isinstance(self.chat_id, str):
26
+ if not self.chat_id.startswith("@") or len(self.chat_id) < 2:
27
+ raise ValueError("Telegram chat_id text must be an @username")
28
+ elif self.chat_id == 0:
19
29
  raise ValueError("Telegram chat_id must be non-zero")
20
30
  if self.topic_id < 0:
21
31
  raise ValueError("Telegram topic_id must be non-negative")
@@ -56,12 +66,18 @@ def parse_provider_thread_id(value: str) -> TelegramThreadIdentity:
56
66
  parts = value.split(":")
57
67
  if len(parts) != 4 or parts[0] != _PROVIDER_PREFIX:
58
68
  raise ValueError("Telegram provider_thread_id has invalid format")
69
+ chat_id: int | str = parts[2]
59
70
  try:
60
- bot_id, chat_id, topic_id = (int(part) for part in parts[1:])
71
+ bot_id = int(parts[1])
72
+ topic_id = int(parts[3])
61
73
  except ValueError as error:
62
74
  raise ValueError(
63
75
  "Telegram provider_thread_id contains invalid integers"
64
76
  ) from error
77
+ try:
78
+ chat_id = int(chat_id)
79
+ except ValueError:
80
+ pass
65
81
  return TelegramThreadIdentity(bot_id=bot_id, chat_id=chat_id, topic_id=topic_id)
66
82
 
67
83
 
@@ -24,6 +24,7 @@ from ...core.channel import (
24
24
  ChannelDeliveryReceipt,
25
25
  ChannelIdentity,
26
26
  ChannelSendRequest,
27
+ DmAddress,
27
28
  IChannel,
28
29
  )
29
30
  from ...core.models import (
@@ -484,6 +485,19 @@ class WeComChannel(IChannel):
484
485
  )
485
486
  return None
486
487
 
488
+ def dm_address(
489
+ self, sender: SenderIdentity, *, sender_kind: SenderKind
490
+ ) -> DmAddress | None:
491
+ del sender_kind
492
+ if sender.id is None:
493
+ return None
494
+ identity = f"wecom:dm:{sender.id}"
495
+ return DmAddress(
496
+ channel_session_id=str(uuid5(NAMESPACE_URL, identity)),
497
+ thread_id=str(uuid5(NAMESPACE_URL, f"bcn:{identity}")),
498
+ provider_thread_id=sender.id,
499
+ )
500
+
487
501
  async def send(
488
502
  self, request: ChannelSendRequest, *, timeout: float
489
503
  ) -> ProviderCallResult[ChannelDeliveryReceipt]:
@@ -19,7 +19,6 @@ class ErrorKind(StrEnum):
19
19
  TIMEOUT = "timeout"
20
20
  VALIDATION = "validation"
21
21
  SESSION_NOT_FOUND = "session_not_found"
22
- TARGET_NOT_REPLYABLE = "target_not_replyable"
23
22
  EMPTY_BODY = "empty_body"
24
23
  FRESH_CHECK_REQUIRED = "fresh_check_required"
25
24
  FRESH_CHECK_FAILED = "fresh_check_failed"
@@ -17,6 +17,8 @@ from .models import (
17
17
  Message,
18
18
  OutboundAttachment,
19
19
  RuntimeOutputEvent,
20
+ SenderIdentity,
21
+ SenderKind,
20
22
  )
21
23
  from .outcomes import ProviderCallResult
22
24
  from .timerwheel import TimerWheel
@@ -120,6 +122,20 @@ class IApproval(Protocol):
120
122
  ) -> ApprovalResult: ...
121
123
 
122
124
 
125
+ @dataclass(frozen=True, slots=True)
126
+ class DmAddress:
127
+ """A DM conversation in one channel's own terms.
128
+
129
+ Both ids come from the channel's own identity string rather than from the
130
+ provider thread id, so a later inbound message from the same peer lands on
131
+ this conversation instead of creating a second one.
132
+ """
133
+
134
+ channel_session_id: str
135
+ thread_id: str
136
+ provider_thread_id: str
137
+
138
+
123
139
  class IChannel(IAsyncLifecycle, IApproval, Protocol):
124
140
  @property
125
141
  def name(self) -> str: ...
@@ -146,6 +162,17 @@ class IChannel(IAsyncLifecycle, IApproval, Protocol):
146
162
 
147
163
  return
148
164
 
165
+ def dm_address(
166
+ self, sender: SenderIdentity, *, sender_kind: SenderKind
167
+ ) -> DmAddress | None:
168
+ """Return where a DM to this sender lives, if this channel has one.
169
+
170
+ `None` means the sender cannot be turned into a DM address here, and the
171
+ caller reports the target as not found.
172
+ """
173
+
174
+ return None
175
+
149
176
  async def send(
150
177
  self,
151
178
  request: ChannelSendRequest,
@@ -216,6 +243,23 @@ class Channel(IChannel):
216
243
  self._provider_session_ids.get(session_id, session_id), anchor
217
244
  )
218
245
 
246
+ def dm_address(
247
+ self, sender: SenderIdentity, *, sender_kind: SenderKind
248
+ ) -> DmAddress | None:
249
+ address = self._channel.dm_address(sender, sender_kind=sender_kind)
250
+ if address is None:
251
+ return None
252
+ thread_id = self._local_id("bcn-session", address.thread_id)
253
+ self._provider_session_ids[thread_id] = address.thread_id
254
+ return DmAddress(
255
+ channel_session_id=self._local_id(
256
+ "channel-session",
257
+ address.channel_session_id,
258
+ ),
259
+ thread_id=thread_id,
260
+ provider_thread_id=address.provider_thread_id,
261
+ )
262
+
219
263
  async def send(
220
264
  self,
221
265
  request: ChannelSendRequest,