bazaar-compute-node 0.1.28__tar.gz → 0.1.29__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 (141) hide show
  1. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/PKG-INFO +1 -1
  2. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/pyproject.toml +1 -1
  3. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/pyproject.toml.orig +1 -1
  4. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/agent.py +1 -0
  5. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/application.py +56 -2
  6. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/system_service.py +57 -161
  7. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/transport.py +7 -0
  8. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/windows_pipe.py +14 -2
  9. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/channel.py +20 -1
  10. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/database.py +4 -0
  11. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/executor.py +41 -5
  12. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/storage.py +1 -1
  13. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/telegram/channel.py +40 -1
  14. bazaar_compute_node-0.1.29/src/bazaar_compute_node/core/lifecycle.py +100 -0
  15. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/orchestration/reminder.py +29 -2
  16. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/orchestration/services.py +24 -12
  17. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/orchestration/session.py +88 -3
  18. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/timerwheel.py +9 -0
  19. bazaar_compute_node-0.1.29/src/bazaar_compute_node/resources/system_service/launchd.plist +37 -0
  20. bazaar_compute_node-0.1.29/src/bazaar_compute_node/resources/system_service/launchd.sh +11 -0
  21. bazaar_compute_node-0.1.29/src/bazaar_compute_node/resources/system_service/systemd.service +19 -0
  22. bazaar_compute_node-0.1.29/src/bazaar_compute_node/resources/system_service/windows.ps1 +98 -0
  23. bazaar_compute_node-0.1.29/src/bazaar_compute_node/resources/system_service/windows.vbs +11 -0
  24. bazaar_compute_node-0.1.29/src/bazaar_compute_node/resources/system_service/windows.xml +39 -0
  25. bazaar_compute_node-0.1.28/src/bazaar_compute_node/core/lifecycle.py +0 -48
  26. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/LICENSE +0 -0
  27. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/README.md +0 -0
  28. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/__init__.py +0 -0
  29. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/__init__.py +0 -0
  30. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/agent_management.py +0 -0
  31. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/attachments.py +0 -0
  32. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/command.py +0 -0
  33. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/config.py +0 -0
  34. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/registry.py +0 -0
  35. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/resource_dispatch.py +0 -0
  36. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/app/wrapper.py +0 -0
  37. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/bcc.py +0 -0
  38. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/cli.py +0 -0
  39. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/__init__.py +0 -0
  40. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
  41. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/codex/approval.py +0 -0
  42. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/codex/client.py +0 -0
  43. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/codex/events.py +0 -0
  44. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
  45. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/codex/process.py +0 -0
  46. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/codex/protocol.py +0 -0
  47. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/codex/runtime.py +0 -0
  48. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/__init__.py +0 -0
  49. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/api.py +0 -0
  50. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/approval.py +0 -0
  51. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/attachments.py +0 -0
  52. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/frame.py +0 -0
  53. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/identity.py +0 -0
  54. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/outbound.py +0 -0
  55. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/plugin.py +0 -0
  56. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/lark/transport.py +0 -0
  57. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
  58. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
  59. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
  60. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
  61. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/codec.py +0 -0
  62. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/__init__.py +0 -0
  63. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/model.py +0 -0
  64. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/registry.py +0 -0
  65. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v01_initial_node_schema.py +0 -0
  66. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v02_session_mapping_indexes.py +0 -0
  67. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v03_message_log_indexes.py +0 -0
  68. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v04_runtime_attempt_facts.py +0 -0
  69. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v05_inbound_message_references.py +0 -0
  70. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v06_inbound_message_reference_integrity.py +0 -0
  71. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v07_inbound_provider_identity.py +0 -0
  72. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v08_transient_stream_events.py +0 -0
  73. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v09_remove_runtime_events.py +0 -0
  74. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v10_add_outbound_attachments.py +0 -0
  75. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v11_remove_runtime_session_mapping.py +0 -0
  76. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v12_add_reminders.py +0 -0
  77. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v13_add_agent_ownership.py +0 -0
  78. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v14_add_inbox_discovery_indexes.py +0 -0
  79. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v15_add_handoffs.py +0 -0
  80. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v16_remove_outbound_drafts.py +0 -0
  81. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v17_remove_agent_identity_triggers.py +0 -0
  82. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v18_unify_messages.py +0 -0
  83. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v19_reminder_system_messages.py +0 -0
  84. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v20_remove_freshness_persistence.py +0 -0
  85. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v21_remove_handoffs.py +0 -0
  86. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/migrations/v22_readable_targets.py +0 -0
  87. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
  88. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -0
  89. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/repository/__init__.py +0 -0
  90. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/repository/base.py +0 -0
  91. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/repository/facade.py +0 -0
  92. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/repository/messages.py +0 -0
  93. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/repository/reminders.py +0 -0
  94. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/sqlite/repository/sessions.py +0 -0
  95. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/telegram/__init__.py +0 -0
  96. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/telegram/api.py +0 -0
  97. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/telegram/approval.py +0 -0
  98. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/telegram/attachments.py +0 -0
  99. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/telegram/identity.py +0 -0
  100. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/telegram/markdown.py +0 -0
  101. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/telegram/outbound.py +0 -0
  102. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/telegram/plugin.py +0 -0
  103. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
  104. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/wecom/channel.py +0 -0
  105. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/wecom/markdown.py +0 -0
  106. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
  107. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
  108. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/__init__.py +0 -0
  109. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/approval.py +0 -0
  110. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/audit.py +0 -0
  111. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/channel.py +0 -0
  112. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/client.py +0 -0
  113. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/command.py +0 -0
  114. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/concurrency.py +0 -0
  115. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/correlation.py +0 -0
  116. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/inbox.py +0 -0
  117. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/instruction.py +0 -0
  118. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/models/__init__.py +0 -0
  119. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/models/entities.py +0 -0
  120. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/models/reminder.py +0 -0
  121. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/models/reminder_owner.py +0 -0
  122. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/models/states.py +0 -0
  123. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/observability.py +0 -0
  124. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
  125. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/orchestration/command.py +0 -0
  126. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/orchestration/delivery.py +0 -0
  127. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/orchestration/error_feedback.py +0 -0
  128. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/orchestration/reminder_command.py +0 -0
  129. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/orchestration/turn.py +0 -0
  130. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/outcomes.py +0 -0
  131. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/paths.py +0 -0
  132. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/reminder.py +0 -0
  133. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/reminder_schedule.py +0 -0
  134. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/runtime.py +0 -0
  135. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/core/storage.py +0 -0
  136. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/i18n/__init__.py +0 -0
  137. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/i18n/catalog.py +0 -0
  138. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/rendering.py +0 -0
  139. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/resources/developer_instructions.md +0 -0
  140. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/resources/locales/en.toml +0 -0
  141. {bazaar_compute_node-0.1.28 → bazaar_compute_node-0.1.29}/src/bazaar_compute_node/resources/locales/zh-CN.toml +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: bazaar-compute-node
3
- Version: 0.1.28
3
+ Version: 0.1.29
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.1.28"
3
+ version = "0.1.29"
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.1.28"
3
+ version = "0.1.29"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -277,6 +277,7 @@ class AgentApplication:
277
277
  "channel": self.channel.name,
278
278
  "runtime": self.runtime.name,
279
279
  "channel_health": dict(self.channel.health),
280
+ "orchestrator_health": self.orchestrator.health,
280
281
  }
281
282
 
282
283
  async def _cleanup_partial_start(self) -> None:
@@ -10,7 +10,7 @@ from dataclasses import dataclass
10
10
  from pathlib import Path
11
11
 
12
12
  from ..core.concurrency import SessionLockRegistry
13
- from ..core.lifecycle import TimeoutBudget
13
+ from ..core.lifecycle import ITaskFailureSource, TimeoutBudget
14
14
  from ..core.models import InboundAttachment, Message
15
15
  from ..core.observability import IAudit
16
16
  from ..core.orchestration import ReminderScheduler
@@ -245,7 +245,61 @@ class NodeApplication:
245
245
  loop.add_signal_handler(signum, self._stopped.set)
246
246
  except NotImplementedError, RuntimeError:
247
247
  pass
248
- await self._stopped.wait()
248
+ stop_task = asyncio.create_task(self._stopped.wait(), name="bcn-stop-signal")
249
+ sources: list[tuple[str, ITaskFailureSource]] = [
250
+ ("timer_wheel", self.timer_wheel),
251
+ ("reminder_scheduler", self.reminder_scheduler),
252
+ ("command_server", self.command_server),
253
+ ]
254
+ if isinstance(self.storage, ITaskFailureSource):
255
+ sources.append(("storage", self.storage))
256
+ failure_tasks = {
257
+ asyncio.create_task(
258
+ source.wait_failure(),
259
+ name=f"bcn-critical-{name}",
260
+ ): name
261
+ for name, source in sources
262
+ }
263
+ tasks = {stop_task, *failure_tasks}
264
+ try:
265
+ done, _ = await asyncio.wait(
266
+ tasks,
267
+ return_when=asyncio.FIRST_COMPLETED,
268
+ )
269
+ failure: tuple[str, BaseException] | None = None
270
+ for task in done:
271
+ if task is stop_task:
272
+ continue
273
+ try:
274
+ task.result()
275
+ except asyncio.CancelledError:
276
+ caught: BaseException = RuntimeError(
277
+ f"{failure_tasks[task]} failure monitor stopped unexpectedly"
278
+ )
279
+ except BaseException as error: # noqa: BLE001
280
+ caught = error
281
+ else:
282
+ caught = RuntimeError(
283
+ f"{failure_tasks[task]} failure monitor returned unexpectedly"
284
+ )
285
+ if failure is None:
286
+ failure = failure_tasks[task], caught
287
+ if failure is not None:
288
+ component, error = failure
289
+ self._ready = False
290
+ self._accepting = False
291
+ self._log(
292
+ "bcn.critical.failed",
293
+ component=component,
294
+ error_type=type(error).__name__,
295
+ error=_safe_error(error),
296
+ )
297
+ raise error
298
+ finally:
299
+ for task in tasks:
300
+ if not task.done():
301
+ task.cancel()
302
+ await asyncio.gather(*tasks, return_exceptions=True)
249
303
 
250
304
  async def _publish_inbox_wake(
251
305
  self,
@@ -6,7 +6,6 @@ import getpass
6
6
  import locale
7
7
  import os
8
8
  import platform
9
- import plistlib
10
9
  import shlex
11
10
  import shutil
12
11
  import subprocess
@@ -15,10 +14,10 @@ import tempfile
15
14
  import time
16
15
  from dataclasses import dataclass
17
16
  from pathlib import Path
18
- from xml.etree import ElementTree
19
17
 
20
18
  from ..core.paths import resolve_data_dir
21
19
  from ..i18n import Translator, create_translator
20
+ from ..rendering import TextTemplate
22
21
  from .config import ConfigurationError, load_control_configuration, resolve_config_path
23
22
  from .transport import LocalCommandClient, local_endpoint_for_path
24
23
 
@@ -26,10 +25,16 @@ SYSTEMD_UNIT_NAME = "bcn.service"
26
25
  LAUNCHD_LABEL = "io.github.yuchanns.bazaar-compute-node"
27
26
  WINDOWS_TASK_NAME = r"\BazaarComputeNode"
28
27
  MANAGED_MARKER = "Managed by bazaar-compute-node."
29
- TASK_NAMESPACE = "http://schemas.microsoft.com/windows/2004/02/mit/task"
30
28
  WINDOWS_STOP_ATTEMPTS = 100
31
29
  WINDOWS_STOP_INTERVAL = 0.05
32
30
 
31
+ _SYSTEMD_UNIT_TEMPLATE = TextTemplate.from_resource("system_service/systemd.service")
32
+ _LAUNCHD_PLIST_TEMPLATE = TextTemplate.from_resource("system_service/launchd.plist")
33
+ _LAUNCHD_WRAPPER_TEMPLATE = TextTemplate.from_resource("system_service/launchd.sh")
34
+ _WINDOWS_TASK_TEMPLATE = TextTemplate.from_resource("system_service/windows.xml")
35
+ _WINDOWS_LAUNCHER_TEMPLATE = TextTemplate.from_resource("system_service/windows.vbs")
36
+ _WINDOWS_WRAPPER_TEMPLATE = TextTemplate.from_resource("system_service/windows.ps1")
37
+
33
38
 
34
39
  @dataclass(frozen=True, slots=True)
35
40
  class SystemServiceContext:
@@ -166,109 +171,53 @@ def _powershell_literal(path: Path | str | None) -> str:
166
171
 
167
172
 
168
173
  def _render_systemd_unit(context: SystemServiceContext) -> str:
169
- executable = _require_executable(context)
170
- lines = [
171
- f"# {MANAGED_MARKER}",
172
- "[Unit]",
173
- "Description=Bazaar Compute Node",
174
- "",
175
- "[Service]",
176
- "Type=simple",
177
- f"ExecStart={_systemd_quote(executable)} run --config {_systemd_quote(context.config_path)}",
178
- f"WorkingDirectory={_systemd_quote(context.data_dir)}",
179
- ]
180
- if context.env_file is not None:
181
- lines.append(f"EnvironmentFile=-{_systemd_quote(context.env_file)}")
182
- lines.extend(
183
- (
184
- "Restart=on-failure",
185
- "RestartSec=5",
186
- "KillSignal=SIGTERM",
187
- "TimeoutStopSec=15",
188
- "UMask=0077",
189
- "StandardOutput=journal",
190
- "StandardError=journal",
191
- "",
192
- "[Install]",
193
- "WantedBy=default.target",
194
- "",
195
- )
174
+ return _SYSTEMD_UNIT_TEMPLATE.render(
175
+ {
176
+ "managed_marker": MANAGED_MARKER,
177
+ "executable": _systemd_quote(_require_executable(context)),
178
+ "config_path": _systemd_quote(context.config_path),
179
+ "data_dir": _systemd_quote(context.data_dir),
180
+ "environment_file": (
181
+ "" if context.env_file is None else _systemd_quote(context.env_file)
182
+ ),
183
+ }
196
184
  )
197
- return "\n".join(lines)
198
185
 
199
186
 
200
187
  def _render_launchd_wrapper() -> str:
201
- return f"""#!/bin/sh
202
- # {MANAGED_MARKER}
203
- set -eu
204
-
205
- if [ -n "${{BCN_ENV_FILE:-}}" ] && [ -f "$BCN_ENV_FILE" ]; then
206
- set -a
207
- . "$BCN_ENV_FILE"
208
- set +a
209
- fi
210
-
211
- exec "$BCN_EXECUTABLE" run --config "$BCN_CONFIG"
212
- """
188
+ return _LAUNCHD_WRAPPER_TEMPLATE.render({"managed_marker": MANAGED_MARKER})
213
189
 
214
190
 
215
191
  def _render_launchd_plist(
216
192
  context: SystemServiceContext,
217
193
  wrapper_path: Path,
218
194
  ) -> bytes:
219
- payload = {
220
- "Label": LAUNCHD_LABEL,
221
- "Comment": MANAGED_MARKER,
222
- "ProgramArguments": [str(wrapper_path)],
223
- "WorkingDirectory": str(context.data_dir),
224
- "EnvironmentVariables": {
225
- "BCN_CONFIG": str(context.config_path),
226
- "BCN_ENV_FILE": "" if context.env_file is None else str(context.env_file),
227
- "BCN_EXECUTABLE": str(_require_executable(context)),
228
- },
229
- "RunAtLoad": True,
230
- "KeepAlive": True,
231
- "ProcessType": "Background",
232
- "ThrottleInterval": 5,
233
- "StandardOutPath": str(context.log_path),
234
- "StandardErrorPath": str(context.log_path),
235
- }
236
- return plistlib.dumps(payload, fmt=plistlib.FMT_XML, sort_keys=False)
195
+ return _LAUNCHD_PLIST_TEMPLATE.render(
196
+ {
197
+ "label": LAUNCHD_LABEL,
198
+ "managed_marker": MANAGED_MARKER,
199
+ "wrapper_path": str(wrapper_path),
200
+ "data_dir": str(context.data_dir),
201
+ "config_path": str(context.config_path),
202
+ "environment_file": (
203
+ "" if context.env_file is None else str(context.env_file)
204
+ ),
205
+ "executable": str(_require_executable(context)),
206
+ "log_path": str(context.log_path),
207
+ }
208
+ ).encode("utf-8")
237
209
 
238
210
 
239
211
  def _render_windows_wrapper(context: SystemServiceContext) -> str:
240
- executable = _powershell_literal(_require_executable(context))
241
- config_path = _powershell_literal(context.config_path)
242
- environment_script = _powershell_literal(context.env_file)
243
- log_path = _powershell_literal(context.log_path)
244
- return f"""# {MANAGED_MARKER}
245
- $ErrorActionPreference = 'Stop'
246
-
247
- $executable = {executable}
248
- $configPath = {config_path}
249
- $environmentScript = {environment_script}
250
- $logPath = {log_path}
251
-
252
- if ($environmentScript -and (Test-Path -LiteralPath $environmentScript)) {{
253
- try {{
254
- . $environmentScript
255
- }} catch {{
256
- $_ | Out-String | Add-Content -LiteralPath $logPath -Encoding utf8
257
- exit 1
258
- }}
259
- }}
260
-
261
- # Native stderr is routed to the service log and must not terminate the wrapper.
262
- $ErrorActionPreference = 'Continue'
263
- try {{
264
- & $executable run --config $configPath *>> $logPath
265
- $exitCode = $LASTEXITCODE
266
- }} catch {{
267
- $_ | Out-String | Add-Content -LiteralPath $logPath -Encoding utf8
268
- exit 1
269
- }}
270
- exit $exitCode
271
- """
212
+ return _WINDOWS_WRAPPER_TEMPLATE.render(
213
+ {
214
+ "managed_marker": MANAGED_MARKER,
215
+ "executable": _powershell_literal(_require_executable(context)),
216
+ "config_path": _powershell_literal(context.config_path),
217
+ "environment_script": _powershell_literal(context.env_file),
218
+ "log_path": _powershell_literal(context.log_path),
219
+ }
220
+ )
272
221
 
273
222
 
274
223
  def _vbs_literal(value: Path | str) -> str:
@@ -280,80 +229,27 @@ def _render_windows_launcher(wrapper_path: Path) -> str:
280
229
  "powershell.exe -NoProfile -NonInteractive -WindowStyle Hidden "
281
230
  f'-ExecutionPolicy Bypass -File "{wrapper_path}"'
282
231
  )
283
- return f"""' {MANAGED_MARKER}
284
- Option Explicit
285
-
286
- Dim shell
287
- Dim command
288
- Dim exitCode
289
-
290
- Set shell = CreateObject("WScript.Shell")
291
- command = {command}
292
- exitCode = shell.Run(command, 0, True)
293
- WScript.Quit exitCode
294
- """
295
-
296
-
297
- def _add_task_element(
298
- parent: ElementTree.Element,
299
- name: str,
300
- text: str | None = None,
301
- ) -> ElementTree.Element:
302
- element = ElementTree.SubElement(parent, f"{{{TASK_NAMESPACE}}}{name}")
303
- if text is not None:
304
- element.text = text
305
- return element
232
+ return _WINDOWS_LAUNCHER_TEMPLATE.render(
233
+ {
234
+ "managed_marker": MANAGED_MARKER,
235
+ "command": command,
236
+ }
237
+ )
306
238
 
307
239
 
308
240
  def _render_windows_task(
309
241
  context: SystemServiceContext,
310
242
  launcher_path: Path,
311
243
  ) -> bytes:
312
- ElementTree.register_namespace("", TASK_NAMESPACE)
313
- task = ElementTree.Element(
314
- f"{{{TASK_NAMESPACE}}}Task",
315
- {"version": "1.4"},
316
- )
317
- registration = _add_task_element(task, "RegistrationInfo")
318
- _add_task_element(registration, "Author", context.user)
319
- _add_task_element(registration, "Description", MANAGED_MARKER)
320
- triggers = _add_task_element(task, "Triggers")
321
- logon_trigger = _add_task_element(triggers, "LogonTrigger")
322
- _add_task_element(logon_trigger, "Enabled", "true")
323
- _add_task_element(logon_trigger, "UserId", context.user)
324
- principals = _add_task_element(task, "Principals")
325
- principal = ElementTree.SubElement(
326
- principals,
327
- f"{{{TASK_NAMESPACE}}}Principal",
328
- {"id": "Author"},
329
- )
330
- _add_task_element(principal, "UserId", context.user)
331
- _add_task_element(principal, "LogonType", "InteractiveToken")
332
- _add_task_element(principal, "RunLevel", "LeastPrivilege")
333
- settings = _add_task_element(task, "Settings")
334
- _add_task_element(settings, "MultipleInstancesPolicy", "IgnoreNew")
335
- _add_task_element(settings, "DisallowStartIfOnBatteries", "false")
336
- _add_task_element(settings, "StopIfGoingOnBatteries", "false")
337
- _add_task_element(settings, "AllowHardTerminate", "true")
338
- _add_task_element(settings, "StartWhenAvailable", "true")
339
- _add_task_element(settings, "ExecutionTimeLimit", "PT0S")
340
- restart = _add_task_element(settings, "RestartOnFailure")
341
- _add_task_element(restart, "Interval", "PT5M")
342
- _add_task_element(restart, "Count", "3")
343
- actions = ElementTree.SubElement(
344
- task,
345
- f"{{{TASK_NAMESPACE}}}Actions",
346
- {"Context": "Author"},
347
- )
348
- action = _add_task_element(actions, "Exec")
349
- _add_task_element(action, "Command", "wscript.exe")
350
- _add_task_element(
351
- action,
352
- "Arguments",
353
- f'//B //Nologo "{launcher_path}"',
354
- )
355
- _add_task_element(action, "WorkingDirectory", str(context.data_dir))
356
- return ElementTree.tostring(task, encoding="utf-16", xml_declaration=True)
244
+ rendered = _WINDOWS_TASK_TEMPLATE.render(
245
+ {
246
+ "user": context.user,
247
+ "managed_marker": MANAGED_MARKER,
248
+ "arguments": f'//B //Nologo "{launcher_path}"',
249
+ "data_dir": str(context.data_dir),
250
+ }
251
+ )
252
+ return rendered.encode("utf-16")
357
253
 
358
254
 
359
255
  def _write_managed_file(path: Path, content: str | bytes, *, mode: int) -> None:
@@ -134,6 +134,13 @@ class LocalCommandServer:
134
134
  if (path_stat.st_dev, path_stat.st_ino) == identity:
135
135
  await asyncio.to_thread(path.unlink)
136
136
 
137
+ async def wait_failure(self) -> None:
138
+ windows_server = self._windows_server
139
+ if windows_server is not None:
140
+ await windows_server.wait_failure()
141
+ return
142
+ await asyncio.Future()
143
+
137
144
  async def _handle_client(
138
145
  self,
139
146
  reader: asyncio.StreamReader,
@@ -12,6 +12,7 @@ from pathlib import Path
12
12
  from typing import Any
13
13
  from urllib.parse import urlsplit
14
14
 
15
+ from bazaar_compute_node.core.lifecycle import TaskFailureSignal
15
16
  from bazaar_compute_node.core.paths import resolve_data_dir
16
17
 
17
18
  RequestHandler = Callable[[Mapping[str, object]], Awaitable[Mapping[str, object]]]
@@ -301,6 +302,7 @@ class WindowsNamedPipeServer:
301
302
  self._stop = threading.Event()
302
303
  self._ready = threading.Event()
303
304
  self._startup_errors: list[BaseException] = []
305
+ self._failure = TaskFailureSignal()
304
306
  self._thread: threading.Thread | None = None
305
307
  self._workers: set[threading.Thread] = set()
306
308
  self._workers_lock = threading.Lock()
@@ -317,6 +319,7 @@ class WindowsNamedPipeServer:
317
319
  self._stop.clear()
318
320
  self._ready.clear()
319
321
  self._startup_errors.clear()
322
+ self._failure.reset()
320
323
  self._thread = threading.Thread(
321
324
  target=self._serve,
322
325
  name="bcn-windows-named-pipe",
@@ -332,6 +335,7 @@ class WindowsNamedPipeServer:
332
335
  raise TimeoutError("Windows named pipe server did not become ready")
333
336
 
334
337
  async def stop(self) -> None:
338
+ self._failure.disable()
335
339
  thread = self._thread
336
340
  if thread is None:
337
341
  _release_mutex(self._mutex)
@@ -352,8 +356,16 @@ class WindowsNamedPipeServer:
352
356
  try:
353
357
  self._serve_loop()
354
358
  except Exception as error: # noqa: BLE001
355
- self._startup_errors.append(error)
356
- self._ready.set()
359
+ if not self._ready.is_set():
360
+ self._startup_errors.append(error)
361
+ self._ready.set()
362
+ return
363
+ loop = self._loop
364
+ if loop is not None:
365
+ loop.call_soon_threadsafe(self._failure.fail, error)
366
+
367
+ async def wait_failure(self) -> None:
368
+ await self._failure.wait()
357
369
 
358
370
  def _serve_loop(self) -> None:
359
371
  initialized = False
@@ -242,6 +242,7 @@ class LarkChannel(IChannel):
242
242
  self._run_typing_dispatcher(),
243
243
  name="bcn-lark-typing",
244
244
  )
245
+ self._typing_runner.add_done_callback(self._typing_runner_done)
245
246
  await transport.start(timeout=_remaining(deadline))
246
247
  generation = transport.health.get("connection_generation", 0)
247
248
  self._connection_generation = (
@@ -731,7 +732,9 @@ class LarkChannel(IChannel):
731
732
  isinstance(raw_name, str)
732
733
  and raw_name.strip()
733
734
  and "]" not in raw_name
734
- and not any(category(character) == "Cc" for character in raw_name)
735
+ and not any(
736
+ category(character) in {"Cc", "Zl", "Zp"} for character in raw_name
737
+ )
735
738
  ):
736
739
  name = raw_name
737
740
  else:
@@ -997,6 +1000,22 @@ class LarkChannel(IChannel):
997
1000
  if state.terminal or self._typing_stopping:
998
1001
  self._typing_states.pop(command.session_id, None)
999
1002
 
1003
+ def _typing_runner_done(self, task: asyncio.Task[None]) -> None:
1004
+ if self._typing_stopping:
1005
+ return
1006
+ error = (
1007
+ RuntimeError("Lark typing dispatcher was canceled unexpectedly")
1008
+ if task.cancelled()
1009
+ else task.exception()
1010
+ or RuntimeError("Lark typing dispatcher stopped unexpectedly")
1011
+ )
1012
+ self._typing_failures += 1
1013
+ _LOGGER.error(
1014
+ "Lark typing dispatcher failed: %s",
1015
+ error,
1016
+ exc_info=(type(error), error, error.__traceback__),
1017
+ )
1018
+
1000
1019
  async def _stop_typing(self, *, timeout: float) -> None:
1001
1020
  self._typing_stopping = True
1002
1021
  runner = self._typing_runner
@@ -198,6 +198,10 @@ class SqliteDatabase:
198
198
  self._executor = None
199
199
  self._schema_version = None
200
200
 
201
+ async def wait_failure(self) -> None:
202
+ executor = self._require_executor()
203
+ await executor.wait_failure()
204
+
201
205
  async def fetchone(
202
206
  self,
203
207
  statement: str,
@@ -1,6 +1,7 @@
1
1
  from __future__ import annotations
2
2
 
3
3
  import asyncio
4
+ import logging
4
5
  from collections import deque
5
6
  from collections.abc import Awaitable, Callable, Iterable, Sequence
6
7
  from contextlib import AbstractAsyncContextManager
@@ -11,6 +12,10 @@ from typing import TypeVar
11
12
 
12
13
  import aiosqlite
13
14
 
15
+ from ...core.lifecycle import TaskFailureSignal
16
+
17
+ _LOGGER = logging.getLogger(__name__)
18
+
14
19
  T = TypeVar("T")
15
20
 
16
21
 
@@ -222,6 +227,7 @@ class SqliteExecutor:
222
227
  self._write_queue = asyncio.Queue[_WriteRequest[object] | None]()
223
228
  self._writer_task: asyncio.Task[None] | None = None
224
229
  self._reader_reaper_task: asyncio.Task[None] | None = None
230
+ self._failure = TaskFailureSignal()
225
231
  self._reader_reaper_stop = asyncio.Event()
226
232
  self._accepting = False
227
233
  self._borrowed_readers = 0
@@ -238,6 +244,7 @@ class SqliteExecutor:
238
244
  if self._writer_task is not None:
239
245
  return
240
246
  self._accepting = True
247
+ self._failure.reset()
241
248
  self._reader_reaper_stop.clear()
242
249
  self._writer_task = asyncio.create_task(
243
250
  self._run_writer(),
@@ -247,10 +254,17 @@ class SqliteExecutor:
247
254
  self._reap_idle_readers(),
248
255
  name="bcn-sqlite-reader-reaper",
249
256
  )
257
+ self._failure.observe(self._writer_task, component="SQLite writer")
258
+ self._reader_reaper_task.add_done_callback(self._reader_reaper_done)
250
259
 
251
260
  async def stop(self) -> None:
252
261
  if self._closed:
253
262
  return
263
+ writer_task = self._writer_task
264
+ if writer_task is not None and writer_task.done():
265
+ await self.abort()
266
+ return
267
+ self._failure.disable()
254
268
  self._accepting = False
255
269
  await self._write_queue.join()
256
270
  async with self._reader_condition:
@@ -274,6 +288,7 @@ class SqliteExecutor:
274
288
  """Interrupt active work and close all connections after a failed drain."""
275
289
  if self._closed:
276
290
  return
291
+ self._failure.disable()
277
292
  self._accepting = False
278
293
  async with self._reader_condition:
279
294
  self._reader_condition.notify_all()
@@ -309,10 +324,7 @@ class SqliteExecutor:
309
324
  writer_task = self._writer_task
310
325
  if writer_task is not None:
311
326
  writer_task.cancel()
312
- try:
313
- await writer_task
314
- except asyncio.CancelledError:
315
- pass
327
+ await asyncio.gather(writer_task, return_exceptions=True)
316
328
  self._writer_task = None
317
329
 
318
330
  await self._stop_reader_reaper()
@@ -520,7 +532,15 @@ class SqliteExecutor:
520
532
  to_close.append(reader.connection)
521
533
  excess -= 1
522
534
  for connection in to_close:
523
- await connection.close()
535
+ try:
536
+ await connection.close()
537
+ except asyncio.CancelledError:
538
+ raise
539
+ except Exception:
540
+ _LOGGER.exception("SQLite idle reader close failed")
541
+
542
+ async def wait_failure(self) -> None:
543
+ await self._failure.wait()
524
544
 
525
545
  async def _stop_reader_reaper(self) -> None:
526
546
  task = self._reader_reaper_task
@@ -530,6 +550,22 @@ class SqliteExecutor:
530
550
  await task
531
551
  self._reader_reaper_task = None
532
552
 
553
+ def _reader_reaper_done(self, task: asyncio.Task[None]) -> None:
554
+ if not self._accepting:
555
+ return
556
+ self._reader_reaper_task = None
557
+ error = (
558
+ RuntimeError("SQLite reader reaper was canceled unexpectedly")
559
+ if task.cancelled()
560
+ else task.exception()
561
+ or RuntimeError("SQLite reader reaper stopped unexpectedly")
562
+ )
563
+ _LOGGER.error(
564
+ "SQLite reader reaper failed: %s",
565
+ error,
566
+ exc_info=(type(error), error, error.__traceback__),
567
+ )
568
+
533
569
  def _require_accepting(self) -> None:
534
570
  if not self._accepting:
535
571
  raise SqliteExecutorClosedError("SQLite executor is not accepting work")
@@ -40,7 +40,7 @@ _READ_OPERATIONS = frozenset(
40
40
  }
41
41
  )
42
42
 
43
- _SNAPSHOT_READ_OPERATIONS = frozenset({"read_message_history"})
43
+ _SNAPSHOT_READ_OPERATIONS = frozenset({"read_inbox_catalog", "read_message_history"})
44
44
 
45
45
  _TRANSACTIONAL_WRITE_OPERATIONS = frozenset(
46
46
  {