bazaar-compute-node 0.1.22__tar.gz → 0.1.23__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.22 → bazaar_compute_node-0.1.23}/PKG-INFO +2 -1
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/pyproject.toml +2 -1
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/pyproject.toml.orig +2 -1
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/agent.py +24 -2
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/command.py +199 -78
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/app/resource_dispatch.py +630 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/system_service.py +2 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/bcc.py +373 -241
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/__init__.py +1 -1
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/agent_migration.py +2 -22
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/codec.py +26 -88
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/contrib/sqlite/handoff_codec.py +24 -0
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/contrib/sqlite/handoff_migration.py +29 -0
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/contrib/sqlite/handoff_repository.py +166 -0
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/contrib/sqlite/inbox_migration.py +19 -0
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/contrib/sqlite/migration.py +19 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/migrations.py +19 -15
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/plugin.py +1 -1
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +50 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/reminder_migration.py +2 -22
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/reminder_repository.py +3 -25
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/repository.py +18 -27
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/scoped_repository.py +188 -41
- bazaar_compute_node-0.1.22/src/bazaar_compute_node/contrib/sqlite/reminder_database.py → bazaar_compute_node-0.1.23/src/bazaar_compute_node/contrib/sqlite/storage.py +12 -12
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/audit.py +0 -12
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/command.py +71 -7
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/correlation.py +0 -6
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/core/handoff.py +49 -0
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/core/inbox.py +30 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/instruction.py +36 -13
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/models/__init__.py +4 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/models/entities.py +39 -58
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/core/models/handoff.py +26 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/models/reminder.py +13 -37
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/models/states.py +0 -2
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/orchestration/command.py +101 -45
- bazaar_compute_node-0.1.23/src/bazaar_compute_node/core/orchestration/handoff_command.py +301 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/orchestration/session.py +84 -8
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/orchestration/turn.py +8 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/reminder.py +5 -24
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/reminder_schedule.py +0 -18
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/storage.py +48 -0
- bazaar_compute_node-0.1.22/src/bazaar_compute_node/app/reminder_dispatch.py +0 -411
- bazaar_compute_node-0.1.22/src/bazaar_compute_node/contrib/sqlite/reminder_codec.py +0 -63
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/LICENSE +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/README.md +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/agent_management.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/application.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/attachments.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/config.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/registry.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/transport.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/windows_pipe.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/wrapper.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/cli.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/codex/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/codex/approval.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/codex/client.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/codex/events.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/codex/plugin.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/codex/process.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/codex/protocol.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/codex/runtime.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/api.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/approval.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/attachments.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/channel.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/frame.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/identity.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/outbound.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/plugin.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/lark/transport.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/logging/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/logging/audit.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/logging/plugin.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/sqlite/database.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/telegram/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/telegram/api.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/telegram/approval.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/telegram/attachments.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/telegram/channel.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/telegram/identity.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/telegram/markdown.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/telegram/outbound.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/telegram/plugin.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/wecom/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/wecom/channel.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/wecom/markdown.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/wecom/outbound.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/contrib/wecom/plugin.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/approval.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/channel.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/client.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/concurrency.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/lifecycle.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/models/reminder_owner.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/observability.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/orchestration/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/orchestration/delivery.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/orchestration/error_feedback.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/orchestration/reminder.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/orchestration/reminder_command.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/orchestration/services.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/outcomes.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/paths.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/runtime.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/core/timerwheel.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/i18n/__init__.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/i18n/catalog.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/i18n/english.py +0 -0
- {bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/i18n/schinese.py +0 -0
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: bazaar-compute-node
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.23
|
|
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
|
|
7
7
|
Requires-Dist: aiohttp>=3.12.0
|
|
8
8
|
Requires-Dist: aiosqlite>=0.20.0
|
|
9
9
|
Requires-Dist: cryptography>=45.0.0
|
|
10
|
+
Requires-Dist: pydantic>=2.0.0
|
|
10
11
|
Requires-Dist: protobug==1.0.0
|
|
11
12
|
Requires-Dist: tzdata>=2024.1 ; sys_platform == 'win32'
|
|
12
13
|
Requires-Python: >=3.14
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bazaar-compute-node"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.23"
|
|
4
4
|
description = "Runtime-agnostic computer node daemon for agents and channels."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "AGPL-3.0-only"
|
|
@@ -10,6 +10,7 @@ dependencies = [
|
|
|
10
10
|
"aiohttp>=3.12.0",
|
|
11
11
|
"aiosqlite>=0.20.0",
|
|
12
12
|
"cryptography>=45.0.0",
|
|
13
|
+
"pydantic>=2.0.0",
|
|
13
14
|
"protobug==1.0.0",
|
|
14
15
|
"tzdata>=2024.1; sys_platform == 'win32'",
|
|
15
16
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "bazaar-compute-node"
|
|
3
|
-
version = "0.1.
|
|
3
|
+
version = "0.1.23"
|
|
4
4
|
description = "Runtime-agnostic computer node daemon for agents and channels."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = "AGPL-3.0-only"
|
|
@@ -10,6 +10,7 @@ dependencies = [
|
|
|
10
10
|
"aiohttp>=3.12.0",
|
|
11
11
|
"aiosqlite>=0.20.0",
|
|
12
12
|
"cryptography>=45.0.0",
|
|
13
|
+
"pydantic>=2.0.0",
|
|
13
14
|
"protobug==1.0.0",
|
|
14
15
|
"tzdata>=2024.1; sys_platform == 'win32'",
|
|
15
16
|
]
|
{bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/agent.py
RENAMED
|
@@ -10,6 +10,8 @@ import secrets
|
|
|
10
10
|
from collections.abc import Callable, Mapping, Sequence
|
|
11
11
|
from dataclasses import dataclass
|
|
12
12
|
from pathlib import Path
|
|
13
|
+
from time import time_ns
|
|
14
|
+
from typing import cast
|
|
13
15
|
|
|
14
16
|
from ..core.channel import AgentScopedChannel, ChannelContext, IChannel
|
|
15
17
|
from ..core.concurrency import ISessionConcurrency, SessionLockRegistry
|
|
@@ -17,17 +19,19 @@ from ..core.lifecycle import TimeoutBudget
|
|
|
17
19
|
from ..core.models import RuntimeSession
|
|
18
20
|
from ..core.observability import IAudit
|
|
19
21
|
from ..core.orchestration import SessionOrchestrator
|
|
22
|
+
from ..core.orchestration.handoff_command import HandoffCommandService
|
|
20
23
|
from ..core.orchestration.reminder_command import ReminderCommandService
|
|
24
|
+
from ..core.orchestration.services import SessionAuditRecorder
|
|
21
25
|
from ..core.paths import resolve_workspace_dir
|
|
22
26
|
from ..core.runtime import IRuntime, RuntimeCommandContext
|
|
23
|
-
from ..core.storage import IStorageScope
|
|
27
|
+
from ..core.storage import IHandoffStorageScope, IStorageScope
|
|
24
28
|
from ..core.timerwheel import TimerWheel
|
|
25
29
|
from ..i18n import Translator
|
|
26
30
|
from .attachments import AttachmentMaterializer
|
|
27
31
|
from .command import CommandDispatchError
|
|
28
32
|
from .config import AgentConfiguration
|
|
29
33
|
from .registry import AgentAdapterFactories
|
|
30
|
-
from .
|
|
34
|
+
from .resource_dispatch import CommandDispatcher
|
|
31
35
|
from .wrapper import install_bcc_wrapper, remove_bcc_wrapper
|
|
32
36
|
|
|
33
37
|
CommandRecord = tuple[str, tuple[str, ...]]
|
|
@@ -78,6 +82,7 @@ class AgentApplication:
|
|
|
78
82
|
self.agent_id = configuration.id
|
|
79
83
|
self.name = configuration.name
|
|
80
84
|
self.storage = storage
|
|
85
|
+
self._handoff_storage = cast(IHandoffStorageScope, storage)
|
|
81
86
|
self.audit = audit
|
|
82
87
|
self.timer_wheel = timer_wheel
|
|
83
88
|
self.timeout_budget = timeout_budget
|
|
@@ -147,6 +152,7 @@ class AgentApplication:
|
|
|
147
152
|
channel=self.channel,
|
|
148
153
|
runtime=self.runtime,
|
|
149
154
|
storage=self.storage,
|
|
155
|
+
handoff_storage=self._handoff_storage,
|
|
150
156
|
audit=self.audit,
|
|
151
157
|
timeout_budget=self.timeout_budget,
|
|
152
158
|
timer_wheel=self.timer_wheel,
|
|
@@ -161,6 +167,16 @@ class AgentApplication:
|
|
|
161
167
|
concurrency=reminder_concurrency,
|
|
162
168
|
poke=reminder_poke,
|
|
163
169
|
)
|
|
170
|
+
self.handoff_service = HandoffCommandService(
|
|
171
|
+
storage=self._handoff_storage,
|
|
172
|
+
audit=SessionAuditRecorder(
|
|
173
|
+
sink=self.audit,
|
|
174
|
+
timeout_budget=self.timeout_budget,
|
|
175
|
+
clock=lambda: time_ns() // 1_000_000,
|
|
176
|
+
),
|
|
177
|
+
publish_wake=self.publish_handoff_wake,
|
|
178
|
+
node_id=lambda: self.agent_id,
|
|
179
|
+
)
|
|
164
180
|
self._provider_control_handler = (
|
|
165
181
|
factories.control(self._adapter_context())
|
|
166
182
|
if factories.control is not None
|
|
@@ -169,6 +185,7 @@ class AgentApplication:
|
|
|
169
185
|
self.command_dispatcher = CommandDispatcher(
|
|
170
186
|
self.orchestrator.command_service,
|
|
171
187
|
reminder_service=self.reminder_service,
|
|
188
|
+
handoff_service=self.handoff_service,
|
|
172
189
|
timeout_budget=self.timeout_budget,
|
|
173
190
|
control_handler=(
|
|
174
191
|
self._handle_control
|
|
@@ -270,6 +287,11 @@ class AgentApplication:
|
|
|
270
287
|
raise RuntimeError("Agent application is not started")
|
|
271
288
|
await self.orchestrator.publish_reminder_wake(session_id)
|
|
272
289
|
|
|
290
|
+
async def publish_handoff_wake(self, session_id: str) -> None:
|
|
291
|
+
if not self._started:
|
|
292
|
+
raise RuntimeError("Agent application is not started")
|
|
293
|
+
await self.orchestrator.publish_handoff_wake(session_id)
|
|
294
|
+
|
|
273
295
|
def health_record(self) -> dict[str, object]:
|
|
274
296
|
return {
|
|
275
297
|
"agent_id": self.agent_id,
|
{bazaar_compute_node-0.1.22 → bazaar_compute_node-0.1.23}/src/bazaar_compute_node/app/command.py
RENAMED
|
@@ -4,13 +4,18 @@ import asyncio
|
|
|
4
4
|
from collections.abc import Awaitable, Callable, Mapping
|
|
5
5
|
from datetime import UTC, datetime
|
|
6
6
|
from time import time_ns
|
|
7
|
+
from typing import Annotated, Literal, cast
|
|
8
|
+
|
|
9
|
+
from pydantic import BaseModel, ConfigDict, Field, StrictInt, StrictStr, ValidationError
|
|
7
10
|
|
|
8
11
|
from ..core.command import (
|
|
9
12
|
ICommandService,
|
|
13
|
+
InboxListResult,
|
|
14
|
+
MessageSendHandoffRequired,
|
|
10
15
|
SessionNotFoundError,
|
|
11
16
|
)
|
|
12
17
|
from ..core.lifecycle import TimeoutBudget
|
|
13
|
-
from ..core.models import InboundMessage, OutboundMessage
|
|
18
|
+
from ..core.models import InboundMessage, InboxTargetSummary, OutboundMessage
|
|
14
19
|
|
|
15
20
|
|
|
16
21
|
def serialize_inbound(message: InboundMessage) -> dict[str, object]:
|
|
@@ -84,6 +89,38 @@ def serialize_outbound(message: OutboundMessage) -> dict[str, object]:
|
|
|
84
89
|
}
|
|
85
90
|
|
|
86
91
|
|
|
92
|
+
def serialize_inbox_target(summary: InboxTargetSummary) -> dict[str, object]:
|
|
93
|
+
sender = summary.latest_sender
|
|
94
|
+
latest_time_ms = (
|
|
95
|
+
summary.latest_provider_time_ms
|
|
96
|
+
if summary.latest_provider_time_ms is not None
|
|
97
|
+
else summary.latest_received_at_ms
|
|
98
|
+
)
|
|
99
|
+
return {
|
|
100
|
+
"target": summary.target,
|
|
101
|
+
"session_id": summary.session_id,
|
|
102
|
+
"target_kind": summary.target_kind.value,
|
|
103
|
+
"current": summary.current,
|
|
104
|
+
"pending_count": summary.pending_count,
|
|
105
|
+
"last_activity_at_ms": summary.last_activity_at_ms,
|
|
106
|
+
"latest_message_id": summary.latest_message_id,
|
|
107
|
+
"latest_sender": (
|
|
108
|
+
None if sender is None else {"id": sender.id, "name": sender.name}
|
|
109
|
+
),
|
|
110
|
+
"latest_time_ms": latest_time_ms,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def serialize_inbox_list(result: InboxListResult) -> dict[str, object]:
|
|
115
|
+
return {
|
|
116
|
+
"targets": [serialize_inbox_target(target) for target in result.targets],
|
|
117
|
+
"total": result.total,
|
|
118
|
+
"shown": result.shown,
|
|
119
|
+
"offset": result.offset,
|
|
120
|
+
"has_more": result.has_more,
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
|
|
87
124
|
class CommandDispatchError(ValueError):
|
|
88
125
|
def __init__(
|
|
89
126
|
self,
|
|
@@ -100,6 +137,125 @@ class CommandDispatchError(ValueError):
|
|
|
100
137
|
self.next_action = next_action
|
|
101
138
|
|
|
102
139
|
|
|
140
|
+
NonEmptyText = Annotated[StrictStr, Field(min_length=1)]
|
|
141
|
+
PositiveInt = Annotated[StrictInt, Field(gt=0)]
|
|
142
|
+
NonNegativeInt = Annotated[StrictInt, Field(ge=0)]
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
class _CommandRequest(BaseModel):
|
|
146
|
+
model_config = ConfigDict(extra="ignore", strict=True)
|
|
147
|
+
|
|
148
|
+
kind: Literal["command"]
|
|
149
|
+
resource: StrictStr
|
|
150
|
+
command: StrictStr
|
|
151
|
+
session_id: NonEmptyText
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class _MessageCheckRequest(_CommandRequest):
|
|
155
|
+
resource: Literal["message"]
|
|
156
|
+
command: Literal["check"]
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class _MessageReadRequest(_CommandRequest):
|
|
160
|
+
resource: Literal["message"]
|
|
161
|
+
command: Literal["read"]
|
|
162
|
+
target: NonEmptyText
|
|
163
|
+
around_message_id: StrictStr | None = None
|
|
164
|
+
limit: PositiveInt = 100
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
class _MessageSendRequest(_CommandRequest):
|
|
168
|
+
resource: Literal["message"]
|
|
169
|
+
command: Literal["send"]
|
|
170
|
+
target: NonEmptyText
|
|
171
|
+
body: StrictStr
|
|
172
|
+
command_id: NonEmptyText
|
|
173
|
+
attachment_paths: list[NonEmptyText] = Field(default_factory=list)
|
|
174
|
+
reply_to_message_id: NonEmptyText | None = None
|
|
175
|
+
created_at_ms: NonNegativeInt = Field(
|
|
176
|
+
default_factory=lambda: time_ns() // 1_000_000
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
class _InboxListRequest(_CommandRequest):
|
|
181
|
+
resource: Literal["inbox"]
|
|
182
|
+
command: Literal["list"]
|
|
183
|
+
limit: PositiveInt = 100
|
|
184
|
+
offset: NonNegativeInt = 0
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
class _ThreadUnfollowRequest(_CommandRequest):
|
|
188
|
+
resource: Literal["thread"]
|
|
189
|
+
command: Literal["unfollow"]
|
|
190
|
+
target: NonEmptyText
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
_RequestModel = type[_CommandRequest]
|
|
194
|
+
|
|
195
|
+
_REQUEST_MODELS: dict[tuple[str, str], _RequestModel] = {
|
|
196
|
+
("message", "check"): _MessageCheckRequest,
|
|
197
|
+
("message", "read"): _MessageReadRequest,
|
|
198
|
+
("message", "send"): _MessageSendRequest,
|
|
199
|
+
("inbox", "list"): _InboxListRequest,
|
|
200
|
+
("thread", "unfollow"): _ThreadUnfollowRequest,
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
_REQUEST_ERRORS: dict[str, tuple[str, str]] = {
|
|
204
|
+
"session_id": ("SESSION_REQUIRED", "session_id must be a non-empty string"),
|
|
205
|
+
"limit": ("INVALID_LIMIT", "limit must be a positive integer"),
|
|
206
|
+
"offset": ("INVALID_OFFSET", "offset must be a non-negative integer"),
|
|
207
|
+
"target": ("TARGET_REQUIRED", "target must be a non-empty string"),
|
|
208
|
+
"around_message_id": (
|
|
209
|
+
"INVALID_AROUND_MESSAGE",
|
|
210
|
+
"around_message_id must be a string",
|
|
211
|
+
),
|
|
212
|
+
"body": ("BODY_REQUIRED", "body must be text"),
|
|
213
|
+
"command_id": (
|
|
214
|
+
"COMMAND_ID_REQUIRED",
|
|
215
|
+
"command_id must be a non-empty string",
|
|
216
|
+
),
|
|
217
|
+
"attachment_paths": (
|
|
218
|
+
"INVALID_ATTACHMENTS",
|
|
219
|
+
"attachment_paths must be a list of non-empty strings",
|
|
220
|
+
),
|
|
221
|
+
"reply_to_message_id": (
|
|
222
|
+
"INVALID_REPLY_TO",
|
|
223
|
+
"reply_to_message_id must be a non-empty string",
|
|
224
|
+
),
|
|
225
|
+
"created_at_ms": (
|
|
226
|
+
"INVALID_CREATED_AT",
|
|
227
|
+
"created_at_ms must be non-negative",
|
|
228
|
+
),
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
def _parse_command_request[RequestT: _CommandRequest](
|
|
233
|
+
request: Mapping[str, object],
|
|
234
|
+
model: type[RequestT],
|
|
235
|
+
*,
|
|
236
|
+
errors: Mapping[str, tuple[str, str]] | None = None,
|
|
237
|
+
) -> RequestT:
|
|
238
|
+
try:
|
|
239
|
+
return model.model_validate(request)
|
|
240
|
+
except ValidationError as error:
|
|
241
|
+
field_name = next(
|
|
242
|
+
(
|
|
243
|
+
part
|
|
244
|
+
for part in error.errors()[0].get("loc", ())
|
|
245
|
+
if isinstance(part, str)
|
|
246
|
+
),
|
|
247
|
+
"request",
|
|
248
|
+
)
|
|
249
|
+
error_map = dict(_REQUEST_ERRORS)
|
|
250
|
+
if errors is not None:
|
|
251
|
+
error_map.update(errors)
|
|
252
|
+
code, message = error_map.get(
|
|
253
|
+
field_name,
|
|
254
|
+
("INVALID_COMMAND", "command request is invalid"),
|
|
255
|
+
)
|
|
256
|
+
raise CommandDispatchError(code, message) from error
|
|
257
|
+
|
|
258
|
+
|
|
103
259
|
ControlHandler = Callable[[Mapping[str, object]], Awaitable[Mapping[str, object]]]
|
|
104
260
|
SessionBindingValidator = Callable[[str, Mapping[str, object]], Awaitable[None]]
|
|
105
261
|
|
|
@@ -225,14 +381,14 @@ class CommandDispatcher:
|
|
|
225
381
|
self._drained.set()
|
|
226
382
|
|
|
227
383
|
async def _dispatch_command(
|
|
228
|
-
self,
|
|
384
|
+
self, raw_request: Mapping[str, object]
|
|
229
385
|
) -> Mapping[str, object]:
|
|
230
|
-
resource =
|
|
386
|
+
resource = raw_request.get("resource")
|
|
231
387
|
if not isinstance(resource, str) or not resource:
|
|
232
388
|
raise CommandDispatchError(
|
|
233
389
|
"RESOURCE_REQUIRED", "resource must be a non-empty string"
|
|
234
390
|
)
|
|
235
|
-
command =
|
|
391
|
+
command = raw_request.get("command")
|
|
236
392
|
if not isinstance(command, str) or not command:
|
|
237
393
|
raise CommandDispatchError(
|
|
238
394
|
"COMMAND_REQUIRED", "command must be a non-empty string"
|
|
@@ -242,6 +398,11 @@ class CommandDispatcher:
|
|
|
242
398
|
raise CommandDispatchError(
|
|
243
399
|
"UNKNOWN_COMMAND", f"unsupported message command: {command}"
|
|
244
400
|
)
|
|
401
|
+
elif resource == "inbox":
|
|
402
|
+
if command != "list":
|
|
403
|
+
raise CommandDispatchError(
|
|
404
|
+
"UNKNOWN_COMMAND", f"unsupported inbox command: {command}"
|
|
405
|
+
)
|
|
245
406
|
elif resource == "thread":
|
|
246
407
|
if command != "unfollow":
|
|
247
408
|
raise CommandDispatchError(
|
|
@@ -252,13 +413,12 @@ class CommandDispatcher:
|
|
|
252
413
|
"UNKNOWN_RESOURCE", f"unsupported command resource: {resource}"
|
|
253
414
|
)
|
|
254
415
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
)
|
|
416
|
+
request = _parse_command_request(
|
|
417
|
+
raw_request, _REQUEST_MODELS[(resource, command)]
|
|
418
|
+
)
|
|
419
|
+
session_id = request.session_id
|
|
260
420
|
if self._session_binding_validator is not None:
|
|
261
|
-
await self._session_binding_validator(session_id,
|
|
421
|
+
await self._session_binding_validator(session_id, raw_request)
|
|
262
422
|
|
|
263
423
|
if resource == "message" and command == "check":
|
|
264
424
|
result = await self._service.check(session_id)
|
|
@@ -277,27 +437,22 @@ class CommandDispatcher:
|
|
|
277
437
|
},
|
|
278
438
|
}
|
|
279
439
|
|
|
440
|
+
if resource == "inbox" and command == "list":
|
|
441
|
+
request = cast(_InboxListRequest, request)
|
|
442
|
+
result = await self._service.list_inbox(
|
|
443
|
+
session_id,
|
|
444
|
+
limit=request.limit,
|
|
445
|
+
offset=request.offset,
|
|
446
|
+
)
|
|
447
|
+
return {"ok": True, "result": serialize_inbox_list(result)}
|
|
448
|
+
|
|
280
449
|
if resource == "message" and command == "read":
|
|
281
|
-
|
|
282
|
-
if not isinstance(target, str) or not target:
|
|
283
|
-
raise CommandDispatchError(
|
|
284
|
-
"TARGET_REQUIRED", "target must be a non-empty string"
|
|
285
|
-
)
|
|
286
|
-
around_message_id = request.get("around_message_id")
|
|
287
|
-
if around_message_id is not None and not isinstance(around_message_id, str):
|
|
288
|
-
raise CommandDispatchError(
|
|
289
|
-
"INVALID_AROUND_MESSAGE", "around_message_id must be a string"
|
|
290
|
-
)
|
|
291
|
-
limit = request.get("limit", 100)
|
|
292
|
-
if isinstance(limit, bool) or not isinstance(limit, int) or limit <= 0:
|
|
293
|
-
raise CommandDispatchError(
|
|
294
|
-
"INVALID_LIMIT", "limit must be a positive integer"
|
|
295
|
-
)
|
|
450
|
+
request = cast(_MessageReadRequest, request)
|
|
296
451
|
result = await self._service.read(
|
|
297
452
|
session_id,
|
|
298
|
-
target=target,
|
|
299
|
-
around_message_id=around_message_id,
|
|
300
|
-
limit=limit,
|
|
453
|
+
target=request.target,
|
|
454
|
+
around_message_id=request.around_message_id,
|
|
455
|
+
limit=request.limit,
|
|
301
456
|
)
|
|
302
457
|
return {
|
|
303
458
|
"ok": True,
|
|
@@ -316,65 +471,31 @@ class CommandDispatcher:
|
|
|
316
471
|
}
|
|
317
472
|
|
|
318
473
|
if resource == "message" and command == "send":
|
|
319
|
-
|
|
320
|
-
body = request.get("body")
|
|
321
|
-
command_id = request.get("command_id")
|
|
322
|
-
attachment_paths = request.get("attachment_paths", [])
|
|
323
|
-
reply_to_message_id = request.get("reply_to_message_id")
|
|
324
|
-
created_at_ms = request.get("created_at_ms", time_ns() // 1_000_000)
|
|
325
|
-
if not isinstance(target, str) or not target:
|
|
326
|
-
raise CommandDispatchError(
|
|
327
|
-
"TARGET_REQUIRED", "target must be a non-empty string"
|
|
328
|
-
)
|
|
329
|
-
if not isinstance(body, str):
|
|
330
|
-
raise CommandDispatchError("BODY_REQUIRED", "body must be text")
|
|
331
|
-
if not isinstance(command_id, str) or not command_id:
|
|
332
|
-
raise CommandDispatchError(
|
|
333
|
-
"COMMAND_ID_REQUIRED", "command_id must be a non-empty string"
|
|
334
|
-
)
|
|
335
|
-
if not isinstance(attachment_paths, list) or not all(
|
|
336
|
-
isinstance(path, str) and path for path in attachment_paths
|
|
337
|
-
):
|
|
338
|
-
raise CommandDispatchError(
|
|
339
|
-
"INVALID_ATTACHMENTS",
|
|
340
|
-
"attachment_paths must be a list of non-empty strings",
|
|
341
|
-
)
|
|
342
|
-
if reply_to_message_id is not None and (
|
|
343
|
-
not isinstance(reply_to_message_id, str) or not reply_to_message_id
|
|
344
|
-
):
|
|
345
|
-
raise CommandDispatchError(
|
|
346
|
-
"INVALID_REPLY_TO",
|
|
347
|
-
"reply_to_message_id must be a non-empty string",
|
|
348
|
-
)
|
|
349
|
-
if (
|
|
350
|
-
isinstance(created_at_ms, bool)
|
|
351
|
-
or not isinstance(created_at_ms, int)
|
|
352
|
-
or created_at_ms < 0
|
|
353
|
-
):
|
|
354
|
-
raise CommandDispatchError(
|
|
355
|
-
"INVALID_CREATED_AT", "created_at_ms must be non-negative"
|
|
356
|
-
)
|
|
474
|
+
request = cast(_MessageSendRequest, request)
|
|
357
475
|
result = await self._service.send(
|
|
358
476
|
session_id=session_id,
|
|
359
|
-
command_id=command_id,
|
|
360
|
-
target=target,
|
|
361
|
-
body=body,
|
|
362
|
-
created_at_ms=created_at_ms,
|
|
363
|
-
attachment_paths=tuple(attachment_paths),
|
|
364
|
-
reply_to_message_id=reply_to_message_id,
|
|
477
|
+
command_id=request.command_id,
|
|
478
|
+
target=request.target,
|
|
479
|
+
body=request.body,
|
|
480
|
+
created_at_ms=request.created_at_ms,
|
|
481
|
+
attachment_paths=tuple(request.attachment_paths),
|
|
482
|
+
reply_to_message_id=request.reply_to_message_id,
|
|
365
483
|
)
|
|
484
|
+
if isinstance(result, MessageSendHandoffRequired):
|
|
485
|
+
return {
|
|
486
|
+
"ok": True,
|
|
487
|
+
"result": {
|
|
488
|
+
"handoff_required": {"target": result.target},
|
|
489
|
+
},
|
|
490
|
+
}
|
|
366
491
|
return {
|
|
367
492
|
"ok": True,
|
|
368
493
|
"result": {"outbound": serialize_outbound(result)},
|
|
369
494
|
}
|
|
370
495
|
|
|
371
496
|
if resource == "thread" and command == "unfollow":
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
raise CommandDispatchError(
|
|
375
|
-
"TARGET_REQUIRED", "target must be a non-empty string"
|
|
376
|
-
)
|
|
377
|
-
changed = await self._service.unfollow(session_id, target=target)
|
|
497
|
+
request = cast(_ThreadUnfollowRequest, request)
|
|
498
|
+
changed = await self._service.unfollow(session_id, target=request.target)
|
|
378
499
|
return {"ok": True, "result": {"changed": changed}}
|
|
379
500
|
|
|
380
501
|
raise AssertionError("validated command route has no handler")
|