bazaar-compute-node 0.1.6__tar.gz → 0.1.8__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.8}/PKG-INFO +7 -3
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/README.md +6 -2
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/pyproject.toml +2 -2
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/pyproject.toml.orig +2 -2
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/application.py +92 -31
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/command.py +29 -9
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/config.py +10 -0
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/app/reminder_dispatch.py +411 -0
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/bcc.py +913 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/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.8/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.8/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.8/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.8/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.8/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.8/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.8/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.8/src/bazaar_compute_node/contrib/codex}/runtime.py +159 -36
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/sqlite/__init__.py +2 -1
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/sqlite/codec.py +0 -40
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/sqlite/migrations.py +14 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/sqlite/plugin.py +1 -1
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +63 -0
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/contrib/sqlite/reminder_database.py +21 -0
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/contrib/sqlite/reminder_migration.py +85 -0
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/contrib/sqlite/reminder_repository.py +420 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/sqlite/repository.py +0 -90
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/command.py +54 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/instruction.py +46 -10
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/models/__init__.py +7 -0
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/core/models/reminder.py +282 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/models/states.py +13 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/orchestration/__init__.py +2 -1
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/core/orchestration/reminder.py +250 -0
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/core/orchestration/reminder_command.py +362 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/orchestration/services.py +8 -0
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/core/orchestration/session.py +1671 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/orchestration/turn.py +91 -20
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/core/reminder.py +342 -0
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/core/reminder_schedule.py +330 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/runtime.py +36 -7
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/storage.py +88 -13
- bazaar_compute_node-0.1.8/src/bazaar_compute_node/core/timerwheel.py +316 -0
- bazaar_compute_node-0.1.6/src/bazaar_compute_node/bcc.py +0 -529
- bazaar_compute_node-0.1.6/src/bazaar_compute_node/core/orchestration/session.py +0 -997
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/LICENSE +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/attachments.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/registry.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/transport.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/wrapper.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/wecom/channel.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/wecom/markdown.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/__init__.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/approval.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/audit.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/channel.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/client.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/concurrency.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/correlation.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/lifecycle.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/models/entities.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/observability.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/orchestration/command.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/core/outcomes.py +0 -0
- {bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/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.8
|
|
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.8"
|
|
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.8"
|
|
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.8}/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
|
|
@@ -10,19 +11,20 @@ from collections.abc import Mapping, Sequence
|
|
|
10
11
|
from pathlib import Path
|
|
11
12
|
|
|
12
13
|
from ..core.channel import ChannelContext, IChannel
|
|
14
|
+
from ..core.concurrency import SessionLockRegistry
|
|
13
15
|
from ..core.lifecycle import TimeoutBudget
|
|
14
16
|
from ..core.models import RuntimeSession
|
|
15
17
|
from ..core.observability import IAudit
|
|
16
|
-
from ..core.orchestration import SessionOrchestrator
|
|
18
|
+
from ..core.orchestration import ReminderScheduler, SessionOrchestrator
|
|
19
|
+
from ..core.orchestration.reminder_command import ReminderCommandService
|
|
17
20
|
from ..core.paths import resolve_data_dir, resolve_workspace_dir
|
|
18
21
|
from ..core.runtime import IRuntime, RuntimeCommandContext, RuntimeSandboxMode
|
|
19
22
|
from ..core.storage import IStorage, NodeIdentity
|
|
23
|
+
from ..core.timerwheel import TimerWheel
|
|
20
24
|
from .attachments import AttachmentMaterializer
|
|
21
|
-
from .command import
|
|
22
|
-
CommandDispatcher,
|
|
23
|
-
CommandDispatchError,
|
|
24
|
-
)
|
|
25
|
+
from .command import CommandDispatchError
|
|
25
26
|
from .registry import AdapterFactories
|
|
27
|
+
from .reminder_dispatch import CommandDispatcher
|
|
26
28
|
from .transport import LocalCommandServer
|
|
27
29
|
from .wrapper import install_bcc_wrapper, remove_bcc_wrapper
|
|
28
30
|
|
|
@@ -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,8 @@ 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.
|
|
87
|
+
self._session_capabilities: dict[str, tuple[str, str]] = {}
|
|
88
|
+
self._concurrency = SessionLockRegistry()
|
|
86
89
|
self._started = False
|
|
87
90
|
self._stopped = asyncio.Event()
|
|
88
91
|
self._runtime_environment_include = tuple(runtime_environment_include)
|
|
@@ -106,6 +109,23 @@ class NodeApplication:
|
|
|
106
109
|
startup_timeout_seconds=self.timeout_budget.startup_seconds,
|
|
107
110
|
)
|
|
108
111
|
self.runtime: IRuntime = factories.runtime(self._runtime_context)
|
|
112
|
+
if (
|
|
113
|
+
isinstance(runtime_idle_timeout_seconds, bool)
|
|
114
|
+
or not isinstance(runtime_idle_timeout_seconds, int | float)
|
|
115
|
+
or not math.isfinite(runtime_idle_timeout_seconds)
|
|
116
|
+
):
|
|
117
|
+
raise ValueError("runtime_idle_timeout_seconds must be a finite number")
|
|
118
|
+
self.timer_wheel = TimerWheel()
|
|
119
|
+
if (
|
|
120
|
+
runtime_idle_timeout_seconds > 0
|
|
121
|
+
and runtime_idle_timeout_seconds * 1_000 > self.timer_wheel.maximum_delay_ms
|
|
122
|
+
):
|
|
123
|
+
raise ValueError("runtime_idle_timeout_seconds exceeds the timer horizon")
|
|
124
|
+
runtime_idle_timeout_ms = (
|
|
125
|
+
math.ceil(runtime_idle_timeout_seconds * 1_000)
|
|
126
|
+
if runtime_idle_timeout_seconds > 0
|
|
127
|
+
else 0
|
|
128
|
+
)
|
|
109
129
|
self.orchestrator = SessionOrchestrator(
|
|
110
130
|
node_id=node_id,
|
|
111
131
|
workspace_id=workspace_id,
|
|
@@ -114,15 +134,30 @@ class NodeApplication:
|
|
|
114
134
|
storage=self.storage,
|
|
115
135
|
audit=self.audit,
|
|
116
136
|
timeout_budget=self.timeout_budget,
|
|
137
|
+
timer_wheel=self.timer_wheel,
|
|
138
|
+
runtime_idle_timeout_ms=runtime_idle_timeout_ms,
|
|
117
139
|
workspace=self._workspace_path,
|
|
140
|
+
concurrency=self._concurrency,
|
|
118
141
|
on_node_initialized=self._ensure_workspace,
|
|
119
142
|
)
|
|
143
|
+
self.reminder_scheduler = ReminderScheduler(
|
|
144
|
+
storage=self.storage,
|
|
145
|
+
timer_wheel=self.timer_wheel,
|
|
146
|
+
concurrency=self._concurrency,
|
|
147
|
+
publish_wake=self.orchestrator.publish_reminder_wake,
|
|
148
|
+
)
|
|
149
|
+
self.reminder_service = ReminderCommandService(
|
|
150
|
+
storage=self.storage,
|
|
151
|
+
concurrency=self._concurrency,
|
|
152
|
+
poke=self.reminder_scheduler.poke,
|
|
153
|
+
)
|
|
120
154
|
self.command_service = self.orchestrator.command_service
|
|
121
155
|
control_handler = None
|
|
122
156
|
if factories.control is not None:
|
|
123
157
|
control_handler = factories.control(self._adapter_context())
|
|
124
158
|
self.command_dispatcher = CommandDispatcher(
|
|
125
159
|
self.command_service,
|
|
160
|
+
reminder_service=self.reminder_service,
|
|
126
161
|
timeout_budget=self.timeout_budget,
|
|
127
162
|
control_handler=self._handle_control,
|
|
128
163
|
session_binding_validator=self._validate_session_binding,
|
|
@@ -147,28 +182,43 @@ class NodeApplication:
|
|
|
147
182
|
install_bcc_wrapper, self.data_dir / "bin"
|
|
148
183
|
)
|
|
149
184
|
try:
|
|
185
|
+
await self.timer_wheel.start()
|
|
150
186
|
await self.command_server.start()
|
|
151
187
|
await self.orchestrator.start(
|
|
152
188
|
timeout=self.timeout_budget.startup_seconds,
|
|
153
189
|
)
|
|
190
|
+
self._started = True
|
|
191
|
+
await self.reminder_scheduler.start(
|
|
192
|
+
timeout=self.timeout_budget.startup_seconds,
|
|
193
|
+
)
|
|
154
194
|
except BaseException:
|
|
155
|
-
|
|
195
|
+
try:
|
|
196
|
+
await self.stop()
|
|
197
|
+
finally:
|
|
198
|
+
await self.timer_wheel.close()
|
|
156
199
|
raise
|
|
157
|
-
self._started = True
|
|
158
200
|
self._stopped.clear()
|
|
159
201
|
self.command_dispatcher.start_accepting()
|
|
160
202
|
|
|
161
203
|
async def stop(self) -> None:
|
|
162
204
|
if not self._started:
|
|
163
205
|
try:
|
|
164
|
-
await self.
|
|
206
|
+
await self.reminder_scheduler.stop(
|
|
165
207
|
timeout=self.timeout_budget.shutdown_seconds,
|
|
166
208
|
)
|
|
167
209
|
finally:
|
|
168
210
|
try:
|
|
169
|
-
await self.
|
|
211
|
+
await self.command_dispatcher.drain(
|
|
212
|
+
timeout=self.timeout_budget.shutdown_seconds,
|
|
213
|
+
)
|
|
170
214
|
finally:
|
|
171
|
-
|
|
215
|
+
try:
|
|
216
|
+
await self.command_server.stop()
|
|
217
|
+
finally:
|
|
218
|
+
try:
|
|
219
|
+
await self._cleanup_bcc_wrapper()
|
|
220
|
+
finally:
|
|
221
|
+
await self.timer_wheel.close()
|
|
172
222
|
return
|
|
173
223
|
self._started = False
|
|
174
224
|
self.command_dispatcher.stop_accepting()
|
|
@@ -178,17 +228,25 @@ class NodeApplication:
|
|
|
178
228
|
)
|
|
179
229
|
finally:
|
|
180
230
|
try:
|
|
181
|
-
await self.
|
|
231
|
+
await self.reminder_scheduler.stop(
|
|
182
232
|
timeout=self.timeout_budget.shutdown_seconds,
|
|
183
233
|
)
|
|
184
234
|
finally:
|
|
185
235
|
try:
|
|
186
|
-
await self.
|
|
236
|
+
await self.orchestrator.stop(
|
|
237
|
+
timeout=self.timeout_budget.shutdown_seconds,
|
|
238
|
+
)
|
|
187
239
|
finally:
|
|
188
240
|
try:
|
|
189
|
-
await self.
|
|
241
|
+
await self.command_server.stop()
|
|
190
242
|
finally:
|
|
191
|
-
|
|
243
|
+
try:
|
|
244
|
+
await self._cleanup_bcc_wrapper()
|
|
245
|
+
finally:
|
|
246
|
+
try:
|
|
247
|
+
await self.timer_wheel.close()
|
|
248
|
+
finally:
|
|
249
|
+
self._stopped.set()
|
|
192
250
|
|
|
193
251
|
async def _cleanup_bcc_wrapper(self) -> None:
|
|
194
252
|
wrapper_path = self._wrapper_path
|
|
@@ -278,18 +336,19 @@ class NodeApplication:
|
|
|
278
336
|
"SESSION_NOT_FOUND",
|
|
279
337
|
f"unknown bcn session: {session_id}",
|
|
280
338
|
)
|
|
281
|
-
|
|
282
|
-
if runtime_session is None or runtime_session_id !=
|
|
339
|
+
runtime_session = self.orchestrator.runtime_session(session_id)
|
|
340
|
+
if runtime_session is None or runtime_session_id != runtime_session.id:
|
|
283
341
|
raise CommandDispatchError(
|
|
284
342
|
"SESSION_BINDING_FAILED",
|
|
285
343
|
"runtime session binding is invalid",
|
|
286
344
|
)
|
|
287
|
-
|
|
345
|
+
capability_binding = self._session_capabilities.get(session_id)
|
|
288
346
|
if (
|
|
289
|
-
|
|
347
|
+
capability_binding is None
|
|
348
|
+
or capability_binding[0] != runtime_session.id
|
|
290
349
|
or not isinstance(session_capability, str)
|
|
291
350
|
or not hmac.compare_digest(
|
|
292
|
-
session_capability.encode(),
|
|
351
|
+
session_capability.encode(), capability_binding[1].encode()
|
|
293
352
|
)
|
|
294
353
|
):
|
|
295
354
|
raise CommandDispatchError(
|
|
@@ -298,7 +357,9 @@ class NodeApplication:
|
|
|
298
357
|
)
|
|
299
358
|
|
|
300
359
|
def _runtime_environment(self, session: RuntimeSession) -> Mapping[str, str]:
|
|
301
|
-
self.
|
|
360
|
+
runtime_session = self.orchestrator.runtime_session(session.bcn_session_id)
|
|
361
|
+
if runtime_session is None or runtime_session.id != session.id:
|
|
362
|
+
raise RuntimeError("runtime session is not the current live binding")
|
|
302
363
|
return self._build_command_environment(
|
|
303
364
|
session.bcn_session_id,
|
|
304
365
|
session.id,
|
|
@@ -316,10 +377,11 @@ class NodeApplication:
|
|
|
316
377
|
wrapper_path = self._wrapper_path
|
|
317
378
|
if wrapper_path is None:
|
|
318
379
|
raise RuntimeError("bcc wrapper is not installed")
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
secrets.token_urlsafe(32)
|
|
322
|
-
|
|
380
|
+
capability_binding = self._session_capabilities.get(session_id)
|
|
381
|
+
if capability_binding is None or capability_binding[0] != runtime_session_id:
|
|
382
|
+
capability_binding = (runtime_session_id, secrets.token_urlsafe(32))
|
|
383
|
+
self._session_capabilities[session_id] = capability_binding
|
|
384
|
+
session_capability = capability_binding[1]
|
|
323
385
|
wrapper_directory = str(wrapper_path.parent)
|
|
324
386
|
allowed = set(_PLATFORM_ENVIRONMENT)
|
|
325
387
|
for name in self.runtime.environment_variable_names():
|
|
@@ -368,13 +430,12 @@ class NodeApplication:
|
|
|
368
430
|
if wrapper_path is None:
|
|
369
431
|
raise RuntimeError("bcc wrapper is not installed")
|
|
370
432
|
self.command_log.append((session_id, tuple(arguments)))
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
)
|
|
433
|
+
runtime_session = self.orchestrator.runtime_session(session_id)
|
|
434
|
+
if runtime_session is None:
|
|
435
|
+
raise RuntimeError("runtime session is not live")
|
|
375
436
|
environment = self._build_command_environment(
|
|
376
437
|
session_id,
|
|
377
|
-
|
|
438
|
+
runtime_session.id,
|
|
378
439
|
)
|
|
379
440
|
process = await asyncio.create_subprocess_exec(
|
|
380
441
|
str(wrapper_path),
|
{bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/src/bazaar_compute_node/app/command.py
RENAMED
|
@@ -100,7 +100,7 @@ SessionBindingValidator = Callable[[str, Mapping[str, object]], Awaitable[None]]
|
|
|
100
100
|
|
|
101
101
|
|
|
102
102
|
class CommandDispatcher:
|
|
103
|
-
"""Translate local JSON requests into core command results."""
|
|
103
|
+
"""Translate resource-scoped local JSON requests into core command results."""
|
|
104
104
|
|
|
105
105
|
def __init__(
|
|
106
106
|
self,
|
|
@@ -222,20 +222,40 @@ class CommandDispatcher:
|
|
|
222
222
|
async def _dispatch_command(
|
|
223
223
|
self, request: Mapping[str, object]
|
|
224
224
|
) -> Mapping[str, object]:
|
|
225
|
-
|
|
226
|
-
if not isinstance(
|
|
225
|
+
resource = request.get("resource")
|
|
226
|
+
if not isinstance(resource, str) or not resource:
|
|
227
227
|
raise CommandDispatchError(
|
|
228
|
-
"
|
|
228
|
+
"RESOURCE_REQUIRED", "resource must be a non-empty string"
|
|
229
229
|
)
|
|
230
230
|
command = request.get("command")
|
|
231
231
|
if not isinstance(command, str) or not command:
|
|
232
232
|
raise CommandDispatchError(
|
|
233
233
|
"COMMAND_REQUIRED", "command must be a non-empty string"
|
|
234
234
|
)
|
|
235
|
+
if resource == "message":
|
|
236
|
+
if command not in {"check", "read", "send"}:
|
|
237
|
+
raise CommandDispatchError(
|
|
238
|
+
"UNKNOWN_COMMAND", f"unsupported message command: {command}"
|
|
239
|
+
)
|
|
240
|
+
elif resource == "thread":
|
|
241
|
+
if command != "unfollow":
|
|
242
|
+
raise CommandDispatchError(
|
|
243
|
+
"UNKNOWN_COMMAND", f"unsupported thread command: {command}"
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
raise CommandDispatchError(
|
|
247
|
+
"UNKNOWN_RESOURCE", f"unsupported command resource: {resource}"
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
session_id = request.get("session_id")
|
|
251
|
+
if not isinstance(session_id, str) or not session_id:
|
|
252
|
+
raise CommandDispatchError(
|
|
253
|
+
"SESSION_REQUIRED", "session_id must be a non-empty string"
|
|
254
|
+
)
|
|
235
255
|
if self._session_binding_validator is not None:
|
|
236
256
|
await self._session_binding_validator(session_id, request)
|
|
237
257
|
|
|
238
|
-
if command == "check":
|
|
258
|
+
if resource == "message" and command == "check":
|
|
239
259
|
result = await self._service.check(session_id)
|
|
240
260
|
return {
|
|
241
261
|
"ok": True,
|
|
@@ -252,7 +272,7 @@ class CommandDispatcher:
|
|
|
252
272
|
},
|
|
253
273
|
}
|
|
254
274
|
|
|
255
|
-
if command == "read":
|
|
275
|
+
if resource == "message" and command == "read":
|
|
256
276
|
target = request.get("target")
|
|
257
277
|
if not isinstance(target, str) or not target:
|
|
258
278
|
raise CommandDispatchError(
|
|
@@ -290,7 +310,7 @@ class CommandDispatcher:
|
|
|
290
310
|
},
|
|
291
311
|
}
|
|
292
312
|
|
|
293
|
-
if command == "send":
|
|
313
|
+
if resource == "message" and command == "send":
|
|
294
314
|
target = request.get("target")
|
|
295
315
|
body = request.get("body")
|
|
296
316
|
command_id = request.get("command_id")
|
|
@@ -343,7 +363,7 @@ class CommandDispatcher:
|
|
|
343
363
|
"result": {"outbound": serialize_outbound(result)},
|
|
344
364
|
}
|
|
345
365
|
|
|
346
|
-
if command == "unfollow":
|
|
366
|
+
if resource == "thread" and command == "unfollow":
|
|
347
367
|
target = request.get("target")
|
|
348
368
|
if not isinstance(target, str) or not target:
|
|
349
369
|
raise CommandDispatchError(
|
|
@@ -352,7 +372,7 @@ class CommandDispatcher:
|
|
|
352
372
|
changed = await self._service.unfollow(session_id, target=target)
|
|
353
373
|
return {"ok": True, "result": {"changed": changed}}
|
|
354
374
|
|
|
355
|
-
raise
|
|
375
|
+
raise AssertionError("validated command route has no handler")
|
|
356
376
|
|
|
357
377
|
|
|
358
378
|
def format_message_time(timestamp_ms: int) -> str:
|
{bazaar_compute_node-0.1.6 → bazaar_compute_node-0.1.8}/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
|
),
|