bazaar-compute-node 0.1.8__tar.gz → 0.1.9__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 (86) hide show
  1. bazaar_compute_node-0.1.9/PKG-INFO +106 -0
  2. bazaar_compute_node-0.1.9/README.md +93 -0
  3. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/pyproject.toml +3 -2
  4. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/pyproject.toml.orig +3 -2
  5. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/application.py +1 -1
  6. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/command.py +4 -2
  7. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/config.py +14 -10
  8. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/registry.py +24 -7
  9. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/cli.py +2 -5
  10. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/codex/approval.py +50 -3
  11. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/telegram/__init__.py +1 -0
  12. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/telegram/api.py +356 -0
  13. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/telegram/approval.py +411 -0
  14. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/telegram/attachments.py +442 -0
  15. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/telegram/channel.py +846 -0
  16. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/telegram/identity.py +68 -0
  17. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/telegram/markdown.py +343 -0
  18. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/telegram/outbound.py +796 -0
  19. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/telegram/plugin.py +20 -0
  20. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/wecom/channel.py +10 -5
  21. bazaar_compute_node-0.1.9/src/bazaar_compute_node/contrib/wecom/plugin.py +33 -0
  22. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/channel.py +50 -5
  23. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/models/entities.py +3 -0
  24. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/orchestration/turn.py +23 -4
  25. bazaar_compute_node-0.1.8/PKG-INFO +0 -184
  26. bazaar_compute_node-0.1.8/README.md +0 -171
  27. bazaar_compute_node-0.1.8/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -29
  28. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/LICENSE +0 -0
  29. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/__init__.py +0 -0
  30. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/__init__.py +0 -0
  31. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/attachments.py +0 -0
  32. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/reminder_dispatch.py +0 -0
  33. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/transport.py +0 -0
  34. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
  35. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/app/wrapper.py +0 -0
  36. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/bcc.py +0 -0
  37. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/__init__.py +0 -0
  38. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
  39. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/codex/client.py +0 -0
  40. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/codex/events.py +0 -0
  41. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
  42. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/codex/process.py +0 -0
  43. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/codex/protocol.py +0 -0
  44. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/codex/runtime.py +0 -0
  45. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
  46. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
  47. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
  48. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
  49. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/codec.py +0 -0
  50. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
  51. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/migrations.py +0 -0
  52. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
  53. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -0
  54. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/reminder_database.py +0 -0
  55. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/reminder_migration.py +0 -0
  56. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/reminder_repository.py +0 -0
  57. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/sqlite/repository.py +0 -0
  58. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
  59. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/wecom/markdown.py +0 -0
  60. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
  61. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/__init__.py +0 -0
  62. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/approval.py +0 -0
  63. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/audit.py +0 -0
  64. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/client.py +0 -0
  65. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/command.py +0 -0
  66. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/concurrency.py +0 -0
  67. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/correlation.py +0 -0
  68. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/instruction.py +0 -0
  69. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/lifecycle.py +0 -0
  70. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/models/__init__.py +0 -0
  71. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/models/reminder.py +0 -0
  72. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/models/states.py +0 -0
  73. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/observability.py +0 -0
  74. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
  75. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/orchestration/command.py +0 -0
  76. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/orchestration/reminder.py +0 -0
  77. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/orchestration/reminder_command.py +0 -0
  78. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/orchestration/services.py +0 -0
  79. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/orchestration/session.py +0 -0
  80. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/outcomes.py +0 -0
  81. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/paths.py +0 -0
  82. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/reminder.py +0 -0
  83. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/reminder_schedule.py +0 -0
  84. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/runtime.py +0 -0
  85. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/storage.py +0 -0
  86. {bazaar_compute_node-0.1.8 → bazaar_compute_node-0.1.9}/src/bazaar_compute_node/core/timerwheel.py +0 -0
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.4
2
+ Name: bazaar-compute-node
3
+ Version: 0.1.9
4
+ Summary: Runtime-agnostic computer node daemon for agents and channels.
5
+ License-Expression: AGPL-3.0-only
6
+ License-File: LICENSE
7
+ Requires-Dist: aiohttp>=3.12.0
8
+ Requires-Dist: aiosqlite>=0.20.0
9
+ Requires-Dist: cryptography>=45.0.0
10
+ Requires-Python: >=3.14
11
+ Project-URL: Repository, https://github.com/yuchanns/bazaar-compute-node
12
+ Description-Content-Type: text/markdown
13
+
14
+ # Bazaar Compute Node
15
+
16
+ Collaborate with your Agents in the bazaar across compute nodes, through any interface, with any harness runtime.
17
+ 在集市里与你的 Agent 们合作,透过任意界面与任意 Harness。
18
+
19
+ <p align="left">
20
+ <a href="https://github.com/yuchanns/bazaar-compute-node/actions"><img
21
+ src="https://github.com/yuchanns/bazaar-compute-node/actions/workflows/release.yml/badge.svg"
22
+ alt="Release"
23
+ /></a>
24
+ <a href="https://pypi.org/project/bazaar-compute-node"><img
25
+ src="https://img.shields.io/pypi/v/bazaar-compute-node.svg"
26
+ alt="Python Package Index"
27
+ /></a>
28
+ </p>
29
+
30
+ **Documentation is coming soon...**
31
+
32
+ ## Quick Start / 快速开始
33
+
34
+ Python 3.14+ is required:
35
+
36
+ ```sh
37
+ # Install
38
+ uv tool install bazaar-compute-node
39
+ bcn --version
40
+
41
+ # Upgrade
42
+ uv tool upgrade bazaar-compute-node
43
+
44
+ # Start daemon
45
+ export BCN_WECOM_BOT_SECRET='your-bot-secret'
46
+
47
+ bcn start --channel wecom --runtime codex \
48
+ --model gpt-5.6-luna --effort max \
49
+ --sandbox-mode workspace-write \
50
+ --network-access true \
51
+ --idle-timeout 600
52
+
53
+ # Stop daemon
54
+ bcn stop
55
+ ```
56
+
57
+ ## Features / 功能
58
+
59
+ - ✅ Done / 已完成
60
+ - 🚧 Working in progress / 开发中
61
+
62
+ ### Facilities / 通用能力
63
+
64
+ | Status / 状态 | Ability / 能力 |
65
+ | --- | --- |
66
+ | ✅ | Reminders / 定时器 |
67
+ | ✅ | Attachments / 附件 |
68
+ | 🚧 | Multi-Agents / 多 Agent |
69
+ | 🚧 | Teams / 团队协作 |
70
+
71
+ ### Channels / 渠道
72
+
73
+ | Status / 状态 | Channel / 渠道 |
74
+ | --- | --- |
75
+ | ✅ | WeCom / 企业微信 |
76
+ | ✅ | Telegram |
77
+ | 🚧 | GitHub |
78
+ | 🚧 | GitLab |
79
+
80
+ ### Harnesses
81
+
82
+ | Status / 状态 | Runtime / 运行时 |
83
+ | --- | --- |
84
+ | ✅ | Codex |
85
+ | 🚧 | Claude Code |
86
+ | 🚧 | Pi |
87
+ | 🚧 | Hermes Agent |
88
+ | 🚧 | DeepSeek Harness |
89
+
90
+ ### Platforms / 平台
91
+
92
+ | Status / 状态 | Platform / 平台 |
93
+ | --- | --- |
94
+ | ✅ | Windows |
95
+ | ✅ | macOS |
96
+ | ✅ | Linux |
97
+
98
+ ## License / 许可证
99
+
100
+ This project is licensed under the [AGPLv3](./LICENSE).
101
+ 本项目采用 [AGPLv3](./LICENSE) 许可证。
102
+
103
+ ## Credits / 致谢
104
+
105
+ This project is inspired by [raft.build](https://raft.build).
106
+ 本项目的灵感来自 [raft.build](https://raft.build)。
@@ -0,0 +1,93 @@
1
+ # Bazaar Compute Node
2
+
3
+ Collaborate with your Agents in the bazaar across compute nodes, through any interface, with any harness runtime.
4
+ 在集市里与你的 Agent 们合作,透过任意界面与任意 Harness。
5
+
6
+ <p align="left">
7
+ <a href="https://github.com/yuchanns/bazaar-compute-node/actions"><img
8
+ src="https://github.com/yuchanns/bazaar-compute-node/actions/workflows/release.yml/badge.svg"
9
+ alt="Release"
10
+ /></a>
11
+ <a href="https://pypi.org/project/bazaar-compute-node"><img
12
+ src="https://img.shields.io/pypi/v/bazaar-compute-node.svg"
13
+ alt="Python Package Index"
14
+ /></a>
15
+ </p>
16
+
17
+ **Documentation is coming soon...**
18
+
19
+ ## Quick Start / 快速开始
20
+
21
+ Python 3.14+ is required:
22
+
23
+ ```sh
24
+ # Install
25
+ uv tool install bazaar-compute-node
26
+ bcn --version
27
+
28
+ # Upgrade
29
+ uv tool upgrade bazaar-compute-node
30
+
31
+ # Start daemon
32
+ export BCN_WECOM_BOT_SECRET='your-bot-secret'
33
+
34
+ bcn start --channel wecom --runtime codex \
35
+ --model gpt-5.6-luna --effort max \
36
+ --sandbox-mode workspace-write \
37
+ --network-access true \
38
+ --idle-timeout 600
39
+
40
+ # Stop daemon
41
+ bcn stop
42
+ ```
43
+
44
+ ## Features / 功能
45
+
46
+ - ✅ Done / 已完成
47
+ - 🚧 Working in progress / 开发中
48
+
49
+ ### Facilities / 通用能力
50
+
51
+ | Status / 状态 | Ability / 能力 |
52
+ | --- | --- |
53
+ | ✅ | Reminders / 定时器 |
54
+ | ✅ | Attachments / 附件 |
55
+ | 🚧 | Multi-Agents / 多 Agent |
56
+ | 🚧 | Teams / 团队协作 |
57
+
58
+ ### Channels / 渠道
59
+
60
+ | Status / 状态 | Channel / 渠道 |
61
+ | --- | --- |
62
+ | ✅ | WeCom / 企业微信 |
63
+ | ✅ | Telegram |
64
+ | 🚧 | GitHub |
65
+ | 🚧 | GitLab |
66
+
67
+ ### Harnesses
68
+
69
+ | Status / 状态 | Runtime / 运行时 |
70
+ | --- | --- |
71
+ | ✅ | Codex |
72
+ | 🚧 | Claude Code |
73
+ | 🚧 | Pi |
74
+ | 🚧 | Hermes Agent |
75
+ | 🚧 | DeepSeek Harness |
76
+
77
+ ### Platforms / 平台
78
+
79
+ | Status / 状态 | Platform / 平台 |
80
+ | --- | --- |
81
+ | ✅ | Windows |
82
+ | ✅ | macOS |
83
+ | ✅ | Linux |
84
+
85
+ ## License / 许可证
86
+
87
+ This project is licensed under the [AGPLv3](./LICENSE).
88
+ 本项目采用 [AGPLv3](./LICENSE) 许可证。
89
+
90
+ ## Credits / 致谢
91
+
92
+ This project is inspired by [raft.build](https://raft.build).
93
+ 本项目的灵感来自 [raft.build](https://raft.build)。
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.8"
3
+ version = "0.1.9"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -22,7 +22,8 @@ bcn = "bazaar_compute_node.cli:main"
22
22
  codex = "bazaar_compute_node.contrib.codex.plugin:create_runtime"
23
23
 
24
24
  [project.entry-points."bazaar_compute_node.channels"]
25
- wecom = "bazaar_compute_node.contrib.wecom.plugin:create_channel"
25
+ wecom = "bazaar_compute_node.contrib.wecom.plugin:builder"
26
+ telegram = "bazaar_compute_node.contrib.telegram.plugin:builder"
26
27
 
27
28
  [project.entry-points."bazaar_compute_node.storages"]
28
29
  sqlite = "bazaar_compute_node.contrib.sqlite.plugin:create_storage"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "bazaar-compute-node"
3
- version = "0.1.8"
3
+ version = "0.1.9"
4
4
  description = "Runtime-agnostic computer node daemon for agents and channels."
5
5
  readme = "README.md"
6
6
  license = "AGPL-3.0-only"
@@ -22,7 +22,8 @@ bcn = "bazaar_compute_node.cli:main"
22
22
  codex = "bazaar_compute_node.contrib.codex.plugin:create_runtime"
23
23
 
24
24
  [project.entry-points."bazaar_compute_node.channels"]
25
- wecom = "bazaar_compute_node.contrib.wecom.plugin:create_channel"
25
+ wecom = "bazaar_compute_node.contrib.wecom.plugin:builder"
26
+ telegram = "bazaar_compute_node.contrib.telegram.plugin:builder"
26
27
 
27
28
  [project.entry-points."bazaar_compute_node.storages"]
28
29
  sqlite = "bazaar_compute_node.contrib.sqlite.plugin:create_storage"
@@ -92,7 +92,7 @@ class NodeApplication:
92
92
  self._attachment_materializer = AttachmentMaterializer(
93
93
  self._workspace_path, self._referenced_attachment_paths
94
94
  )
95
- self.channel: IChannel = factories.channel(
95
+ self.channel: IChannel = factories.channel.build(
96
96
  ChannelContext(
97
97
  attachments=self._attachment_materializer,
98
98
  options=dict(channel_options or {}),
@@ -376,6 +376,8 @@ class CommandDispatcher:
376
376
 
377
377
 
378
378
  def format_message_time(timestamp_ms: int) -> str:
379
- return datetime.fromtimestamp(timestamp_ms / 1000, tz=UTC).strftime(
380
- "%Y-%m-%d %H:%M:%S"
379
+ return (
380
+ datetime.fromtimestamp(timestamp_ms / 1000, tz=UTC)
381
+ .astimezone()
382
+ .strftime("%Y-%m-%d %H:%M:%S")
381
383
  )
@@ -3,8 +3,10 @@ from __future__ import annotations
3
3
  import math
4
4
  import os
5
5
  import tomllib
6
- from dataclasses import dataclass
6
+ from collections.abc import Mapping
7
+ from dataclasses import dataclass, field
7
8
  from pathlib import Path
9
+ from types import MappingProxyType
8
10
 
9
11
  from ..core.paths import resolve_data_dir
10
12
  from ..core.runtime import RuntimeSandboxMode
@@ -28,8 +30,9 @@ class NodeConfiguration:
28
30
  network_access: bool = True
29
31
  runtime_idle_timeout_seconds: float = 0
30
32
  runtime_env_include: tuple[str, ...] = ()
31
- wecom_bot_id: str | None = None
32
- wecom_websocket_url: str | None = None
33
+ channel_options: Mapping[str, Mapping[str, object]] = field(
34
+ default_factory=lambda: MappingProxyType({})
35
+ )
33
36
 
34
37
 
35
38
  class ConfigurationError(ValueError):
@@ -67,9 +70,13 @@ def load_node_configuration(path: Path | None = None) -> NodeConfiguration:
67
70
  channel = payload.get("channel", {})
68
71
  if not isinstance(channel, dict):
69
72
  raise ConfigurationError("[channel] must be a TOML table")
70
- wecom = channel.get("wecom", {})
71
- if not isinstance(wecom, dict):
72
- raise ConfigurationError("[channel.wecom] must be a TOML table")
73
+ channel_options: dict[str, Mapping[str, object]] = {}
74
+ for channel_name, options in channel.items():
75
+ if not channel_name:
76
+ raise ConfigurationError("channel name must be non-empty text")
77
+ if not isinstance(options, dict):
78
+ raise ConfigurationError(f"[channel.{channel_name}] must be a TOML table")
79
+ channel_options[channel_name] = MappingProxyType(dict(options))
73
80
  sandbox_mode = runtime.get("sandbox_mode", RuntimeSandboxMode.WORKSPACE_WRITE.value)
74
81
  if not isinstance(sandbox_mode, str):
75
82
  raise ConfigurationError("runtime.sandbox_mode must be text")
@@ -105,10 +112,7 @@ def load_node_configuration(path: Path | None = None) -> NodeConfiguration:
105
112
  runtime_env_include=_text_list(
106
113
  runtime_env.get("include", []), "runtime.env.include"
107
114
  ),
108
- wecom_bot_id=_optional_text(wecom.get("bot_id"), "channel.wecom.bot_id"),
109
- wecom_websocket_url=_optional_text(
110
- wecom.get("websocket_url"), "channel.wecom.websocket_url"
111
- ),
115
+ channel_options=MappingProxyType(channel_options),
112
116
  )
113
117
 
114
118
 
@@ -6,7 +6,7 @@ from functools import partial
6
6
  from importlib.metadata import EntryPoint, entry_points
7
7
  from typing import Any, cast
8
8
 
9
- from ..core.channel import ChannelContext, IChannel
9
+ from ..core.channel import IChannelBuilder
10
10
  from ..core.observability import IAudit
11
11
  from ..core.runtime import IRuntime, RuntimeCommandContext
12
12
  from ..core.storage import IStorage
@@ -18,7 +18,6 @@ STORAGE_ENTRY_POINT_GROUP = "bazaar_compute_node.storages"
18
18
  AUDIT_ENTRY_POINT_GROUP = "bazaar_compute_node.audits"
19
19
  CONTROL_ENTRY_POINT_GROUP = "bazaar_compute_node.controls"
20
20
 
21
- ChannelFactory = Callable[[ChannelContext], IChannel]
22
21
  RuntimeFactory = Callable[[RuntimeCommandContext], IRuntime]
23
22
  StorageFactory = Callable[[], IStorage]
24
23
  AuditFactory = Callable[[], IAudit]
@@ -26,7 +25,7 @@ AuditFactory = Callable[[], IAudit]
26
25
 
27
26
  @dataclass(frozen=True, slots=True)
28
27
  class AdapterFactories:
29
- channel: ChannelFactory
28
+ channel: IChannelBuilder
30
29
  runtime: RuntimeFactory
31
30
  storage: StorageFactory
32
31
  audit: AuditFactory
@@ -60,10 +59,7 @@ class AdapterRegistry:
60
59
  if storage_options:
61
60
  storage_factory = partial(storage_factory, dict(storage_options))
62
61
  return AdapterFactories(
63
- channel=cast(
64
- ChannelFactory,
65
- self._load(CHANNEL_ENTRY_POINT_GROUP, channel),
66
- ),
62
+ channel=self._load_channel_builder(channel),
67
63
  runtime=cast(
68
64
  RuntimeFactory,
69
65
  self._load(RUNTIME_ENTRY_POINT_GROUP, runtime),
@@ -79,6 +75,27 @@ class AdapterRegistry:
79
75
  ),
80
76
  )
81
77
 
78
+ def _load_channel_builder(self, name: str) -> IChannelBuilder:
79
+ entry_point = self._find(CHANNEL_ENTRY_POINT_GROUP, name)
80
+ if entry_point is None:
81
+ raise ProviderLoadError(
82
+ f"provider '{name}' is not installed for entry point group "
83
+ f"'{CHANNEL_ENTRY_POINT_GROUP}'"
84
+ )
85
+ try:
86
+ builder = entry_point.load()
87
+ except Exception as error:
88
+ raise ProviderLoadError(
89
+ f"failed to load provider '{name}' from "
90
+ f"'{CHANNEL_ENTRY_POINT_GROUP}': {error}"
91
+ ) from error
92
+ if not callable(getattr(builder, "build", None)):
93
+ raise ProviderLoadError(
94
+ f"provider '{name}' from '{CHANNEL_ENTRY_POINT_GROUP}' "
95
+ "does not provide a callable build method"
96
+ )
97
+ return cast(IChannelBuilder, builder)
98
+
82
99
  def _load(self, group: str, name: str) -> Any:
83
100
  entry_point = self._find(group, name)
84
101
  if entry_point is None:
@@ -174,10 +174,7 @@ def _apply_runtime_configuration(
174
174
  args.audit = DEFAULT_AUDIT
175
175
  args.runtime_env_include = configuration.runtime_env_include
176
176
  args.runtime_idle_timeout_seconds = configuration.runtime_idle_timeout_seconds
177
- args.channel_options = {
178
- "bot_id": configuration.wecom_bot_id,
179
- "websocket_url": configuration.wecom_websocket_url,
180
- }
177
+ args.channel_options = dict(configuration.channel_options.get(args.channel, {}))
181
178
 
182
179
 
183
180
  async def _run_node(args: argparse.Namespace, parser: argparse.ArgumentParser) -> int:
@@ -191,7 +188,7 @@ async def _run_node(args: argparse.Namespace, parser: argparse.ArgumentParser) -
191
188
  runtime_sandbox_mode=args.sandbox_mode,
192
189
  runtime_network_access=args.network_access,
193
190
  runtime_idle_timeout_seconds=args.runtime_idle_timeout_seconds,
194
- channel_options=args.channel_options if args.channel == "wecom" else {},
191
+ channel_options=args.channel_options,
195
192
  runtime_environment_include=args.runtime_env_include,
196
193
  )
197
194
  await node.start()
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import json
3
4
  from collections.abc import Mapping
4
5
  from dataclasses import dataclass
5
6
  from typing import cast
@@ -21,6 +22,7 @@ from .protocol import (
21
22
  _COMMAND_METHOD = "item/commandExecution/requestApproval"
22
23
  _FILE_CHANGE_METHOD = "item/fileChange/requestApproval"
23
24
  _PERMISSIONS_METHOD = "item/permissions/requestApproval"
25
+ _MAX_APPROVAL_DESCRIPTION = 4_000
24
26
 
25
27
  _APPROVAL_METHODS = frozenset(
26
28
  {
@@ -90,13 +92,57 @@ def parse_approval_request(
90
92
  _FILE_CHANGE_METHOD: "file_change",
91
93
  _PERMISSIONS_METHOD: "permissions",
92
94
  }[method]
93
- if method == _PERMISSIONS_METHOD and not isinstance(
94
- params.get("permissions"), Mapping
95
- ):
95
+ permissions = params.get("permissions")
96
+ if method == _PERMISSIONS_METHOD and not isinstance(permissions, Mapping):
96
97
  raise AppServerProtocolError(
97
98
  "permissions approval request has no permissions object"
98
99
  )
99
100
 
101
+ description_parts: list[str] = []
102
+ reason = params.get("reason")
103
+ if isinstance(reason, str) and reason.strip():
104
+ description_parts.append(reason.strip())
105
+ if method == _COMMAND_METHOD:
106
+ command = params.get("command")
107
+ if isinstance(command, str) and command.strip():
108
+ description_parts.append(f"Command: {command.strip()}")
109
+ elif (
110
+ isinstance(command, list)
111
+ and command
112
+ and all(isinstance(argument, str) and argument for argument in command)
113
+ ):
114
+ description_parts.append(f"Command: {' '.join(command)}")
115
+ cwd = params.get("cwd")
116
+ if isinstance(cwd, str) and cwd:
117
+ description_parts.append(f"Working directory: {cwd}")
118
+ elif method == _FILE_CHANGE_METHOD:
119
+ grant_root = params.get("grantRoot")
120
+ if isinstance(grant_root, str) and grant_root:
121
+ description_parts.append(f"Grant root: {grant_root}")
122
+ cwd = params.get("cwd")
123
+ if isinstance(cwd, str) and cwd:
124
+ description_parts.append(f"Working directory: {cwd}")
125
+ elif isinstance(permissions, Mapping):
126
+ cwd = params.get("cwd")
127
+ if isinstance(cwd, str) and cwd:
128
+ description_parts.append(f"Working directory: {cwd}")
129
+ try:
130
+ encoded_permissions = json.dumps(
131
+ dict(permissions),
132
+ ensure_ascii=False,
133
+ sort_keys=True,
134
+ separators=(",", ":"),
135
+ )
136
+ except TypeError, ValueError:
137
+ encoded_permissions = None
138
+ if encoded_permissions:
139
+ description_parts.append(f"Permissions: {encoded_permissions}")
140
+
141
+ description: str | None = None
142
+ if description_parts:
143
+ description = "\n".join(description_parts)
144
+ if len(description) > _MAX_APPROVAL_DESCRIPTION:
145
+ description = description[: _MAX_APPROVAL_DESCRIPTION - 1] + "…"
100
146
  metadata: dict[str, object] = {
101
147
  "provider_method": method,
102
148
  "provider_item_id": provider_item_id,
@@ -119,6 +165,7 @@ def parse_approval_request(
119
165
  action=action,
120
166
  created_at_ms=started_at_ms,
121
167
  turn_id=turn_id,
168
+ description=description,
122
169
  metadata=metadata,
123
170
  ),
124
171
  )
@@ -0,0 +1 @@
1
+ """Telegram Bot API Channel adapter."""