bazaar-compute-node 0.1.3__py3-none-any.whl
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/__init__.py +3 -0
- bazaar_compute_node/app/__init__.py +1 -0
- bazaar_compute_node/app/application.py +398 -0
- bazaar_compute_node/app/attachments.py +154 -0
- bazaar_compute_node/app/command.py +342 -0
- bazaar_compute_node/app/config.py +121 -0
- bazaar_compute_node/app/registry.py +120 -0
- bazaar_compute_node/app/transport.py +264 -0
- bazaar_compute_node/app/windows_pipe.py +463 -0
- bazaar_compute_node/app/wrapper.py +63 -0
- bazaar_compute_node/bcc.py +524 -0
- bazaar_compute_node/cli.py +382 -0
- bazaar_compute_node/contrib/__init__.py +1 -0
- bazaar_compute_node/contrib/codex_app_server/__init__.py +63 -0
- bazaar_compute_node/contrib/codex_app_server/approval.py +168 -0
- bazaar_compute_node/contrib/codex_app_server/client.py +408 -0
- bazaar_compute_node/contrib/codex_app_server/events.py +431 -0
- bazaar_compute_node/contrib/codex_app_server/plugin.py +15 -0
- bazaar_compute_node/contrib/codex_app_server/process.py +583 -0
- bazaar_compute_node/contrib/codex_app_server/protocol.py +103 -0
- bazaar_compute_node/contrib/codex_app_server/runtime.py +513 -0
- bazaar_compute_node/contrib/logging/__init__.py +5 -0
- bazaar_compute_node/contrib/logging/audit.py +61 -0
- bazaar_compute_node/contrib/logging/plugin.py +11 -0
- bazaar_compute_node/contrib/sqlite/__init__.py +14 -0
- bazaar_compute_node/contrib/sqlite/codec.py +768 -0
- bazaar_compute_node/contrib/sqlite/database.py +282 -0
- bazaar_compute_node/contrib/sqlite/migrations.py +646 -0
- bazaar_compute_node/contrib/sqlite/plugin.py +11 -0
- bazaar_compute_node/contrib/sqlite/repository.py +1059 -0
- bazaar_compute_node/contrib/wecom/__init__.py +1 -0
- bazaar_compute_node/contrib/wecom/channel.py +960 -0
- bazaar_compute_node/contrib/wecom/markdown.py +146 -0
- bazaar_compute_node/contrib/wecom/plugin.py +29 -0
- bazaar_compute_node/core/__init__.py +5 -0
- bazaar_compute_node/core/approval.py +51 -0
- bazaar_compute_node/core/audit.py +101 -0
- bazaar_compute_node/core/channel.py +121 -0
- bazaar_compute_node/core/client.py +30 -0
- bazaar_compute_node/core/command.py +85 -0
- bazaar_compute_node/core/concurrency.py +29 -0
- bazaar_compute_node/core/correlation.py +48 -0
- bazaar_compute_node/core/instruction.py +224 -0
- bazaar_compute_node/core/lifecycle.py +48 -0
- bazaar_compute_node/core/models/__init__.py +63 -0
- bazaar_compute_node/core/models/entities.py +514 -0
- bazaar_compute_node/core/models/states.py +369 -0
- bazaar_compute_node/core/observability.py +47 -0
- bazaar_compute_node/core/orchestration/__init__.py +5 -0
- bazaar_compute_node/core/orchestration/command.py +614 -0
- bazaar_compute_node/core/orchestration/services.py +135 -0
- bazaar_compute_node/core/orchestration/session.py +891 -0
- bazaar_compute_node/core/orchestration/turn.py +451 -0
- bazaar_compute_node/core/outcomes.py +51 -0
- bazaar_compute_node/core/paths.py +19 -0
- bazaar_compute_node/core/runtime.py +118 -0
- bazaar_compute_node/core/storage.py +167 -0
- bazaar_compute_node-0.1.3.dist-info/METADATA +178 -0
- bazaar_compute_node-0.1.3.dist-info/RECORD +62 -0
- bazaar_compute_node-0.1.3.dist-info/WHEEL +4 -0
- bazaar_compute_node-0.1.3.dist-info/entry_points.txt +15 -0
- bazaar_compute_node-0.1.3.dist-info/licenses/LICENSE +613 -0
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import asyncio
|
|
4
|
+
import os
|
|
5
|
+
from contextlib import AbstractAsyncContextManager
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from time import time_ns
|
|
9
|
+
from uuid import uuid7
|
|
10
|
+
|
|
11
|
+
import aiosqlite
|
|
12
|
+
|
|
13
|
+
from ...core.paths import resolve_data_dir
|
|
14
|
+
from ...core.storage import NodeIdentity
|
|
15
|
+
from .migrations import (
|
|
16
|
+
MigrationChecksumError,
|
|
17
|
+
MigrationError,
|
|
18
|
+
apply_migrations,
|
|
19
|
+
)
|
|
20
|
+
from .repository import SqliteTransaction
|
|
21
|
+
|
|
22
|
+
DATABASE_FILENAME = "bcn.sqlite3"
|
|
23
|
+
NODE_STATE_KEY = 1
|
|
24
|
+
DEFAULT_BUSY_TIMEOUT_MS = 5_000
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class NodeIdentityError(MigrationError):
|
|
28
|
+
"""The persistent node identity does not match the requested identity."""
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass(frozen=True, slots=True)
|
|
32
|
+
class NodeState:
|
|
33
|
+
node_id: str
|
|
34
|
+
schema_version: int
|
|
35
|
+
workspace_id: str
|
|
36
|
+
created_at_ms: int
|
|
37
|
+
updated_at_ms: int
|
|
38
|
+
metadata_json: str
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class SqliteDatabase:
|
|
42
|
+
"""Persistent SQLite foundation used by the storage repository adapter."""
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def name(self) -> str:
|
|
46
|
+
return "sqlite"
|
|
47
|
+
|
|
48
|
+
def __init__(
|
|
49
|
+
self,
|
|
50
|
+
*,
|
|
51
|
+
busy_timeout_ms: int = DEFAULT_BUSY_TIMEOUT_MS,
|
|
52
|
+
) -> None:
|
|
53
|
+
if (
|
|
54
|
+
isinstance(busy_timeout_ms, bool)
|
|
55
|
+
or not isinstance(busy_timeout_ms, int)
|
|
56
|
+
or busy_timeout_ms <= 0
|
|
57
|
+
):
|
|
58
|
+
raise ValueError("busy_timeout_ms must be a positive integer")
|
|
59
|
+
self.data_dir = resolve_data_dir()
|
|
60
|
+
self.database_path = self.data_dir / DATABASE_FILENAME
|
|
61
|
+
self._busy_timeout_ms = busy_timeout_ms
|
|
62
|
+
self._connection: aiosqlite.Connection | None = None
|
|
63
|
+
self._node_state: NodeState | None = None
|
|
64
|
+
self._schema_version: int | None = None
|
|
65
|
+
self._lifecycle_lock = asyncio.Lock()
|
|
66
|
+
self._transaction_lock = asyncio.Lock()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def node_state(self) -> NodeState:
|
|
70
|
+
if self._node_state is None:
|
|
71
|
+
raise RuntimeError("SQLite node identity has not been initialized")
|
|
72
|
+
return self._node_state
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def node_id(self) -> str:
|
|
76
|
+
return self.node_state.node_id
|
|
77
|
+
|
|
78
|
+
@property
|
|
79
|
+
def workspace_id(self) -> str:
|
|
80
|
+
return self.node_state.workspace_id
|
|
81
|
+
|
|
82
|
+
@property
|
|
83
|
+
def is_started(self) -> bool:
|
|
84
|
+
return self._connection is not None
|
|
85
|
+
|
|
86
|
+
async def start(self, *, timeout: float) -> None:
|
|
87
|
+
if timeout <= 0:
|
|
88
|
+
raise ValueError("timeout must be positive")
|
|
89
|
+
async with self._lifecycle_lock:
|
|
90
|
+
if self._connection is not None:
|
|
91
|
+
return
|
|
92
|
+
connection: aiosqlite.Connection | None = None
|
|
93
|
+
try:
|
|
94
|
+
async with asyncio.timeout(timeout):
|
|
95
|
+
self.data_dir.mkdir(
|
|
96
|
+
parents=True,
|
|
97
|
+
exist_ok=True,
|
|
98
|
+
mode=0o700,
|
|
99
|
+
)
|
|
100
|
+
_restrict_permissions(self.data_dir, 0o700)
|
|
101
|
+
connection = await aiosqlite.connect(
|
|
102
|
+
self.database_path,
|
|
103
|
+
timeout=self._busy_timeout_ms / 1000,
|
|
104
|
+
isolation_level=None,
|
|
105
|
+
)
|
|
106
|
+
connection.row_factory = aiosqlite.Row
|
|
107
|
+
await connection.execute("PRAGMA journal_mode = WAL")
|
|
108
|
+
await connection.execute("PRAGMA synchronous = NORMAL")
|
|
109
|
+
await connection.execute("PRAGMA foreign_keys = ON")
|
|
110
|
+
await connection.execute(
|
|
111
|
+
f"PRAGMA busy_timeout = {self._busy_timeout_ms}"
|
|
112
|
+
)
|
|
113
|
+
_restrict_permissions(self.database_path, 0o600)
|
|
114
|
+
self._connection = connection
|
|
115
|
+
async with SqliteTransaction(self) as transaction:
|
|
116
|
+
self._schema_version = await apply_migrations(
|
|
117
|
+
transaction,
|
|
118
|
+
clock=_current_time_ms,
|
|
119
|
+
)
|
|
120
|
+
except BaseException:
|
|
121
|
+
self._connection = None
|
|
122
|
+
self._node_state = None
|
|
123
|
+
self._schema_version = None
|
|
124
|
+
if connection is not None:
|
|
125
|
+
await connection.close()
|
|
126
|
+
raise
|
|
127
|
+
|
|
128
|
+
async def stop(self, *, timeout: float) -> None:
|
|
129
|
+
if timeout <= 0:
|
|
130
|
+
raise ValueError("timeout must be positive")
|
|
131
|
+
async with self._lifecycle_lock:
|
|
132
|
+
connection = self._connection
|
|
133
|
+
if connection is None:
|
|
134
|
+
return
|
|
135
|
+
try:
|
|
136
|
+
async with asyncio.timeout(timeout):
|
|
137
|
+
async with self._transaction_lock:
|
|
138
|
+
await connection.close()
|
|
139
|
+
finally:
|
|
140
|
+
self._connection = None
|
|
141
|
+
self._node_state = None
|
|
142
|
+
self._schema_version = None
|
|
143
|
+
|
|
144
|
+
async def initialize(
|
|
145
|
+
self,
|
|
146
|
+
*,
|
|
147
|
+
node_id: str | None = None,
|
|
148
|
+
workspace_id: str | None = None,
|
|
149
|
+
) -> NodeIdentity:
|
|
150
|
+
if node_id is not None and (not isinstance(node_id, str) or not node_id):
|
|
151
|
+
raise ValueError("node_id must be a non-empty string")
|
|
152
|
+
if workspace_id is not None and (
|
|
153
|
+
not isinstance(workspace_id, str) or not workspace_id
|
|
154
|
+
):
|
|
155
|
+
raise ValueError("workspace_id must be a non-empty string")
|
|
156
|
+
async with self._lifecycle_lock:
|
|
157
|
+
self._require_connection()
|
|
158
|
+
schema_version = self._schema_version
|
|
159
|
+
if schema_version is None:
|
|
160
|
+
raise RuntimeError("SQLite schema has not been initialized")
|
|
161
|
+
state: NodeState | None = None
|
|
162
|
+
async with SqliteTransaction(self) as transaction:
|
|
163
|
+
state = await self._ensure_node_state(
|
|
164
|
+
transaction,
|
|
165
|
+
schema_version,
|
|
166
|
+
requested_node_id=node_id,
|
|
167
|
+
requested_workspace_id=workspace_id,
|
|
168
|
+
)
|
|
169
|
+
if state is None:
|
|
170
|
+
raise RuntimeError("SQLite node initialization did not create state")
|
|
171
|
+
self._node_state = state
|
|
172
|
+
return NodeIdentity(
|
|
173
|
+
node_id=state.node_id,
|
|
174
|
+
workspace_id=state.workspace_id,
|
|
175
|
+
)
|
|
176
|
+
|
|
177
|
+
def transaction(self) -> AbstractAsyncContextManager[SqliteTransaction]:
|
|
178
|
+
return SqliteTransaction(self)
|
|
179
|
+
|
|
180
|
+
def _require_connection(self) -> aiosqlite.Connection:
|
|
181
|
+
if self._connection is None:
|
|
182
|
+
raise RuntimeError("SQLite database has not been started")
|
|
183
|
+
return self._connection
|
|
184
|
+
|
|
185
|
+
async def _ensure_node_state(
|
|
186
|
+
self,
|
|
187
|
+
transaction: SqliteTransaction,
|
|
188
|
+
schema_version: int,
|
|
189
|
+
*,
|
|
190
|
+
requested_node_id: str | None,
|
|
191
|
+
requested_workspace_id: str | None,
|
|
192
|
+
) -> NodeState:
|
|
193
|
+
row = await transaction.fetchone(
|
|
194
|
+
"SELECT node_id, schema_version, workspace_id, created_at_ms, "
|
|
195
|
+
"updated_at_ms, metadata_json FROM node_state "
|
|
196
|
+
"WHERE singleton_key = ?",
|
|
197
|
+
(NODE_STATE_KEY,),
|
|
198
|
+
)
|
|
199
|
+
now_ms = _current_time_ms()
|
|
200
|
+
if row is None:
|
|
201
|
+
node_id = requested_node_id or f"bcn-node-{uuid7()}"
|
|
202
|
+
workspace_id = requested_workspace_id or str(uuid7())
|
|
203
|
+
metadata_json = "{}"
|
|
204
|
+
await transaction.execute(
|
|
205
|
+
"INSERT INTO node_state "
|
|
206
|
+
"(singleton_key, node_id, schema_version, workspace_id, "
|
|
207
|
+
"created_at_ms, updated_at_ms, metadata_json) "
|
|
208
|
+
"VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
209
|
+
(
|
|
210
|
+
NODE_STATE_KEY,
|
|
211
|
+
node_id,
|
|
212
|
+
schema_version,
|
|
213
|
+
workspace_id,
|
|
214
|
+
now_ms,
|
|
215
|
+
now_ms,
|
|
216
|
+
metadata_json,
|
|
217
|
+
),
|
|
218
|
+
)
|
|
219
|
+
return NodeState(
|
|
220
|
+
node_id=node_id,
|
|
221
|
+
schema_version=schema_version,
|
|
222
|
+
workspace_id=workspace_id,
|
|
223
|
+
created_at_ms=now_ms,
|
|
224
|
+
updated_at_ms=now_ms,
|
|
225
|
+
metadata_json=metadata_json,
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
node_id = row["node_id"]
|
|
229
|
+
workspace_id = row["workspace_id"]
|
|
230
|
+
if not isinstance(node_id, str) or not node_id:
|
|
231
|
+
raise NodeIdentityError("persistent node_id is missing")
|
|
232
|
+
if not isinstance(workspace_id, str) or not workspace_id:
|
|
233
|
+
raise NodeIdentityError("persistent workspace_id is missing")
|
|
234
|
+
if requested_node_id is not None and node_id != requested_node_id:
|
|
235
|
+
raise NodeIdentityError(
|
|
236
|
+
f"requested node_id does not match persisted node_id: {node_id}"
|
|
237
|
+
)
|
|
238
|
+
if (
|
|
239
|
+
requested_workspace_id is not None
|
|
240
|
+
and workspace_id != requested_workspace_id
|
|
241
|
+
):
|
|
242
|
+
raise NodeIdentityError(
|
|
243
|
+
"requested workspace_id does not match the persisted workspace_id"
|
|
244
|
+
)
|
|
245
|
+
if row["schema_version"] != schema_version:
|
|
246
|
+
await transaction.execute(
|
|
247
|
+
"UPDATE node_state SET schema_version = ?, updated_at_ms = ? "
|
|
248
|
+
"WHERE singleton_key = ?",
|
|
249
|
+
(schema_version, now_ms, NODE_STATE_KEY),
|
|
250
|
+
)
|
|
251
|
+
updated_at_ms = now_ms
|
|
252
|
+
else:
|
|
253
|
+
updated_at_ms = int(row["updated_at_ms"])
|
|
254
|
+
return NodeState(
|
|
255
|
+
node_id=node_id,
|
|
256
|
+
schema_version=schema_version,
|
|
257
|
+
workspace_id=workspace_id,
|
|
258
|
+
created_at_ms=int(row["created_at_ms"]),
|
|
259
|
+
updated_at_ms=updated_at_ms,
|
|
260
|
+
metadata_json=row["metadata_json"] or "{}",
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
|
|
264
|
+
def _current_time_ms() -> int:
|
|
265
|
+
return time_ns() // 1_000_000
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
def _restrict_permissions(path: Path, mode: int) -> None:
|
|
269
|
+
if os.name != "nt":
|
|
270
|
+
path.chmod(mode)
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
__all__ = [
|
|
274
|
+
"DATABASE_FILENAME",
|
|
275
|
+
"DEFAULT_BUSY_TIMEOUT_MS",
|
|
276
|
+
"MigrationChecksumError",
|
|
277
|
+
"MigrationError",
|
|
278
|
+
"NodeIdentityError",
|
|
279
|
+
"NodeState",
|
|
280
|
+
"SqliteDatabase",
|
|
281
|
+
"SqliteTransaction",
|
|
282
|
+
]
|