bazaar-compute-node 0.1.6__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.6 → bazaar_compute_node-0.1.7}/PKG-INFO +7 -3
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/README.md +6 -2
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/pyproject.toml +2 -2
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/pyproject.toml.orig +2 -2
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/application.py +54 -20
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/config.py +10 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/cli.py +2 -0
- {bazaar_compute_node-0.1.6/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.6/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.6/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.6/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.6/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.6/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.6/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.6/src/bazaar_compute_node/contrib/codex_app_server → bazaar_compute_node-0.1.7/src/bazaar_compute_node/contrib/codex}/runtime.py +159 -36
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/codec.py +0 -40
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/migrations.py +14 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/repository.py +0 -90
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/services.py +8 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/session.py +721 -170
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/turn.py +75 -13
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/runtime.py +36 -7
- {bazaar_compute_node-0.1.6 → 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.6 → bazaar_compute_node-0.1.7}/LICENSE +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/attachments.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/command.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/registry.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/transport.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/wrapper.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/bcc.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/sqlite/plugin.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/wecom/channel.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/wecom/markdown.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/approval.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/audit.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/channel.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/client.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/command.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/concurrency.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/correlation.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/instruction.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/lifecycle.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/models/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/models/entities.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/models/states.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/observability.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/orchestration/command.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/outcomes.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/core/paths.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
|
|
|
@@ -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
|
|
|
@@ -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"
|
|
@@ -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"
|
{bazaar_compute_node-0.1.6 → 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,8 @@ 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,
|
|
117
138
|
workspace=self._workspace_path,
|
|
118
139
|
on_node_initialized=self._ensure_workspace,
|
|
119
140
|
)
|
|
@@ -147,12 +168,16 @@ class NodeApplication:
|
|
|
147
168
|
install_bcc_wrapper, self.data_dir / "bin"
|
|
148
169
|
)
|
|
149
170
|
try:
|
|
171
|
+
await self.timer_wheel.start()
|
|
150
172
|
await self.command_server.start()
|
|
151
173
|
await self.orchestrator.start(
|
|
152
174
|
timeout=self.timeout_budget.startup_seconds,
|
|
153
175
|
)
|
|
154
176
|
except BaseException:
|
|
155
|
-
|
|
177
|
+
try:
|
|
178
|
+
await self.stop()
|
|
179
|
+
finally:
|
|
180
|
+
await self.timer_wheel.close()
|
|
156
181
|
raise
|
|
157
182
|
self._started = True
|
|
158
183
|
self._stopped.clear()
|
|
@@ -168,7 +193,10 @@ class NodeApplication:
|
|
|
168
193
|
try:
|
|
169
194
|
await self.command_server.stop()
|
|
170
195
|
finally:
|
|
171
|
-
|
|
196
|
+
try:
|
|
197
|
+
await self._cleanup_bcc_wrapper()
|
|
198
|
+
finally:
|
|
199
|
+
await self.timer_wheel.close()
|
|
172
200
|
return
|
|
173
201
|
self._started = False
|
|
174
202
|
self.command_dispatcher.stop_accepting()
|
|
@@ -188,7 +216,10 @@ class NodeApplication:
|
|
|
188
216
|
try:
|
|
189
217
|
await self._cleanup_bcc_wrapper()
|
|
190
218
|
finally:
|
|
191
|
-
|
|
219
|
+
try:
|
|
220
|
+
await self.timer_wheel.close()
|
|
221
|
+
finally:
|
|
222
|
+
self._stopped.set()
|
|
192
223
|
|
|
193
224
|
async def _cleanup_bcc_wrapper(self) -> None:
|
|
194
225
|
wrapper_path = self._wrapper_path
|
|
@@ -278,18 +309,19 @@ class NodeApplication:
|
|
|
278
309
|
"SESSION_NOT_FOUND",
|
|
279
310
|
f"unknown bcn session: {session_id}",
|
|
280
311
|
)
|
|
281
|
-
|
|
282
|
-
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:
|
|
283
314
|
raise CommandDispatchError(
|
|
284
315
|
"SESSION_BINDING_FAILED",
|
|
285
316
|
"runtime session binding is invalid",
|
|
286
317
|
)
|
|
287
|
-
|
|
318
|
+
capability_binding = self._session_capabilities.get(session_id)
|
|
288
319
|
if (
|
|
289
|
-
|
|
320
|
+
capability_binding is None
|
|
321
|
+
or capability_binding[0] != runtime_session.id
|
|
290
322
|
or not isinstance(session_capability, str)
|
|
291
323
|
or not hmac.compare_digest(
|
|
292
|
-
session_capability.encode(),
|
|
324
|
+
session_capability.encode(), capability_binding[1].encode()
|
|
293
325
|
)
|
|
294
326
|
):
|
|
295
327
|
raise CommandDispatchError(
|
|
@@ -298,7 +330,9 @@ class NodeApplication:
|
|
|
298
330
|
)
|
|
299
331
|
|
|
300
332
|
def _runtime_environment(self, session: RuntimeSession) -> Mapping[str, str]:
|
|
301
|
-
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")
|
|
302
336
|
return self._build_command_environment(
|
|
303
337
|
session.bcn_session_id,
|
|
304
338
|
session.id,
|
|
@@ -316,10 +350,11 @@ class NodeApplication:
|
|
|
316
350
|
wrapper_path = self._wrapper_path
|
|
317
351
|
if wrapper_path is None:
|
|
318
352
|
raise RuntimeError("bcc wrapper is not installed")
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
secrets.token_urlsafe(32)
|
|
322
|
-
|
|
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]
|
|
323
358
|
wrapper_directory = str(wrapper_path.parent)
|
|
324
359
|
allowed = set(_PLATFORM_ENVIRONMENT)
|
|
325
360
|
for name in self.runtime.environment_variable_names():
|
|
@@ -368,13 +403,12 @@ class NodeApplication:
|
|
|
368
403
|
if wrapper_path is None:
|
|
369
404
|
raise RuntimeError("bcc wrapper is not installed")
|
|
370
405
|
self.command_log.append((session_id, tuple(arguments)))
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
)
|
|
406
|
+
runtime_session = self.orchestrator.runtime_session(session_id)
|
|
407
|
+
if runtime_session is None:
|
|
408
|
+
raise RuntimeError("runtime session is not live")
|
|
375
409
|
environment = self._build_command_environment(
|
|
376
410
|
session_id,
|
|
377
|
-
|
|
411
|
+
runtime_session.id,
|
|
378
412
|
)
|
|
379
413
|
process = await asyncio.create_subprocess_exec(
|
|
380
414
|
str(wrapper_path),
|
{bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.7}/src/bazaar_compute_node/app/config.py
RENAMED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
+
import math
|
|
3
4
|
import os
|
|
4
5
|
import tomllib
|
|
5
6
|
from dataclasses import dataclass
|
|
@@ -25,6 +26,7 @@ class NodeConfiguration:
|
|
|
25
26
|
effort: str | None = None
|
|
26
27
|
sandbox_mode: RuntimeSandboxMode = RuntimeSandboxMode.WORKSPACE_WRITE
|
|
27
28
|
network_access: bool = True
|
|
29
|
+
runtime_idle_timeout_seconds: float = 0
|
|
28
30
|
runtime_env_include: tuple[str, ...] = ()
|
|
29
31
|
wecom_bot_id: str | None = None
|
|
30
32
|
wecom_websocket_url: str | None = None
|
|
@@ -81,6 +83,13 @@ def load_node_configuration(path: Path | None = None) -> NodeConfiguration:
|
|
|
81
83
|
network_access = runtime.get("network_access", True)
|
|
82
84
|
if not isinstance(network_access, bool):
|
|
83
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")
|
|
84
93
|
return NodeConfiguration(
|
|
85
94
|
channel=_optional_text(node.get("channel"), "node.channel"),
|
|
86
95
|
runtime=_optional_text(node.get("runtime"), "node.runtime"),
|
|
@@ -92,6 +101,7 @@ def load_node_configuration(path: Path | None = None) -> NodeConfiguration:
|
|
|
92
101
|
effort=_optional_text(runtime.get("effort"), "runtime.effort"),
|
|
93
102
|
sandbox_mode=parsed_sandbox_mode,
|
|
94
103
|
network_access=network_access,
|
|
104
|
+
runtime_idle_timeout_seconds=float(idle_timeout),
|
|
95
105
|
runtime_env_include=_text_list(
|
|
96
106
|
runtime_env.get("include", []), "runtime.env.include"
|
|
97
107
|
),
|
|
@@ -173,6 +173,7 @@ def _apply_runtime_configuration(
|
|
|
173
173
|
if args.audit is None:
|
|
174
174
|
args.audit = DEFAULT_AUDIT
|
|
175
175
|
args.runtime_env_include = configuration.runtime_env_include
|
|
176
|
+
args.runtime_idle_timeout_seconds = configuration.runtime_idle_timeout_seconds
|
|
176
177
|
args.channel_options = {
|
|
177
178
|
"bot_id": configuration.wecom_bot_id,
|
|
178
179
|
"websocket_url": configuration.wecom_websocket_url,
|
|
@@ -189,6 +190,7 @@ async def _run_node(args: argparse.Namespace, parser: argparse.ArgumentParser) -
|
|
|
189
190
|
runtime_options=_runtime_options(args),
|
|
190
191
|
runtime_sandbox_mode=args.sandbox_mode,
|
|
191
192
|
runtime_network_access=args.network_access,
|
|
193
|
+
runtime_idle_timeout_seconds=args.runtime_idle_timeout_seconds,
|
|
192
194
|
channel_options=args.channel_options if args.channel == "wecom" else {},
|
|
193
195
|
runtime_environment_include=args.runtime_env_include,
|
|
194
196
|
)
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
"""Codex App Server process, protocol, and runtime adapter."""
|
|
2
2
|
|
|
3
3
|
from .client import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
4
|
+
Client,
|
|
5
|
+
ErrorInfo,
|
|
6
|
+
FsChangedInfo,
|
|
7
|
+
ThreadInfo,
|
|
8
|
+
TurnInfo,
|
|
9
|
+
build_fs_watch_params,
|
|
9
10
|
build_initialize_params,
|
|
10
11
|
build_thread_resume_params,
|
|
11
12
|
build_thread_start_params,
|
|
@@ -13,18 +14,23 @@ from .client import (
|
|
|
13
14
|
build_turn_start_params,
|
|
14
15
|
build_turn_steer_params,
|
|
15
16
|
parse_error_notification,
|
|
17
|
+
parse_fs_changed_notification,
|
|
18
|
+
parse_fs_watch_response,
|
|
19
|
+
parse_initialize_response,
|
|
20
|
+
parse_skills_changed_notification,
|
|
16
21
|
parse_thread_response,
|
|
17
22
|
parse_turn_notification,
|
|
18
23
|
parse_turn_response,
|
|
19
24
|
parse_turn_steer_response,
|
|
20
25
|
)
|
|
21
|
-
from .events import
|
|
26
|
+
from .events import TurnEventStream
|
|
22
27
|
from .process import (
|
|
23
28
|
JsonlProcessSpec,
|
|
24
29
|
JsonlProcessState,
|
|
25
30
|
JsonlProcessSupervisor,
|
|
26
31
|
)
|
|
27
32
|
from .protocol import (
|
|
33
|
+
AppServerProtocolError,
|
|
28
34
|
JsonlMessage,
|
|
29
35
|
JsonlProcessExited,
|
|
30
36
|
JsonlProcessNotRunning,
|
|
@@ -33,16 +39,13 @@ from .protocol import (
|
|
|
33
39
|
JsonlRequestTimeout,
|
|
34
40
|
JsonlTransportError,
|
|
35
41
|
)
|
|
36
|
-
from .runtime import
|
|
42
|
+
from .runtime import Runtime
|
|
37
43
|
|
|
38
44
|
__all__ = [
|
|
39
|
-
"
|
|
40
|
-
"
|
|
41
|
-
"
|
|
42
|
-
"
|
|
43
|
-
"CodexThreadInfo",
|
|
44
|
-
"CodexTurnEventStream",
|
|
45
|
-
"CodexTurnInfo",
|
|
45
|
+
"AppServerProtocolError",
|
|
46
|
+
"Client",
|
|
47
|
+
"ErrorInfo",
|
|
48
|
+
"FsChangedInfo",
|
|
46
49
|
"JsonlMessage",
|
|
47
50
|
"JsonlProcessExited",
|
|
48
51
|
"JsonlProcessNotRunning",
|
|
@@ -53,6 +56,11 @@ __all__ = [
|
|
|
53
56
|
"JsonlRemoteError",
|
|
54
57
|
"JsonlRequestTimeout",
|
|
55
58
|
"JsonlTransportError",
|
|
59
|
+
"Runtime",
|
|
60
|
+
"ThreadInfo",
|
|
61
|
+
"TurnEventStream",
|
|
62
|
+
"TurnInfo",
|
|
63
|
+
"build_fs_watch_params",
|
|
56
64
|
"build_initialize_params",
|
|
57
65
|
"build_thread_resume_params",
|
|
58
66
|
"build_thread_start_params",
|
|
@@ -60,6 +68,10 @@ __all__ = [
|
|
|
60
68
|
"build_turn_start_params",
|
|
61
69
|
"build_turn_steer_params",
|
|
62
70
|
"parse_error_notification",
|
|
71
|
+
"parse_fs_changed_notification",
|
|
72
|
+
"parse_fs_watch_response",
|
|
73
|
+
"parse_initialize_response",
|
|
74
|
+
"parse_skills_changed_notification",
|
|
63
75
|
"parse_thread_response",
|
|
64
76
|
"parse_turn_notification",
|
|
65
77
|
"parse_turn_response",
|
|
@@ -4,9 +4,15 @@ from collections.abc import Mapping
|
|
|
4
4
|
from dataclasses import dataclass
|
|
5
5
|
from typing import cast
|
|
6
6
|
|
|
7
|
-
from ...core.models import
|
|
7
|
+
from ...core.models import (
|
|
8
|
+
ApprovalDecision,
|
|
9
|
+
ApprovalResult,
|
|
10
|
+
)
|
|
11
|
+
from ...core.models import (
|
|
12
|
+
ApprovalRequest as CoreApprovalRequest,
|
|
13
|
+
)
|
|
8
14
|
from .protocol import (
|
|
9
|
-
|
|
15
|
+
AppServerProtocolError,
|
|
10
16
|
JsonlMessage,
|
|
11
17
|
JsonlRequestId,
|
|
12
18
|
is_request_id,
|
|
@@ -26,13 +32,13 @@ _APPROVAL_METHODS = frozenset(
|
|
|
26
32
|
|
|
27
33
|
|
|
28
34
|
@dataclass(frozen=True, slots=True)
|
|
29
|
-
class
|
|
35
|
+
class ApprovalEnvelope:
|
|
30
36
|
"""Provider request plus its neutral approval projection."""
|
|
31
37
|
|
|
32
38
|
request_id: JsonlRequestId
|
|
33
39
|
method: str
|
|
34
40
|
params: Mapping[str, object]
|
|
35
|
-
request:
|
|
41
|
+
request: CoreApprovalRequest
|
|
36
42
|
|
|
37
43
|
|
|
38
44
|
def is_approval_method(method: object) -> bool:
|
|
@@ -47,28 +53,26 @@ def parse_approval_request(
|
|
|
47
53
|
turn_id: str,
|
|
48
54
|
provider_thread_id: str,
|
|
49
55
|
provider_turn_id: str | None,
|
|
50
|
-
) ->
|
|
56
|
+
) -> ApprovalEnvelope:
|
|
51
57
|
method = message.get("method")
|
|
52
58
|
if not isinstance(method, str) or not is_approval_method(method):
|
|
53
|
-
raise
|
|
59
|
+
raise AppServerProtocolError("message is not an approval request")
|
|
54
60
|
request_id = message.get("id")
|
|
55
61
|
if not is_request_id(request_id):
|
|
56
|
-
raise
|
|
57
|
-
"approval request id must be an integer or string"
|
|
58
|
-
)
|
|
62
|
+
raise AppServerProtocolError("approval request id must be an integer or string")
|
|
59
63
|
request_id = cast(JsonlRequestId, request_id)
|
|
60
64
|
params = message.get("params")
|
|
61
65
|
if not isinstance(params, Mapping):
|
|
62
|
-
raise
|
|
66
|
+
raise AppServerProtocolError("approval request params must be an object")
|
|
63
67
|
|
|
64
68
|
thread_value = _require_text(params, "threadId")
|
|
65
69
|
if thread_value != provider_thread_id:
|
|
66
|
-
raise
|
|
70
|
+
raise AppServerProtocolError(
|
|
67
71
|
"approval request thread does not match runtime thread"
|
|
68
72
|
)
|
|
69
73
|
provider_turn_value = _require_text(params, "turnId")
|
|
70
74
|
if provider_turn_id is None or provider_turn_value != provider_turn_id:
|
|
71
|
-
raise
|
|
75
|
+
raise AppServerProtocolError(
|
|
72
76
|
"approval request turn does not match runtime turn"
|
|
73
77
|
)
|
|
74
78
|
provider_item_id = _require_text(params, "itemId")
|
|
@@ -78,7 +82,7 @@ def parse_approval_request(
|
|
|
78
82
|
or isinstance(started_at_ms, bool)
|
|
79
83
|
or started_at_ms < 0
|
|
80
84
|
):
|
|
81
|
-
raise
|
|
85
|
+
raise AppServerProtocolError(
|
|
82
86
|
"approval request startedAtMs must be a non-negative integer"
|
|
83
87
|
)
|
|
84
88
|
action = {
|
|
@@ -89,7 +93,7 @@ def parse_approval_request(
|
|
|
89
93
|
if method == _PERMISSIONS_METHOD and not isinstance(
|
|
90
94
|
params.get("permissions"), Mapping
|
|
91
95
|
):
|
|
92
|
-
raise
|
|
96
|
+
raise AppServerProtocolError(
|
|
93
97
|
"permissions approval request has no permissions object"
|
|
94
98
|
)
|
|
95
99
|
|
|
@@ -104,11 +108,11 @@ def parse_approval_request(
|
|
|
104
108
|
value = params.get(provider_key)
|
|
105
109
|
if isinstance(value, str) and value:
|
|
106
110
|
metadata[metadata_key] = value
|
|
107
|
-
return
|
|
111
|
+
return ApprovalEnvelope(
|
|
108
112
|
request_id=request_id,
|
|
109
113
|
method=method,
|
|
110
114
|
params=params,
|
|
111
|
-
request=
|
|
115
|
+
request=CoreApprovalRequest(
|
|
112
116
|
request_id=str(request_id),
|
|
113
117
|
session_id=session_id,
|
|
114
118
|
runtime_session_id=runtime_session_id,
|
|
@@ -121,7 +125,7 @@ def parse_approval_request(
|
|
|
121
125
|
|
|
122
126
|
|
|
123
127
|
def build_approval_response(
|
|
124
|
-
approval:
|
|
128
|
+
approval: ApprovalEnvelope,
|
|
125
129
|
result: ApprovalResult,
|
|
126
130
|
) -> Mapping[str, object]:
|
|
127
131
|
if result.request_id != approval.request.request_id:
|
|
@@ -134,11 +138,11 @@ def build_approval_response(
|
|
|
134
138
|
return {"permissions": {}}
|
|
135
139
|
permissions = approval.params.get("permissions")
|
|
136
140
|
if not isinstance(permissions, Mapping):
|
|
137
|
-
raise
|
|
141
|
+
raise AppServerProtocolError(
|
|
138
142
|
"permissions approval request has no permissions object"
|
|
139
143
|
)
|
|
140
144
|
return {"permissions": dict(permissions), "scope": "turn"}
|
|
141
|
-
raise
|
|
145
|
+
raise AppServerProtocolError("unsupported approval response method")
|
|
142
146
|
|
|
143
147
|
|
|
144
148
|
def approval_error(error: BaseException) -> Mapping[str, object]:
|
|
@@ -153,14 +157,14 @@ def approval_error(error: BaseException) -> Mapping[str, object]:
|
|
|
153
157
|
def _require_text(params: Mapping[str, object], field_name: str) -> str:
|
|
154
158
|
value = params.get(field_name)
|
|
155
159
|
if not isinstance(value, str) or not value:
|
|
156
|
-
raise
|
|
160
|
+
raise AppServerProtocolError(
|
|
157
161
|
f"approval request {field_name} must be non-empty text"
|
|
158
162
|
)
|
|
159
163
|
return value
|
|
160
164
|
|
|
161
165
|
|
|
162
166
|
__all__ = [
|
|
163
|
-
"
|
|
167
|
+
"ApprovalEnvelope",
|
|
164
168
|
"approval_error",
|
|
165
169
|
"build_approval_response",
|
|
166
170
|
"is_approval_method",
|