bazaar-compute-node 0.1.38__tar.gz → 0.1.39__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 (207) hide show
  1. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/PKG-INFO +2 -1
  2. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/pyproject.toml +2 -1
  3. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/pyproject.toml.orig +2 -1
  4. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/agent.py +7 -15
  5. bazaar_compute_node-0.1.39/src/bazaar_compute_node/app/agent_management.py +213 -0
  6. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/application.py +5 -9
  7. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/attachments.py +70 -58
  8. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/command.py +145 -140
  9. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/resource_dispatch.py +188 -148
  10. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/system_service.py +4 -45
  11. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/transport.py +2 -1
  12. bazaar_compute_node-0.1.39/src/bazaar_compute_node/app/usage.py +12 -0
  13. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/windows_pipe.py +4 -6
  14. bazaar_compute_node-0.1.39/src/bazaar_compute_node/bcc.py +9 -0
  15. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cli.py +9 -0
  16. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/__init__.py +0 -0
  17. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcc/__init__.py +62 -0
  18. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcc/_client.py +84 -0
  19. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcc/_format.py +277 -0
  20. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcc/inbox.py +37 -0
  21. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcc/message.py +125 -0
  22. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcc/node.py +52 -0
  23. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcc/reminder.py +175 -0
  24. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcc/thread.py +31 -0
  25. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcn/__init__.py +70 -0
  26. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcn/_options.py +94 -0
  27. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcn/_runner.py +22 -0
  28. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcn/agent.py +125 -0
  29. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcn/node.py +189 -0
  30. bazaar_compute_node-0.1.39/src/bazaar_compute_node/cmd/bcn/service.py +63 -0
  31. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/claude/approval.py +22 -18
  32. bazaar_compute_node-0.1.39/src/bazaar_compute_node/contrib/claude/events.py +633 -0
  33. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/claude/runtime.py +80 -49
  34. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/codex/approval.py +83 -65
  35. bazaar_compute_node-0.1.39/src/bazaar_compute_node/contrib/codex/events.py +747 -0
  36. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/codex/process.py +0 -4
  37. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/codex/runtime.py +247 -217
  38. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/api.py +196 -162
  39. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/approval.py +19 -86
  40. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/attachments.py +65 -77
  41. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/channel.py +357 -272
  42. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/outbound.py +162 -255
  43. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/transport.py +254 -104
  44. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/database.py +42 -43
  45. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/registry.py +3 -6
  46. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/repository/facade.py +69 -34
  47. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/repository/messages.py +161 -125
  48. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/storage.py +0 -6
  49. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/telegram/api.py +16 -0
  50. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/telegram/approval.py +150 -91
  51. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/telegram/attachments.py +63 -46
  52. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/telegram/channel.py +203 -91
  53. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/telegram/markdown.py +100 -85
  54. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/telegram/outbound.py +253 -190
  55. bazaar_compute_node-0.1.39/src/bazaar_compute_node/contrib/wecom/channel.py +1792 -0
  56. bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/agent.py +49 -0
  57. bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/approval.py +85 -0
  58. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/audit.py +15 -34
  59. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/channel.py +34 -56
  60. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/command.py +0 -6
  61. bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/correlation.py +22 -0
  62. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/inbox.py +0 -4
  63. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/models/__init__.py +50 -24
  64. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/models/entities.py +1 -33
  65. bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/models/events.py +177 -0
  66. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/models/reminder.py +1 -8
  67. bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/models/states.py +62 -0
  68. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/orchestration/command.py +208 -97
  69. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/orchestration/error_feedback.py +12 -1
  70. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/orchestration/reminder.py +33 -6
  71. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/orchestration/reminder_command.py +2 -6
  72. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/orchestration/services.py +4 -80
  73. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/orchestration/session.py +512 -514
  74. bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/orchestration/turn.py +901 -0
  75. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/outcomes.py +0 -11
  76. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/reminder.py +0 -12
  77. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/reminder_schedule.py +0 -8
  78. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/runtime.py +87 -24
  79. bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/utils/clock.py +15 -0
  80. {bazaar_compute_node-0.1.38/src/bazaar_compute_node/contrib/wecom → bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/utils}/markdown.py +103 -85
  81. bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/utils/sanitization.py +44 -0
  82. bazaar_compute_node-0.1.39/src/bazaar_compute_node/core/utils/text.py +36 -0
  83. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/rendering.py +5 -0
  84. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/locales/en.toml +5 -1
  85. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/locales/zh-CN.toml +5 -1
  86. bazaar_compute_node-0.1.38/src/bazaar_compute_node/app/agent_management.py +0 -269
  87. bazaar_compute_node-0.1.38/src/bazaar_compute_node/bcc.py +0 -808
  88. bazaar_compute_node-0.1.38/src/bazaar_compute_node/cli.py +0 -302
  89. bazaar_compute_node-0.1.38/src/bazaar_compute_node/contrib/claude/events.py +0 -481
  90. bazaar_compute_node-0.1.38/src/bazaar_compute_node/contrib/codex/events.py +0 -398
  91. bazaar_compute_node-0.1.38/src/bazaar_compute_node/contrib/wecom/channel.py +0 -1340
  92. bazaar_compute_node-0.1.38/src/bazaar_compute_node/core/approval.py +0 -51
  93. bazaar_compute_node-0.1.38/src/bazaar_compute_node/core/correlation.py +0 -42
  94. bazaar_compute_node-0.1.38/src/bazaar_compute_node/core/models/states.py +0 -375
  95. bazaar_compute_node-0.1.38/src/bazaar_compute_node/core/orchestration/runtime_pool.py +0 -62
  96. bazaar_compute_node-0.1.38/src/bazaar_compute_node/core/orchestration/turn.py +0 -714
  97. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/LICENSE +0 -0
  98. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/README.md +0 -0
  99. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/__init__.py +0 -0
  100. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/__init__.py +0 -0
  101. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/config.py +0 -0
  102. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/registry.py +0 -0
  103. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/upgrade.py +0 -0
  104. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/version_check.py +0 -0
  105. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/app/wrapper.py +0 -0
  106. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/__init__.py +0 -0
  107. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/claude/__init__.py +0 -0
  108. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/claude/client.py +0 -0
  109. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/claude/plugin.py +0 -0
  110. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/claude/process.py +0 -0
  111. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/claude/protocol.py +0 -0
  112. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
  113. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/codex/client.py +0 -0
  114. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
  115. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/codex/protocol.py +0 -0
  116. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/__init__.py +0 -0
  117. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/frame.py +0 -0
  118. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/identity.py +0 -0
  119. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/lark/plugin.py +0 -0
  120. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
  121. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
  122. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
  123. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
  124. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/codec.py +0 -0
  125. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/executor.py +0 -0
  126. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/__init__.py +0 -0
  127. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/model.py +0 -0
  128. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v01_initial_node_schema.py +0 -0
  129. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v02_session_mapping_indexes.py +0 -0
  130. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v03_message_log_indexes.py +0 -0
  131. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v04_runtime_attempt_facts.py +0 -0
  132. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v05_inbound_message_references.py +0 -0
  133. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v06_inbound_message_reference_integrity.py +0 -0
  134. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v07_inbound_provider_identity.py +0 -0
  135. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v08_transient_stream_events.py +0 -0
  136. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v09_remove_runtime_events.py +0 -0
  137. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v10_add_outbound_attachments.py +0 -0
  138. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v11_remove_runtime_session_mapping.py +0 -0
  139. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v12_add_reminders.py +0 -0
  140. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v13_add_agent_ownership.py +0 -0
  141. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v14_add_inbox_discovery_indexes.py +0 -0
  142. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v15_add_handoffs.py +0 -0
  143. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v16_remove_outbound_drafts.py +0 -0
  144. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v17_remove_agent_identity_triggers.py +0 -0
  145. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v18_unify_messages.py +0 -0
  146. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v19_reminder_system_messages.py +0 -0
  147. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v20_remove_freshness_persistence.py +0 -0
  148. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v21_remove_handoffs.py +0 -0
  149. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v22_readable_targets.py +0 -0
  150. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v23_persist_sender_identity.py +0 -0
  151. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/migrations/v24_persist_sender_display_name.py +0 -0
  152. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
  153. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -0
  154. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/repository/__init__.py +0 -0
  155. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/repository/base.py +0 -0
  156. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/repository/reminders.py +0 -0
  157. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/sqlite/repository/sessions.py +0 -0
  158. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/telegram/__init__.py +0 -0
  159. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/telegram/identity.py +0 -0
  160. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/telegram/plugin.py +0 -0
  161. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
  162. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
  163. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
  164. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/__init__.py +0 -0
  165. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/client.py +0 -0
  166. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/concurrency.py +0 -0
  167. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/instruction.py +0 -0
  168. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/lifecycle.py +0 -0
  169. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/models/reminder_owner.py +0 -0
  170. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/observability.py +0 -0
  171. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
  172. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/orchestration/delivery.py +0 -0
  173. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/orchestration/upgrade_notice.py +0 -0
  174. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/paths.py +0 -0
  175. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/restart.py +0 -0
  176. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/storage.py +0 -0
  177. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/timerwheel.py +0 -0
  178. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/utils/__init__.py +0 -0
  179. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/core/utils/line_reader.py +0 -0
  180. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/i18n/__init__.py +0 -0
  181. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/i18n/catalog.py +0 -0
  182. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/approval_no_person.tpl +0 -0
  183. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/check.tpl +0 -0
  184. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/error.tpl +0 -0
  185. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/inbox_list.tpl +0 -0
  186. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/inbox_target.tpl +0 -0
  187. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/read.tpl +0 -0
  188. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/reminder_list.tpl +0 -0
  189. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/reminder_mutation.tpl +0 -0
  190. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/reminder_schedule.tpl +0 -0
  191. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/unfollow.tpl +0 -0
  192. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/upgrade.tpl +0 -0
  193. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/bcc/version.tpl +0 -0
  194. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/command/attachment_suffix.tpl +0 -0
  195. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/command/check_message.tpl +0 -0
  196. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/command/freshness_hold.tpl +0 -0
  197. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/command/read_message.tpl +0 -0
  198. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/command/send_result.tpl +0 -0
  199. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/command/sender.tpl +0 -0
  200. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/developer_instructions.md +0 -0
  201. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/inbox_notice.tpl +0 -0
  202. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/system_service/launchd.plist +0 -0
  203. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/system_service/launchd.sh +0 -0
  204. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/system_service/systemd.service +0 -0
  205. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/system_service/windows.ps1 +0 -0
  206. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/system_service/windows.vbs +0 -0
  207. {bazaar_compute_node-0.1.38 → bazaar_compute_node-0.1.39}/src/bazaar_compute_node/resources/system_service/windows.xml +0 -0
@@ -1,11 +1,12 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bazaar-compute-node
3
- Version: 0.1.38
3
+ Version: 0.1.39
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
7
7
  Requires-Dist: aiohttp>=3.12.0
8
8
  Requires-Dist: aiosqlite>=0.20.0
9
+ Requires-Dist: click>=8.1.0
9
10
  Requires-Dist: cryptography>=45.0.0
10
11
  Requires-Dist: jinja2>=3.1.6
11
12
  Requires-Dist: packaging>=25.0
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.38"
3
+ version = "0.1.39"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -9,6 +9,7 @@ requires-python = ">=3.14"
9
9
  dependencies = [
10
10
  "aiohttp>=3.12.0",
11
11
  "aiosqlite>=0.20.0",
12
+ "click>=8.1.0",
12
13
  "cryptography>=45.0.0",
13
14
  "jinja2>=3.1.6",
14
15
  "packaging>=25.0",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.38"
3
+ version = "0.1.39"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -9,6 +9,7 @@ requires-python = ">=3.14"
9
9
  dependencies = [
10
10
  "aiohttp>=3.12.0",
11
11
  "aiosqlite>=0.20.0",
12
+ "click>=8.1.0",
12
13
  "cryptography>=45.0.0",
13
14
  "jinja2>=3.1.6",
14
15
  "packaging>=25.0",
@@ -12,7 +12,7 @@ from dataclasses import dataclass
12
12
  from functools import partial
13
13
  from pathlib import Path
14
14
 
15
- from ..core.channel import AgentScopedChannel, ChannelContext, IChannel
15
+ from ..core.channel import Channel, ChannelContext, IChannel
16
16
  from ..core.concurrency import ISessionConcurrency, SessionLockRegistry
17
17
  from ..core.lifecycle import TimeoutBudget
18
18
  from ..core.models import (
@@ -113,7 +113,7 @@ class AgentApplication:
113
113
  timer_wheel=self.timer_wheel,
114
114
  )
115
115
  )
116
- self.channel: IChannel = AgentScopedChannel(self.agent_id, provider_channel)
116
+ self.channel: IChannel = Channel(self.agent_id, provider_channel)
117
117
  runtime_contexts: list[RuntimeCommandContext] = []
118
118
  runtimes: list[IRuntime] = []
119
119
  for index, runtime_configuration in enumerate(configuration.runtimes):
@@ -170,7 +170,7 @@ class AgentApplication:
170
170
  ),
171
171
  workspace=self.workspace_path,
172
172
  translator=self.translator,
173
- error_feedback_detail=self._error_feedback_detail,
173
+ error_feedback_detail=self._redact_session_secrets,
174
174
  upgrade_notice=upgrade_notice,
175
175
  concurrency=self._concurrency,
176
176
  )
@@ -440,21 +440,13 @@ class AgentApplication:
440
440
  )
441
441
  return environment
442
442
 
443
- def _error_feedback_detail(
444
- self,
445
- session_id: object,
446
- error_message: object,
447
- ) -> str:
448
- if not isinstance(session_id, str) or not session_id:
449
- raise ValueError("session_id must be a non-empty string")
450
- if not isinstance(error_message, str):
451
- raise TypeError("error_message must be text")
443
+ def _redact_session_secrets(self, session_id: str, text: str) -> str:
452
444
  binding = self._session_capabilities.get(session_id)
453
445
  if binding is None:
454
- return error_message
446
+ return text
455
447
  for token in binding.token_values:
456
- error_message = error_message.replace(token, "<redacted>")
457
- return error_message
448
+ text = text.replace(token, "<redacted>")
449
+ return text
458
450
 
459
451
  async def _run_runtime_command(
460
452
  self,
@@ -0,0 +1,213 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ from collections.abc import Sequence
5
+ from dataclasses import dataclass, replace
6
+ from pathlib import Path
7
+ from types import MappingProxyType
8
+ from typing import Any
9
+ from uuid import uuid7
10
+
11
+ from ..core.runtime import RuntimeSandboxMode
12
+ from ..i18n import create_translator
13
+ from .config import (
14
+ AgentConfiguration,
15
+ ChannelConfiguration,
16
+ ConfigurationError,
17
+ NodeConfiguration,
18
+ RuntimeConfiguration,
19
+ _write_configuration,
20
+ load_node_configuration,
21
+ resolve_config_path,
22
+ )
23
+ from .usage import Usage
24
+
25
+
26
+ @dataclass(frozen=True, slots=True)
27
+ class _SetOptions:
28
+ agent: dict[str, object]
29
+ channel: dict[str, object]
30
+ runtime: dict[str, object]
31
+ env: dict[str, str]
32
+
33
+
34
+ def _set_options(
35
+ agent_options: Sequence[tuple[str, str, Any]], parser: Usage
36
+ ) -> _SetOptions:
37
+ """Sort every --set value under the scope it names."""
38
+
39
+ options = _SetOptions(agent={}, channel={}, runtime={}, env={})
40
+ seen: set[tuple[str, str]] = set()
41
+ for scope, key, value in agent_options:
42
+ # runtime.env accumulates one name per --set; a repeated name is
43
+ # simply overwritten, the same as a hand-edited config.toml would be
44
+ if (scope, key) == ("runtime", "env"):
45
+ options.env.update(value)
46
+ continue
47
+ identity = (scope, key)
48
+ if identity in seen:
49
+ parser.error(f"duplicate --set option: {scope}.{key}")
50
+ seen.add(identity)
51
+ if scope == "agent":
52
+ options.agent[key] = value
53
+ elif scope == "channel":
54
+ options.channel[key] = value
55
+ else:
56
+ options.runtime[key] = value
57
+ return options
58
+
59
+
60
+ def _runtime_configuration(
61
+ kind: str, options: _SetOptions, parser: Usage
62
+ ) -> RuntimeConfiguration:
63
+ """Read the runtime settings out of what --set collected."""
64
+
65
+ values = options.runtime
66
+ model = values.pop("model", None)
67
+ effort = values.pop("effort", None)
68
+ raw_sandbox_mode = values.pop(
69
+ "sandbox_mode",
70
+ RuntimeSandboxMode.WORKSPACE_WRITE.value,
71
+ )
72
+ if not isinstance(raw_sandbox_mode, str):
73
+ parser.error("runtime.sandbox_mode must be text")
74
+ try:
75
+ sandbox_mode = RuntimeSandboxMode(raw_sandbox_mode)
76
+ except ValueError:
77
+ allowed = ", ".join(mode.value for mode in RuntimeSandboxMode)
78
+ parser.error(f"runtime.sandbox_mode must be one of: {allowed}")
79
+ network_access = values.pop("network_access", True)
80
+ if not isinstance(network_access, bool):
81
+ parser.error("runtime.network_access must be a boolean")
82
+ if model is not None and not isinstance(model, str):
83
+ parser.error("runtime.model must be text")
84
+ if effort is not None and not isinstance(effort, str):
85
+ parser.error("runtime.effort must be text")
86
+ return RuntimeConfiguration(
87
+ kind=kind,
88
+ model=model,
89
+ effort=effort,
90
+ sandbox_mode=sandbox_mode,
91
+ network_access=network_access,
92
+ env=MappingProxyType(options.env),
93
+ options=MappingProxyType(values),
94
+ )
95
+
96
+
97
+ def _list_agents(configuration: NodeConfiguration) -> int:
98
+ if not configuration.agents:
99
+ print(create_translator(None).text("cli.agent.empty"), flush=True)
100
+ return 0
101
+ for agent in configuration.agents:
102
+ print(
103
+ f"id={agent.id} name={agent.name} "
104
+ f"channel={agent.channel.kind} runtime={_runtime_kinds(agent)}",
105
+ flush=True,
106
+ )
107
+ return 0
108
+
109
+
110
+ def _add_agent(
111
+ args: argparse.Namespace,
112
+ parser: Usage,
113
+ configuration: NodeConfiguration,
114
+ config_path: Path,
115
+ ) -> int:
116
+ options = _set_options(args.agent_options, parser)
117
+
118
+ idle_timeout = options.agent.pop("idle_timeout", 0)
119
+ if isinstance(idle_timeout, bool) or not isinstance(idle_timeout, int | float):
120
+ parser.error("agent.idle_timeout must be a number")
121
+
122
+ try:
123
+ agent = AgentConfiguration(
124
+ id=str(uuid7()),
125
+ name=args.name,
126
+ channel=ChannelConfiguration(
127
+ kind=args.channel,
128
+ options=MappingProxyType(options.channel),
129
+ ),
130
+ runtimes=(_runtime_configuration(args.runtime, options, parser),),
131
+ idle_timeout_seconds=idle_timeout,
132
+ )
133
+ updated = replace(
134
+ configuration,
135
+ agents=(*configuration.agents, agent),
136
+ )
137
+ _write_configuration(config_path, updated)
138
+ except ConfigurationError as error:
139
+ parser.error(str(error))
140
+ print(
141
+ f"Agent added id={agent.id} name={agent.name} "
142
+ f"channel={agent.channel.kind} runtime={_runtime_kinds(agent)}",
143
+ flush=True,
144
+ )
145
+ print("Run `bcn restart` to apply.", flush=True)
146
+ return 0
147
+
148
+
149
+ def _remove_agent(
150
+ args: argparse.Namespace,
151
+ parser: Usage,
152
+ configuration: NodeConfiguration,
153
+ config_path: Path,
154
+ ) -> int:
155
+ matches = [
156
+ agent
157
+ for agent in configuration.agents
158
+ if agent.id == args.selector or agent.name == args.selector
159
+ ]
160
+ if not matches:
161
+ parser.error(f"Agent not found: {args.selector}")
162
+ if len(matches) > 1:
163
+ parser.error(f"Agent selector is ambiguous: {args.selector}")
164
+ removed = matches[0]
165
+ updated = replace(
166
+ configuration,
167
+ agents=tuple(agent for agent in configuration.agents if agent.id != removed.id),
168
+ )
169
+ try:
170
+ _write_configuration(config_path, updated)
171
+ except ConfigurationError as error:
172
+ parser.error(str(error))
173
+ print(f"Agent removed id={removed.id} name={removed.name}", flush=True)
174
+ print("Workspace and durable data were preserved.", flush=True)
175
+ print("Run `bcn restart` to apply.", flush=True)
176
+ return 0
177
+
178
+
179
+ def run_agent_command(
180
+ args: argparse.Namespace,
181
+ parser: Usage,
182
+ ) -> int:
183
+ if (
184
+ args.storage is not None
185
+ or args.audit is not None
186
+ or args.database_name is not None
187
+ or args.endpoint is not None
188
+ or args.foreground
189
+ ):
190
+ parser.error("bcn agent commands only accept the node-level --config option")
191
+
192
+ config_path = (args.config or resolve_config_path()).expanduser()
193
+ try:
194
+ configuration = load_node_configuration(config_path)
195
+ except ConfigurationError as error:
196
+ parser.error(str(error))
197
+
198
+ match args.agent_command:
199
+ case "list":
200
+ return _list_agents(configuration)
201
+ case "add":
202
+ return _add_agent(args, parser, configuration, config_path)
203
+ case "remove":
204
+ return _remove_agent(args, parser, configuration, config_path)
205
+ case unsupported:
206
+ raise AssertionError(f"unsupported Agent command: {unsupported}")
207
+
208
+
209
+ def _runtime_kinds(agent: AgentConfiguration) -> str:
210
+ return ",".join(runtime.kind for runtime in agent.runtimes)
211
+
212
+
213
+ __all__ = ["run_agent_command"]
@@ -19,6 +19,7 @@ from ..core.paths import resolve_data_dir
19
19
  from ..core.restart import RESTART_EXIT_CODE
20
20
  from ..core.storage import IStorage
21
21
  from ..core.timerwheel import TimerWheel
22
+ from ..core.utils.text import format_exception
22
23
  from ..i18n import create_translator
23
24
  from .agent import AgentApplication
24
25
  from .config import AgentConfiguration, NodeConfiguration
@@ -214,7 +215,7 @@ class NodeApplication:
214
215
  runtimes=_runtime_kinds(configuration),
215
216
  status="failed",
216
217
  error_type=type(error).__name__,
217
- error=_safe_error(error),
218
+ error=format_exception(error),
218
219
  )
219
220
  self.agent_startup_results[configuration.id] = result
220
221
  self._log("agent.start.failed", **result.as_health_record())
@@ -265,7 +266,7 @@ class NodeApplication:
265
266
  "agent.stop.failed",
266
267
  agent_id=agent_id,
267
268
  error_type=type(error).__name__,
268
- error=_safe_error(error),
269
+ error=format_exception(error),
269
270
  )
270
271
  self.agents.clear()
271
272
  try:
@@ -349,7 +350,7 @@ class NodeApplication:
349
350
  "bcn.critical.failed",
350
351
  component=component,
351
352
  error_type=type(error).__name__,
352
- error=_safe_error(error),
353
+ error=format_exception(error),
353
354
  )
354
355
  raise error
355
356
  finally:
@@ -381,7 +382,7 @@ class NodeApplication:
381
382
  agent_id=agent_id,
382
383
  owner_session_id=message.session_id,
383
384
  error_type=type(error).__name__,
384
- error=_safe_error(error),
385
+ error=format_exception(error),
385
386
  )
386
387
  return False
387
388
  return True
@@ -508,9 +509,4 @@ class NodeApplication:
508
509
  )
509
510
 
510
511
 
511
- def _safe_error(error: BaseException) -> str:
512
- message = str(error).strip()
513
- return message or type(error).__name__
514
-
515
-
516
512
  __all__ = ["AgentStartupResult", "NodeApplication"]
@@ -61,6 +61,75 @@ class AttachmentMaterializer:
61
61
  continue
62
62
  await asyncio.to_thread(shutil.rmtree, path)
63
63
 
64
+ async def _stage(
65
+ self,
66
+ source: bytes | AsyncIterable[bytes],
67
+ temporary: Path,
68
+ destination: Path,
69
+ root: Path,
70
+ ) -> int:
71
+ """Write an attachment aside, then move it into place once it is whole."""
72
+
73
+ current_size = await asyncio.to_thread(self._stored_size, root)
74
+ descriptor: int | None = None
75
+ try:
76
+ descriptor = await asyncio.to_thread(
77
+ os.open,
78
+ temporary,
79
+ os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0),
80
+ 0o600,
81
+ )
82
+ try:
83
+ size = await self._write(descriptor, source, current_size)
84
+ await asyncio.to_thread(os.fsync, descriptor)
85
+ stored = await asyncio.to_thread(os.fstat, descriptor)
86
+ if stored.st_size != size:
87
+ raise OSError(
88
+ "attachment write size mismatch: "
89
+ f"expected {size}, got {stored.st_size}"
90
+ )
91
+ finally:
92
+ await asyncio.to_thread(os.close, descriptor)
93
+ descriptor = None
94
+ await asyncio.to_thread(destination.parent.mkdir, parents=True, mode=0o700)
95
+ await asyncio.to_thread(os.replace, temporary, destination)
96
+ if os.name != "nt":
97
+ await asyncio.to_thread(destination.chmod, 0o600)
98
+ return size
99
+ except BaseException:
100
+ if descriptor is not None:
101
+ await asyncio.to_thread(os.close, descriptor)
102
+ if await asyncio.to_thread(temporary.exists):
103
+ await asyncio.to_thread(temporary.unlink)
104
+ raise
105
+
106
+ async def _write(
107
+ self,
108
+ descriptor: int,
109
+ source: bytes | AsyncIterable[bytes],
110
+ current_size: int,
111
+ ) -> int:
112
+ """Write the source through, refusing it the moment it goes over a limit."""
113
+
114
+ if isinstance(source, bytes):
115
+ self._check_room(len(source), current_size)
116
+ await asyncio.to_thread(_write_all, descriptor, source)
117
+ return len(source)
118
+ size = 0
119
+ async for chunk in source:
120
+ if not isinstance(chunk, bytes):
121
+ raise TypeError("attachment stream must yield bytes")
122
+ size += len(chunk)
123
+ self._check_room(size, current_size)
124
+ await asyncio.to_thread(_write_all, descriptor, chunk)
125
+ return size
126
+
127
+ def _check_room(self, size: int, current_size: int) -> None:
128
+ if size > self._max_file_bytes:
129
+ raise ValueError("attachment exceeds the per-file size limit")
130
+ if current_size + size > self._max_workspace_bytes:
131
+ raise ValueError("attachment workspace quota exceeded")
132
+
64
133
  async def materialize(
65
134
  self,
66
135
  source: bytes | AsyncIterable[bytes],
@@ -84,64 +153,7 @@ class AttachmentMaterializer:
84
153
  await asyncio.to_thread(
85
154
  staging.mkdir, parents=True, exist_ok=True, mode=0o700
86
155
  )
87
- current_size = await asyncio.to_thread(self._stored_size, root)
88
- size = 0
89
- descriptor: int | None = None
90
- try:
91
- open_flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL
92
- binary_flag = getattr(os, "O_BINARY", None)
93
- if binary_flag is not None:
94
- open_flags |= binary_flag
95
- descriptor = await asyncio.to_thread(
96
- os.open,
97
- temporary,
98
- open_flags,
99
- 0o600,
100
- )
101
- try:
102
- if isinstance(source, bytes):
103
- size = len(source)
104
- if size > self._max_file_bytes:
105
- raise ValueError(
106
- "attachment exceeds the per-file size limit"
107
- )
108
- if current_size + size > self._max_workspace_bytes:
109
- raise ValueError("attachment workspace quota exceeded")
110
- await asyncio.to_thread(_write_all, descriptor, source)
111
- else:
112
- async for chunk in source:
113
- if not isinstance(chunk, bytes):
114
- raise TypeError("attachment stream must yield bytes")
115
- size += len(chunk)
116
- if size > self._max_file_bytes:
117
- raise ValueError(
118
- "attachment exceeds the per-file size limit"
119
- )
120
- if current_size + size > self._max_workspace_bytes:
121
- raise ValueError("attachment workspace quota exceeded")
122
- await asyncio.to_thread(_write_all, descriptor, chunk)
123
- await asyncio.to_thread(os.fsync, descriptor)
124
- stored_size = await asyncio.to_thread(os.fstat, descriptor)
125
- if stored_size.st_size != size:
126
- raise OSError(
127
- "attachment write size mismatch: "
128
- f"expected {size}, got {stored_size.st_size}"
129
- )
130
- finally:
131
- await asyncio.to_thread(os.close, descriptor)
132
- descriptor = None
133
- await asyncio.to_thread(
134
- destination.parent.mkdir, parents=True, mode=0o700
135
- )
136
- await asyncio.to_thread(os.replace, temporary, destination)
137
- if os.name != "nt":
138
- await asyncio.to_thread(destination.chmod, 0o600)
139
- except BaseException:
140
- if descriptor is not None:
141
- await asyncio.to_thread(os.close, descriptor)
142
- if await asyncio.to_thread(temporary.exists):
143
- await asyncio.to_thread(temporary.unlink)
144
- raise
156
+ size = await self._stage(source, temporary, destination, root)
145
157
  return InboundAttachment(
146
158
  attachment_id=attachment_id,
147
159
  name=Path(name).name,