bazaar-compute-node 0.1.5__tar.gz → 0.1.7__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.
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/PKG-INFO +10 -4
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/README.md +9 -3
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/pyproject.toml +4 -3
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/pyproject.toml.orig +8 -7
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/application.py +63 -26
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/attachments.py +52 -16
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/command.py +19 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/config.py +22 -4
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/registry.py +9 -4
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/transport.py +16 -10
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/windows_pipe.py +4 -2
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/bcc.py +5 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/cli.py +48 -10
- {bazaar_compute_node-0.1.5/src/bazaar_compute_node/contrib/codex_app_server → bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/codex}/__init__.py +26 -14
- {bazaar_compute_node-0.1.5/src/bazaar_compute_node/contrib/codex_app_server → bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/codex}/approval.py +25 -21
- {bazaar_compute_node-0.1.5/src/bazaar_compute_node/contrib/codex_app_server → bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/codex}/client.py +127 -26
- {bazaar_compute_node-0.1.5/src/bazaar_compute_node/contrib/codex_app_server → bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/codex}/events.py +9 -9
- {bazaar_compute_node-0.1.5/src/bazaar_compute_node/contrib/codex_app_server → bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/codex}/plugin.py +2 -2
- {bazaar_compute_node-0.1.5/src/bazaar_compute_node/contrib/codex_app_server → bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/codex}/process.py +23 -0
- {bazaar_compute_node-0.1.5/src/bazaar_compute_node/contrib/codex_app_server → bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/codex}/protocol.py +2 -2
- {bazaar_compute_node-0.1.5/src/bazaar_compute_node/contrib/codex_app_server → bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/codex}/runtime.py +160 -37
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/codec.py +33 -40
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/database.py +16 -4
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/migrations.py +26 -0
- bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/sqlite/plugin.py +16 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/repository.py +18 -91
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/wecom/channel.py +400 -45
- bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/wecom/outbound.py +193 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/command.py +1 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/instruction.py +3 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/models/__init__.py +2 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/models/entities.py +40 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/command.py +130 -18
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/services.py +8 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/session.py +724 -170
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/turn.py +75 -13
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/paths.py +14 -1
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/runtime.py +36 -7
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/storage.py +0 -13
- bazaar_compute_node-0.1.7/src/bazaar_compute_node/core/timerwheel.py +316 -0
- bazaar_compute_node-0.1.5/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -11
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/LICENSE +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/__init__.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/__init__.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/wrapper.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/__init__.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/wecom/markdown.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/__init__.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/approval.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/audit.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/channel.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/client.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/concurrency.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/correlation.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/lifecycle.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/models/states.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/observability.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
- {bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/outcomes.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bazaar-compute-node
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.7
|
|
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
|
|
@@ -24,6 +24,7 @@ Harness 工具。
|
|
|
24
24
|
- **自由组合**:根据使用场景分别选择 Channel 与 Runtime,并可独立替换和扩展。
|
|
25
25
|
- **持续会话**:每个用户和对话拥有独立上下文,节点重启后仍可继续之前的任务。
|
|
26
26
|
- **真实工作区**:Runtime 可以在独立工作区中分析、创建和修改文件,而不局限于文本回答。
|
|
27
|
+
- **双向附件**:Channel 收到的媒体会进入会话工作区,Runtime 也可以通过 Channel 交付工作区中的文件。
|
|
27
28
|
- **可靠交付**:自动处理长结果的分批发送并记录交付状态,避免任务结果静默丢失。
|
|
28
29
|
- **安全边界**:复用所选 Harness 的权限机制,按任务需要控制文件与网络访问。
|
|
29
30
|
- **跨平台运行**:支持 Linux、macOS 和 Windows,并以后台进程长期运行。
|
|
@@ -57,7 +58,6 @@ bcn 将沿着可组合的 Channel、Runtime 与节点通用能力继续扩展。
|
|
|
57
58
|
|
|
58
59
|
### 节点能力
|
|
59
60
|
|
|
60
|
-
- **附件发送**:让 Agent 可以通过当前 Channel 交付生成的文件;
|
|
61
61
|
- **定时任务**:创建一次性或周期性任务,由节点按计划自动执行并返回结果。
|
|
62
62
|
|
|
63
63
|
## 安装
|
|
@@ -91,7 +91,8 @@ Channel 即可。
|
|
|
91
91
|
### WeCom
|
|
92
92
|
|
|
93
93
|
WeCom Channel 对接企业微信智能机器人,支持单聊、群聊、文本与媒体输入,以及 Markdown
|
|
94
|
-
|
|
94
|
+
长消息分批和附件返回。收到的媒体会以工作区相对路径提供给 Runtime;Runtime 可以把工作区内的
|
|
95
|
+
普通文件作为附件发送,附件路径不能包含符号链接。群聊中需要 @机器人,确保消息能够进入节点。
|
|
95
96
|
|
|
96
97
|
在 `~/.bcn/config.toml` 中配置 Bot ID:
|
|
97
98
|
|
|
@@ -128,9 +129,12 @@ Runtime 即可。
|
|
|
128
129
|
[runtime]
|
|
129
130
|
sandbox_mode = "workspace-write"
|
|
130
131
|
network_access = true
|
|
132
|
+
idle_timeout = 0
|
|
131
133
|
```
|
|
132
134
|
|
|
133
135
|
如需覆盖 Codex 的默认模型或推理强度,可以在 `[runtime]` 中增加 `model` 和 `effort`。
|
|
136
|
+
`idle_timeout` 以秒为单位并支持小数;默认值 `0` 与所有 `<= 0` 的值表示 runtime session
|
|
137
|
+
常驻。设置为正值后,每条新收到的消息都会刷新空闲回收期限。
|
|
134
138
|
除非你明确需要使用运行用户已有的主机权限,否则不要将 `sandbox_mode` 改为
|
|
135
139
|
`danger-full-access`。
|
|
136
140
|
|
|
@@ -167,7 +171,9 @@ uvx --from 'git+https://github.com/yuchanns/bazaar-compute-node@main' bcn restar
|
|
|
167
171
|
uvx --from 'git+https://github.com/yuchanns/bazaar-compute-node@main' bcn --help
|
|
168
172
|
```
|
|
169
173
|
|
|
170
|
-
节点数据默认保存在 `~/.bcn
|
|
174
|
+
节点数据默认保存在 `~/.bcn`。可通过 `BCN_DATA_NAME` 指定 HOME 下的另一数据目录名称,
|
|
175
|
+
例如 `BCN_DATA_NAME=.bcn-test`;该值必须是单个目录名。停止节点会优雅关闭后台进程,
|
|
176
|
+
不会删除既有会话、工作区或消息记录。
|
|
171
177
|
|
|
172
178
|
## 许可证
|
|
173
179
|
|
|
@@ -11,6 +11,7 @@ Harness 工具。
|
|
|
11
11
|
- **自由组合**:根据使用场景分别选择 Channel 与 Runtime,并可独立替换和扩展。
|
|
12
12
|
- **持续会话**:每个用户和对话拥有独立上下文,节点重启后仍可继续之前的任务。
|
|
13
13
|
- **真实工作区**:Runtime 可以在独立工作区中分析、创建和修改文件,而不局限于文本回答。
|
|
14
|
+
- **双向附件**:Channel 收到的媒体会进入会话工作区,Runtime 也可以通过 Channel 交付工作区中的文件。
|
|
14
15
|
- **可靠交付**:自动处理长结果的分批发送并记录交付状态,避免任务结果静默丢失。
|
|
15
16
|
- **安全边界**:复用所选 Harness 的权限机制,按任务需要控制文件与网络访问。
|
|
16
17
|
- **跨平台运行**:支持 Linux、macOS 和 Windows,并以后台进程长期运行。
|
|
@@ -44,7 +45,6 @@ bcn 将沿着可组合的 Channel、Runtime 与节点通用能力继续扩展。
|
|
|
44
45
|
|
|
45
46
|
### 节点能力
|
|
46
47
|
|
|
47
|
-
- **附件发送**:让 Agent 可以通过当前 Channel 交付生成的文件;
|
|
48
48
|
- **定时任务**:创建一次性或周期性任务,由节点按计划自动执行并返回结果。
|
|
49
49
|
|
|
50
50
|
## 安装
|
|
@@ -78,7 +78,8 @@ Channel 即可。
|
|
|
78
78
|
### WeCom
|
|
79
79
|
|
|
80
80
|
WeCom Channel 对接企业微信智能机器人,支持单聊、群聊、文本与媒体输入,以及 Markdown
|
|
81
|
-
|
|
81
|
+
长消息分批和附件返回。收到的媒体会以工作区相对路径提供给 Runtime;Runtime 可以把工作区内的
|
|
82
|
+
普通文件作为附件发送,附件路径不能包含符号链接。群聊中需要 @机器人,确保消息能够进入节点。
|
|
82
83
|
|
|
83
84
|
在 `~/.bcn/config.toml` 中配置 Bot ID:
|
|
84
85
|
|
|
@@ -115,9 +116,12 @@ Runtime 即可。
|
|
|
115
116
|
[runtime]
|
|
116
117
|
sandbox_mode = "workspace-write"
|
|
117
118
|
network_access = true
|
|
119
|
+
idle_timeout = 0
|
|
118
120
|
```
|
|
119
121
|
|
|
120
122
|
如需覆盖 Codex 的默认模型或推理强度,可以在 `[runtime]` 中增加 `model` 和 `effort`。
|
|
123
|
+
`idle_timeout` 以秒为单位并支持小数;默认值 `0` 与所有 `<= 0` 的值表示 runtime session
|
|
124
|
+
常驻。设置为正值后,每条新收到的消息都会刷新空闲回收期限。
|
|
121
125
|
除非你明确需要使用运行用户已有的主机权限,否则不要将 `sandbox_mode` 改为
|
|
122
126
|
`danger-full-access`。
|
|
123
127
|
|
|
@@ -154,7 +158,9 @@ uvx --from 'git+https://github.com/yuchanns/bazaar-compute-node@main' bcn restar
|
|
|
154
158
|
uvx --from 'git+https://github.com/yuchanns/bazaar-compute-node@main' bcn --help
|
|
155
159
|
```
|
|
156
160
|
|
|
157
|
-
节点数据默认保存在 `~/.bcn
|
|
161
|
+
节点数据默认保存在 `~/.bcn`。可通过 `BCN_DATA_NAME` 指定 HOME 下的另一数据目录名称,
|
|
162
|
+
例如 `BCN_DATA_NAME=.bcn-test`;该值必须是单个目录名。停止节点会优雅关闭后台进程,
|
|
163
|
+
不会删除既有会话、工作区或消息记录。
|
|
158
164
|
|
|
159
165
|
## 许可证
|
|
160
166
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bazaar-compute-node"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.7"
|
|
4
4
|
description = "Runtime-agnostic computer node daemon for agents and channels."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "AGPL-3.0-only"
|
|
@@ -19,7 +19,7 @@ Repository = "https://github.com/yuchanns/bazaar-compute-node"
|
|
|
19
19
|
bcn = "bazaar_compute_node.cli:main"
|
|
20
20
|
|
|
21
21
|
[project.entry-points."bazaar_compute_node.runtimes"]
|
|
22
|
-
codex = "bazaar_compute_node.contrib.
|
|
22
|
+
codex = "bazaar_compute_node.contrib.codex.plugin:create_runtime"
|
|
23
23
|
|
|
24
24
|
[project.entry-points."bazaar_compute_node.channels"]
|
|
25
25
|
wecom = "bazaar_compute_node.contrib.wecom.plugin:create_channel"
|
|
@@ -43,7 +43,8 @@ path = "tests/support"
|
|
|
43
43
|
editable = true
|
|
44
44
|
|
|
45
45
|
[tool.pytest.ini_options]
|
|
46
|
-
|
|
46
|
+
addopts = "-m 'not e2e'"
|
|
47
|
+
markers = ["e2e: requires a real external provider"]
|
|
47
48
|
|
|
48
49
|
[build-system]
|
|
49
50
|
requires = ["uv_build>=0.12.2,<0.13.0"]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bazaar-compute-node"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.7"
|
|
4
4
|
description = "Runtime-agnostic computer node daemon for agents and channels."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "AGPL-3.0-only"
|
|
@@ -19,7 +19,7 @@ Repository = "https://github.com/yuchanns/bazaar-compute-node"
|
|
|
19
19
|
bcn = "bazaar_compute_node.cli:main"
|
|
20
20
|
|
|
21
21
|
[project.entry-points."bazaar_compute_node.runtimes"]
|
|
22
|
-
codex = "bazaar_compute_node.contrib.
|
|
22
|
+
codex = "bazaar_compute_node.contrib.codex.plugin:create_runtime"
|
|
23
23
|
|
|
24
24
|
[project.entry-points."bazaar_compute_node.channels"]
|
|
25
25
|
wecom = "bazaar_compute_node.contrib.wecom.plugin:create_channel"
|
|
@@ -41,11 +41,12 @@ dev = [
|
|
|
41
41
|
[tool.uv.sources]
|
|
42
42
|
bcn-test-support = { path = "tests/support", editable = true }
|
|
43
43
|
|
|
44
|
-
[build-system]
|
|
45
|
-
requires = ["uv_build>=0.12.2,<0.13.0"]
|
|
46
|
-
build-backend = "uv_build"
|
|
47
|
-
|
|
48
44
|
[tool.pytest.ini_options]
|
|
45
|
+
addopts = "-m 'not e2e'"
|
|
49
46
|
markers = [
|
|
50
|
-
"
|
|
47
|
+
"e2e: requires a real external provider",
|
|
51
48
|
]
|
|
49
|
+
|
|
50
|
+
[build-system]
|
|
51
|
+
requires = ["uv_build>=0.12.2,<0.13.0"]
|
|
52
|
+
build-backend = "uv_build"
|
{bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/application.py
RENAMED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import hmac
|
|
5
|
+
import math
|
|
5
6
|
import os
|
|
6
7
|
import re
|
|
7
8
|
import secrets
|
|
@@ -17,6 +18,7 @@ from ..core.orchestration import SessionOrchestrator
|
|
|
17
18
|
from ..core.paths import resolve_data_dir, resolve_workspace_dir
|
|
18
19
|
from ..core.runtime import IRuntime, RuntimeCommandContext, RuntimeSandboxMode
|
|
19
20
|
from ..core.storage import IStorage, NodeIdentity
|
|
21
|
+
from ..core.timerwheel import TimerWheel
|
|
20
22
|
from .attachments import AttachmentMaterializer
|
|
21
23
|
from .command import (
|
|
22
24
|
CommandDispatcher,
|
|
@@ -64,6 +66,7 @@ class NodeApplication:
|
|
|
64
66
|
runtime_options: Mapping[str, str] | None = None,
|
|
65
67
|
runtime_sandbox_mode: RuntimeSandboxMode = RuntimeSandboxMode.WORKSPACE_WRITE,
|
|
66
68
|
runtime_network_access: bool = True,
|
|
69
|
+
runtime_idle_timeout_seconds: float = 0,
|
|
67
70
|
channel_options: Mapping[str, object] | None = None,
|
|
68
71
|
runtime_environment_include: Sequence[str] = (),
|
|
69
72
|
timeout_budget: TimeoutBudget | None = None,
|
|
@@ -81,8 +84,7 @@ class NodeApplication:
|
|
|
81
84
|
self.command_log: list[CommandRecord] = []
|
|
82
85
|
self._wrapper_path: Path | None = None
|
|
83
86
|
self._identity: NodeIdentity | None = None
|
|
84
|
-
self._session_capabilities: dict[str, str] = {}
|
|
85
|
-
self._runtime_session_ids: dict[str, str] = {}
|
|
87
|
+
self._session_capabilities: dict[str, tuple[str, str]] = {}
|
|
86
88
|
self._started = False
|
|
87
89
|
self._stopped = asyncio.Event()
|
|
88
90
|
self._runtime_environment_include = tuple(runtime_environment_include)
|
|
@@ -106,6 +108,23 @@ class NodeApplication:
|
|
|
106
108
|
startup_timeout_seconds=self.timeout_budget.startup_seconds,
|
|
107
109
|
)
|
|
108
110
|
self.runtime: IRuntime = factories.runtime(self._runtime_context)
|
|
111
|
+
if (
|
|
112
|
+
isinstance(runtime_idle_timeout_seconds, bool)
|
|
113
|
+
or not isinstance(runtime_idle_timeout_seconds, int | float)
|
|
114
|
+
or not math.isfinite(runtime_idle_timeout_seconds)
|
|
115
|
+
):
|
|
116
|
+
raise ValueError("runtime_idle_timeout_seconds must be a finite number")
|
|
117
|
+
self.timer_wheel = TimerWheel()
|
|
118
|
+
if (
|
|
119
|
+
runtime_idle_timeout_seconds > 0
|
|
120
|
+
and runtime_idle_timeout_seconds * 1_000 > self.timer_wheel.maximum_delay_ms
|
|
121
|
+
):
|
|
122
|
+
raise ValueError("runtime_idle_timeout_seconds exceeds the timer horizon")
|
|
123
|
+
runtime_idle_timeout_ms = (
|
|
124
|
+
math.ceil(runtime_idle_timeout_seconds * 1_000)
|
|
125
|
+
if runtime_idle_timeout_seconds > 0
|
|
126
|
+
else 0
|
|
127
|
+
)
|
|
109
128
|
self.orchestrator = SessionOrchestrator(
|
|
110
129
|
node_id=node_id,
|
|
111
130
|
workspace_id=workspace_id,
|
|
@@ -114,6 +133,9 @@ class NodeApplication:
|
|
|
114
133
|
storage=self.storage,
|
|
115
134
|
audit=self.audit,
|
|
116
135
|
timeout_budget=self.timeout_budget,
|
|
136
|
+
timer_wheel=self.timer_wheel,
|
|
137
|
+
runtime_idle_timeout_ms=runtime_idle_timeout_ms,
|
|
138
|
+
workspace=self._workspace_path,
|
|
117
139
|
on_node_initialized=self._ensure_workspace,
|
|
118
140
|
)
|
|
119
141
|
self.command_service = self.orchestrator.command_service
|
|
@@ -139,17 +161,23 @@ class NodeApplication:
|
|
|
139
161
|
async def start(self) -> None:
|
|
140
162
|
if self._started:
|
|
141
163
|
return
|
|
142
|
-
self.data_dir.mkdir
|
|
164
|
+
await asyncio.to_thread(self.data_dir.mkdir, parents=True, exist_ok=True)
|
|
143
165
|
if os.name != "nt":
|
|
144
|
-
self.data_dir.chmod
|
|
145
|
-
self._wrapper_path =
|
|
166
|
+
await asyncio.to_thread(self.data_dir.chmod, 0o700)
|
|
167
|
+
self._wrapper_path = await asyncio.to_thread(
|
|
168
|
+
install_bcc_wrapper, self.data_dir / "bin"
|
|
169
|
+
)
|
|
146
170
|
try:
|
|
171
|
+
await self.timer_wheel.start()
|
|
147
172
|
await self.command_server.start()
|
|
148
173
|
await self.orchestrator.start(
|
|
149
174
|
timeout=self.timeout_budget.startup_seconds,
|
|
150
175
|
)
|
|
151
176
|
except BaseException:
|
|
152
|
-
|
|
177
|
+
try:
|
|
178
|
+
await self.stop()
|
|
179
|
+
finally:
|
|
180
|
+
await self.timer_wheel.close()
|
|
153
181
|
raise
|
|
154
182
|
self._started = True
|
|
155
183
|
self._stopped.clear()
|
|
@@ -165,7 +193,10 @@ class NodeApplication:
|
|
|
165
193
|
try:
|
|
166
194
|
await self.command_server.stop()
|
|
167
195
|
finally:
|
|
168
|
-
|
|
196
|
+
try:
|
|
197
|
+
await self._cleanup_bcc_wrapper()
|
|
198
|
+
finally:
|
|
199
|
+
await self.timer_wheel.close()
|
|
169
200
|
return
|
|
170
201
|
self._started = False
|
|
171
202
|
self.command_dispatcher.stop_accepting()
|
|
@@ -183,15 +214,18 @@ class NodeApplication:
|
|
|
183
214
|
await self.command_server.stop()
|
|
184
215
|
finally:
|
|
185
216
|
try:
|
|
186
|
-
self._cleanup_bcc_wrapper()
|
|
217
|
+
await self._cleanup_bcc_wrapper()
|
|
187
218
|
finally:
|
|
188
|
-
|
|
219
|
+
try:
|
|
220
|
+
await self.timer_wheel.close()
|
|
221
|
+
finally:
|
|
222
|
+
self._stopped.set()
|
|
189
223
|
|
|
190
|
-
def _cleanup_bcc_wrapper(self) -> None:
|
|
224
|
+
async def _cleanup_bcc_wrapper(self) -> None:
|
|
191
225
|
wrapper_path = self._wrapper_path
|
|
192
226
|
if wrapper_path is None:
|
|
193
227
|
return
|
|
194
|
-
remove_bcc_wrapper
|
|
228
|
+
await asyncio.to_thread(remove_bcc_wrapper, wrapper_path)
|
|
195
229
|
self._wrapper_path = None
|
|
196
230
|
|
|
197
231
|
async def wait(self) -> None:
|
|
@@ -275,18 +309,19 @@ class NodeApplication:
|
|
|
275
309
|
"SESSION_NOT_FOUND",
|
|
276
310
|
f"unknown bcn session: {session_id}",
|
|
277
311
|
)
|
|
278
|
-
|
|
279
|
-
if runtime_session is None or runtime_session_id !=
|
|
312
|
+
runtime_session = self.orchestrator.runtime_session(session_id)
|
|
313
|
+
if runtime_session is None or runtime_session_id != runtime_session.id:
|
|
280
314
|
raise CommandDispatchError(
|
|
281
315
|
"SESSION_BINDING_FAILED",
|
|
282
316
|
"runtime session binding is invalid",
|
|
283
317
|
)
|
|
284
|
-
|
|
318
|
+
capability_binding = self._session_capabilities.get(session_id)
|
|
285
319
|
if (
|
|
286
|
-
|
|
320
|
+
capability_binding is None
|
|
321
|
+
or capability_binding[0] != runtime_session.id
|
|
287
322
|
or not isinstance(session_capability, str)
|
|
288
323
|
or not hmac.compare_digest(
|
|
289
|
-
session_capability.encode(),
|
|
324
|
+
session_capability.encode(), capability_binding[1].encode()
|
|
290
325
|
)
|
|
291
326
|
):
|
|
292
327
|
raise CommandDispatchError(
|
|
@@ -295,7 +330,9 @@ class NodeApplication:
|
|
|
295
330
|
)
|
|
296
331
|
|
|
297
332
|
def _runtime_environment(self, session: RuntimeSession) -> Mapping[str, str]:
|
|
298
|
-
self.
|
|
333
|
+
runtime_session = self.orchestrator.runtime_session(session.bcn_session_id)
|
|
334
|
+
if runtime_session is None or runtime_session.id != session.id:
|
|
335
|
+
raise RuntimeError("runtime session is not the current live binding")
|
|
299
336
|
return self._build_command_environment(
|
|
300
337
|
session.bcn_session_id,
|
|
301
338
|
session.id,
|
|
@@ -313,10 +350,11 @@ class NodeApplication:
|
|
|
313
350
|
wrapper_path = self._wrapper_path
|
|
314
351
|
if wrapper_path is None:
|
|
315
352
|
raise RuntimeError("bcc wrapper is not installed")
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
secrets.token_urlsafe(32)
|
|
319
|
-
|
|
353
|
+
capability_binding = self._session_capabilities.get(session_id)
|
|
354
|
+
if capability_binding is None or capability_binding[0] != runtime_session_id:
|
|
355
|
+
capability_binding = (runtime_session_id, secrets.token_urlsafe(32))
|
|
356
|
+
self._session_capabilities[session_id] = capability_binding
|
|
357
|
+
session_capability = capability_binding[1]
|
|
320
358
|
wrapper_directory = str(wrapper_path.parent)
|
|
321
359
|
allowed = set(_PLATFORM_ENVIRONMENT)
|
|
322
360
|
for name in self.runtime.environment_variable_names():
|
|
@@ -365,13 +403,12 @@ class NodeApplication:
|
|
|
365
403
|
if wrapper_path is None:
|
|
366
404
|
raise RuntimeError("bcc wrapper is not installed")
|
|
367
405
|
self.command_log.append((session_id, tuple(arguments)))
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
)
|
|
406
|
+
runtime_session = self.orchestrator.runtime_session(session_id)
|
|
407
|
+
if runtime_session is None:
|
|
408
|
+
raise RuntimeError("runtime session is not live")
|
|
372
409
|
environment = self._build_command_environment(
|
|
373
410
|
session_id,
|
|
374
|
-
|
|
411
|
+
runtime_session.id,
|
|
375
412
|
)
|
|
376
413
|
process = await asyncio.create_subprocess_exec(
|
|
377
414
|
str(wrapper_path),
|
{bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/attachments.py
RENAMED
|
@@ -33,13 +33,24 @@ class AttachmentMaterializer:
|
|
|
33
33
|
root = self._workspace() / "attachments"
|
|
34
34
|
staging = root / ".staging"
|
|
35
35
|
await asyncio.to_thread(staging.mkdir, parents=True, exist_ok=True, mode=0o700)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
staged_files = await asyncio.to_thread(
|
|
37
|
+
lambda: tuple(
|
|
38
|
+
path
|
|
39
|
+
for path in staging.iterdir()
|
|
40
|
+
if path.is_file() and not path.is_symlink()
|
|
41
|
+
)
|
|
42
|
+
)
|
|
43
|
+
for path in staged_files:
|
|
44
|
+
await asyncio.to_thread(path.unlink)
|
|
39
45
|
referenced = await self._referenced_paths()
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
46
|
+
attachment_directories = await asyncio.to_thread(
|
|
47
|
+
lambda: tuple(
|
|
48
|
+
path
|
|
49
|
+
for path in root.iterdir()
|
|
50
|
+
if path != staging and path.is_dir() and not path.is_symlink()
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
for path in attachment_directories:
|
|
43
54
|
try:
|
|
44
55
|
if UUID(path.name).version != 7:
|
|
45
56
|
continue
|
|
@@ -61,10 +72,7 @@ class AttachmentMaterializer:
|
|
|
61
72
|
if not name or not kind:
|
|
62
73
|
raise ValueError("attachment name and kind must be non-empty")
|
|
63
74
|
attachment_id = str(uuid7())
|
|
64
|
-
suffix =
|
|
65
|
-
if not _SAFE_SUFFIX.fullmatch(suffix):
|
|
66
|
-
guessed = mimetypes.guess_extension(media_type or "") or ".bin"
|
|
67
|
-
suffix = guessed if _SAFE_SUFFIX.fullmatch(guessed) else ".bin"
|
|
75
|
+
suffix = await asyncio.to_thread(_attachment_suffix, name, media_type)
|
|
68
76
|
relative = PurePosixPath(
|
|
69
77
|
"attachments", attachment_id, f"content{suffix.lower()}"
|
|
70
78
|
)
|
|
@@ -78,8 +86,15 @@ class AttachmentMaterializer:
|
|
|
78
86
|
)
|
|
79
87
|
current_size = await asyncio.to_thread(self._stored_size, root)
|
|
80
88
|
size = 0
|
|
89
|
+
descriptor: int | None = None
|
|
81
90
|
try:
|
|
82
|
-
|
|
91
|
+
descriptor = await asyncio.to_thread(
|
|
92
|
+
os.open,
|
|
93
|
+
temporary,
|
|
94
|
+
os.O_WRONLY | os.O_CREAT | os.O_EXCL,
|
|
95
|
+
0o600,
|
|
96
|
+
)
|
|
97
|
+
try:
|
|
83
98
|
if isinstance(source, bytes):
|
|
84
99
|
size = len(source)
|
|
85
100
|
if size > self._max_file_bytes:
|
|
@@ -88,7 +103,7 @@ class AttachmentMaterializer:
|
|
|
88
103
|
)
|
|
89
104
|
if current_size + size > self._max_workspace_bytes:
|
|
90
105
|
raise ValueError("attachment workspace quota exceeded")
|
|
91
|
-
await asyncio.to_thread(
|
|
106
|
+
await asyncio.to_thread(_write_all, descriptor, source)
|
|
92
107
|
else:
|
|
93
108
|
async for chunk in source:
|
|
94
109
|
if not isinstance(chunk, bytes):
|
|
@@ -100,9 +115,11 @@ class AttachmentMaterializer:
|
|
|
100
115
|
)
|
|
101
116
|
if current_size + size > self._max_workspace_bytes:
|
|
102
117
|
raise ValueError("attachment workspace quota exceeded")
|
|
103
|
-
await asyncio.to_thread(
|
|
104
|
-
await asyncio.to_thread(
|
|
105
|
-
|
|
118
|
+
await asyncio.to_thread(_write_all, descriptor, chunk)
|
|
119
|
+
await asyncio.to_thread(os.fsync, descriptor)
|
|
120
|
+
finally:
|
|
121
|
+
await asyncio.to_thread(os.close, descriptor)
|
|
122
|
+
descriptor = None
|
|
106
123
|
await asyncio.to_thread(
|
|
107
124
|
destination.parent.mkdir, parents=True, mode=0o700
|
|
108
125
|
)
|
|
@@ -110,7 +127,9 @@ class AttachmentMaterializer:
|
|
|
110
127
|
if os.name != "nt":
|
|
111
128
|
await asyncio.to_thread(destination.chmod, 0o600)
|
|
112
129
|
except BaseException:
|
|
113
|
-
if
|
|
130
|
+
if descriptor is not None:
|
|
131
|
+
await asyncio.to_thread(os.close, descriptor)
|
|
132
|
+
if await asyncio.to_thread(temporary.exists):
|
|
114
133
|
await asyncio.to_thread(temporary.unlink)
|
|
115
134
|
raise
|
|
116
135
|
return InboundAttachment(
|
|
@@ -151,4 +170,21 @@ class AttachmentMaterializer:
|
|
|
151
170
|
)
|
|
152
171
|
|
|
153
172
|
|
|
173
|
+
def _write_all(descriptor: int, payload: bytes) -> None:
|
|
174
|
+
view = memoryview(payload)
|
|
175
|
+
while view:
|
|
176
|
+
written = os.write(descriptor, view)
|
|
177
|
+
if written == 0:
|
|
178
|
+
raise OSError("attachment write made no progress")
|
|
179
|
+
view = view[written:]
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
def _attachment_suffix(name: str, media_type: str | None) -> str:
|
|
183
|
+
suffix = Path(name).suffix
|
|
184
|
+
if _SAFE_SUFFIX.fullmatch(suffix):
|
|
185
|
+
return suffix
|
|
186
|
+
guessed = mimetypes.guess_extension(media_type or "") or ".bin"
|
|
187
|
+
return guessed if _SAFE_SUFFIX.fullmatch(guessed) else ".bin"
|
|
188
|
+
|
|
189
|
+
|
|
154
190
|
__all__ = ["AttachmentMaterializer"]
|
{bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/command.py
RENAMED
|
@@ -55,6 +55,16 @@ def serialize_outbound(message: OutboundMessage) -> dict[str, object]:
|
|
|
55
55
|
"target": message.target,
|
|
56
56
|
"reply_to_message_id": message.reply_to_message_id,
|
|
57
57
|
"body": message.body,
|
|
58
|
+
"attachments": [
|
|
59
|
+
{
|
|
60
|
+
"name": attachment.name,
|
|
61
|
+
"relative_path": attachment.relative_path,
|
|
62
|
+
"media_type": attachment.media_type,
|
|
63
|
+
"size_bytes": attachment.size_bytes,
|
|
64
|
+
"sha256": attachment.sha256,
|
|
65
|
+
}
|
|
66
|
+
for attachment in message.attachments
|
|
67
|
+
],
|
|
58
68
|
"state": message.state.value,
|
|
59
69
|
"fresh_check_state": message.fresh_check_state.value,
|
|
60
70
|
"created_at_ms": message.created_at_ms,
|
|
@@ -284,6 +294,7 @@ class CommandDispatcher:
|
|
|
284
294
|
target = request.get("target")
|
|
285
295
|
body = request.get("body")
|
|
286
296
|
command_id = request.get("command_id")
|
|
297
|
+
attachment_paths = request.get("attachment_paths", [])
|
|
287
298
|
reply_to_message_id = request.get("reply_to_message_id")
|
|
288
299
|
created_at_ms = request.get("created_at_ms", time_ns() // 1_000_000)
|
|
289
300
|
if not isinstance(target, str) or not target:
|
|
@@ -296,6 +307,13 @@ class CommandDispatcher:
|
|
|
296
307
|
raise CommandDispatchError(
|
|
297
308
|
"COMMAND_ID_REQUIRED", "command_id must be a non-empty string"
|
|
298
309
|
)
|
|
310
|
+
if not isinstance(attachment_paths, list) or not all(
|
|
311
|
+
isinstance(path, str) and path for path in attachment_paths
|
|
312
|
+
):
|
|
313
|
+
raise CommandDispatchError(
|
|
314
|
+
"INVALID_ATTACHMENTS",
|
|
315
|
+
"attachment_paths must be a list of non-empty strings",
|
|
316
|
+
)
|
|
299
317
|
if reply_to_message_id is not None and (
|
|
300
318
|
not isinstance(reply_to_message_id, str) or not reply_to_message_id
|
|
301
319
|
):
|
|
@@ -317,6 +335,7 @@ class CommandDispatcher:
|
|
|
317
335
|
target=target,
|
|
318
336
|
body=body,
|
|
319
337
|
created_at_ms=created_at_ms,
|
|
338
|
+
attachment_paths=tuple(attachment_paths),
|
|
320
339
|
reply_to_message_id=reply_to_message_id,
|
|
321
340
|
)
|
|
322
341
|
return {
|
{bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/config.py
RENAMED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import math
|
|
4
|
+
import os
|
|
3
5
|
import tomllib
|
|
4
6
|
from dataclasses import dataclass
|
|
5
7
|
from pathlib import Path
|
|
@@ -19,10 +21,12 @@ class NodeConfiguration:
|
|
|
19
21
|
storage: str | None = None
|
|
20
22
|
audit: str | None = None
|
|
21
23
|
endpoint: str | None = None
|
|
24
|
+
database_name: str | None = None
|
|
22
25
|
model: str | None = None
|
|
23
26
|
effort: str | None = None
|
|
24
27
|
sandbox_mode: RuntimeSandboxMode = RuntimeSandboxMode.WORKSPACE_WRITE
|
|
25
28
|
network_access: bool = True
|
|
29
|
+
runtime_idle_timeout_seconds: float = 0
|
|
26
30
|
runtime_env_include: tuple[str, ...] = ()
|
|
27
31
|
wecom_bot_id: str | None = None
|
|
28
32
|
wecom_websocket_url: str | None = None
|
|
@@ -36,13 +40,18 @@ def resolve_config_path() -> Path:
|
|
|
36
40
|
return resolve_data_dir() / CONFIG_FILENAME
|
|
37
41
|
|
|
38
42
|
|
|
39
|
-
def load_node_configuration() -> NodeConfiguration:
|
|
40
|
-
path = resolve_config_path()
|
|
43
|
+
def load_node_configuration(path: Path | None = None) -> NodeConfiguration:
|
|
44
|
+
path = path or resolve_config_path()
|
|
41
45
|
try:
|
|
46
|
+
path.parent.mkdir(parents=True, exist_ok=True, mode=0o700)
|
|
47
|
+
try:
|
|
48
|
+
descriptor = os.open(path, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
49
|
+
except FileExistsError:
|
|
50
|
+
pass
|
|
51
|
+
else:
|
|
52
|
+
os.close(descriptor)
|
|
42
53
|
with path.open("rb") as config_file:
|
|
43
54
|
payload = tomllib.load(config_file)
|
|
44
|
-
except FileNotFoundError:
|
|
45
|
-
return NodeConfiguration()
|
|
46
55
|
except (OSError, UnicodeDecodeError, tomllib.TOMLDecodeError) as error:
|
|
47
56
|
raise ConfigurationError(f"cannot read {path}: {error}") from error
|
|
48
57
|
|
|
@@ -74,16 +83,25 @@ def load_node_configuration() -> NodeConfiguration:
|
|
|
74
83
|
network_access = runtime.get("network_access", True)
|
|
75
84
|
if not isinstance(network_access, bool):
|
|
76
85
|
raise ConfigurationError("runtime.network_access must be a boolean")
|
|
86
|
+
idle_timeout = runtime.get("idle_timeout", 0)
|
|
87
|
+
if (
|
|
88
|
+
isinstance(idle_timeout, bool)
|
|
89
|
+
or not isinstance(idle_timeout, int | float)
|
|
90
|
+
or not math.isfinite(idle_timeout)
|
|
91
|
+
):
|
|
92
|
+
raise ConfigurationError("runtime.idle_timeout must be a finite number")
|
|
77
93
|
return NodeConfiguration(
|
|
78
94
|
channel=_optional_text(node.get("channel"), "node.channel"),
|
|
79
95
|
runtime=_optional_text(node.get("runtime"), "node.runtime"),
|
|
80
96
|
storage=_optional_text(node.get("storage"), "node.storage"),
|
|
81
97
|
audit=_optional_text(node.get("audit"), "node.audit"),
|
|
82
98
|
endpoint=_optional_text(node.get("endpoint"), "node.endpoint"),
|
|
99
|
+
database_name=_optional_text(node.get("database_name"), "node.database_name"),
|
|
83
100
|
model=_optional_text(runtime.get("model"), "runtime.model"),
|
|
84
101
|
effort=_optional_text(runtime.get("effort"), "runtime.effort"),
|
|
85
102
|
sandbox_mode=parsed_sandbox_mode,
|
|
86
103
|
network_access=network_access,
|
|
104
|
+
runtime_idle_timeout_seconds=float(idle_timeout),
|
|
87
105
|
runtime_env_include=_text_list(
|
|
88
106
|
runtime_env.get("include", []), "runtime.env.include"
|
|
89
107
|
),
|
{bazaar_compute_node-0.1.5 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/registry.py
RENAMED
|
@@ -2,6 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from collections.abc import Callable, Mapping
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
from functools import partial
|
|
5
6
|
from importlib.metadata import EntryPoint, entry_points
|
|
6
7
|
from typing import Any, cast
|
|
7
8
|
|
|
@@ -46,11 +47,18 @@ class AdapterRegistry:
|
|
|
46
47
|
runtime: str,
|
|
47
48
|
storage: str = "sqlite",
|
|
48
49
|
audit: str = "logging",
|
|
50
|
+
storage_options: Mapping[str, object] | None = None,
|
|
49
51
|
) -> AdapterFactories:
|
|
50
52
|
control = self._load_optional(
|
|
51
53
|
CONTROL_ENTRY_POINT_GROUP,
|
|
52
54
|
f"{channel}+{runtime}+{storage}",
|
|
53
55
|
)
|
|
56
|
+
storage_factory = cast(
|
|
57
|
+
Callable[[Mapping[str, object]], IStorage] | StorageFactory,
|
|
58
|
+
self._load(STORAGE_ENTRY_POINT_GROUP, storage),
|
|
59
|
+
)
|
|
60
|
+
if storage_options:
|
|
61
|
+
storage_factory = partial(storage_factory, dict(storage_options))
|
|
54
62
|
return AdapterFactories(
|
|
55
63
|
channel=cast(
|
|
56
64
|
ChannelFactory,
|
|
@@ -60,10 +68,7 @@ class AdapterRegistry:
|
|
|
60
68
|
RuntimeFactory,
|
|
61
69
|
self._load(RUNTIME_ENTRY_POINT_GROUP, runtime),
|
|
62
70
|
),
|
|
63
|
-
storage=cast(
|
|
64
|
-
StorageFactory,
|
|
65
|
-
self._load(STORAGE_ENTRY_POINT_GROUP, storage),
|
|
66
|
-
),
|
|
71
|
+
storage=cast(StorageFactory, storage_factory),
|
|
67
72
|
audit=cast(
|
|
68
73
|
AuditFactory,
|
|
69
74
|
self._load(AUDIT_ENTRY_POINT_GROUP, audit),
|