Spaces:
Running
Running
Upload 193 files
Browse files- app.py +29 -0
- openspace/__pycache__/__init__.cpython-311.pyc +0 -0
- openspace/__pycache__/dashboard_server.cpython-311.pyc +0 -0
- openspace/__pycache__/mcp_server.cpython-311.pyc +0 -0
- openspace/cloud/__pycache__/__init__.cpython-311.pyc +0 -0
- openspace/cloud/__pycache__/auth.cpython-311.pyc +0 -0
- openspace/cloud/__pycache__/client.cpython-311.pyc +0 -0
- openspace/cloud/cli/__pycache__/__init__.cpython-311.pyc +0 -0
- openspace/cloud/cli/__pycache__/upload_skill.cpython-311.pyc +0 -0
- openspace/config/__pycache__/__init__.cpython-311.pyc +0 -0
- openspace/config/__pycache__/constants.cpython-311.pyc +0 -0
- openspace/config/__pycache__/grounding.cpython-311.pyc +0 -0
- openspace/config/__pycache__/loader.cpython-311.pyc +0 -0
- openspace/config/__pycache__/utils.cpython-311.pyc +0 -0
- openspace/dashboard_server.py +61 -4
- openspace/grounding/core/__pycache__/types.cpython-311.pyc +0 -0
- openspace/host_detection/__pycache__/__init__.cpython-311.pyc +0 -0
- openspace/host_detection/__pycache__/nanobot.cpython-311.pyc +0 -0
- openspace/host_detection/__pycache__/openclaw.cpython-311.pyc +0 -0
- openspace/host_detection/__pycache__/resolver.cpython-311.pyc +0 -0
- openspace/recording/__pycache__/__init__.cpython-311.pyc +0 -0
- openspace/recording/__pycache__/action_recorder.cpython-311.pyc +0 -0
- openspace/recording/__pycache__/utils.cpython-311.pyc +0 -0
- openspace/skill_engine/__pycache__/__init__.cpython-311.pyc +0 -0
- openspace/skill_engine/__pycache__/fuzzy_match.cpython-311.pyc +0 -0
- openspace/skill_engine/__pycache__/patch.cpython-311.pyc +0 -0
- openspace/skill_engine/__pycache__/registry.cpython-311.pyc +0 -0
- openspace/skill_engine/__pycache__/skill_ranker.cpython-311.pyc +0 -0
- openspace/skill_engine/__pycache__/skill_utils.cpython-311.pyc +0 -0
- openspace/skill_engine/__pycache__/store.cpython-311.pyc +0 -0
- openspace/skill_engine/__pycache__/types.cpython-311.pyc +0 -0
- openspace/skill_engine/store.py +36 -117
- openspace/utils/__pycache__/logging.cpython-311.pyc +0 -0
- requirements.txt +1 -1
app.py
CHANGED
|
@@ -1,5 +1,34 @@
|
|
| 1 |
import os
|
|
|
|
|
|
|
| 2 |
from openspace.dashboard_server import create_app
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 3 |
|
| 4 |
app = create_app()
|
| 5 |
|
|
|
|
| 1 |
import os
|
| 2 |
+
import asyncio
|
| 3 |
+
from pathlib import Path
|
| 4 |
from openspace.dashboard_server import create_app
|
| 5 |
+
from openspace.skill_engine.registry import SkillRegistry
|
| 6 |
+
from openspace.skill_engine.store import SkillStore
|
| 7 |
+
|
| 8 |
+
def sync_local_skills():
|
| 9 |
+
try:
|
| 10 |
+
print("Starting local skill sync...")
|
| 11 |
+
registry = SkillRegistry()
|
| 12 |
+
skill_dir = Path(__file__).resolve().parent / "skills"
|
| 13 |
+
if skill_dir.exists() and skill_dir.is_dir():
|
| 14 |
+
added = registry.discover_from_dirs([skill_dir])
|
| 15 |
+
if added:
|
| 16 |
+
store = SkillStore()
|
| 17 |
+
# Run the async sync method in a synchronous context
|
| 18 |
+
loop = asyncio.new_event_loop()
|
| 19 |
+
asyncio.set_event_loop(loop)
|
| 20 |
+
count = loop.run_until_complete(store.sync_from_registry(added))
|
| 21 |
+
loop.close()
|
| 22 |
+
print(f"Successfully synced {count} skills from {skill_dir} into database.")
|
| 23 |
+
else:
|
| 24 |
+
print("No skills discovered in directory.")
|
| 25 |
+
else:
|
| 26 |
+
print(f"Skill directory not found at {skill_dir}")
|
| 27 |
+
except Exception as e:
|
| 28 |
+
print(f"Error syncing local skills: {e}")
|
| 29 |
+
|
| 30 |
+
# Run sync before creating the app
|
| 31 |
+
sync_local_skills()
|
| 32 |
|
| 33 |
app = create_app()
|
| 34 |
|
openspace/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (2.5 kB). View file
|
|
|
openspace/__pycache__/dashboard_server.cpython-311.pyc
ADDED
|
Binary file (44.9 kB). View file
|
|
|
openspace/__pycache__/mcp_server.cpython-311.pyc
ADDED
|
Binary file (44.6 kB). View file
|
|
|
openspace/cloud/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.27 kB). View file
|
|
|
openspace/cloud/__pycache__/auth.cpython-311.pyc
ADDED
|
Binary file (4.79 kB). View file
|
|
|
openspace/cloud/__pycache__/client.cpython-311.pyc
ADDED
|
Binary file (25.9 kB). View file
|
|
|
openspace/cloud/cli/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (174 Bytes). View file
|
|
|
openspace/cloud/cli/__pycache__/upload_skill.cpython-311.pyc
ADDED
|
Binary file (5.93 kB). View file
|
|
|
openspace/config/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (672 Bytes). View file
|
|
|
openspace/config/__pycache__/constants.cpython-311.pyc
ADDED
|
Binary file (690 Bytes). View file
|
|
|
openspace/config/__pycache__/grounding.cpython-311.pyc
ADDED
|
Binary file (18.2 kB). View file
|
|
|
openspace/config/__pycache__/loader.cpython-311.pyc
ADDED
|
Binary file (8.7 kB). View file
|
|
|
openspace/config/__pycache__/utils.cpython-311.pyc
ADDED
|
Binary file (2.22 kB). View file
|
|
|
openspace/dashboard_server.py
CHANGED
|
@@ -234,8 +234,18 @@ def create_app() -> Flask:
|
|
| 234 |
if not stage_dir.exists():
|
| 235 |
abort(404, description="Artifact not found")
|
| 236 |
|
| 237 |
-
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
final_dir = PROJECT_ROOT / "skills" / skill_name
|
| 240 |
|
| 241 |
# If final_dir exists, we might need to overwrite or abort
|
|
@@ -260,21 +270,68 @@ def create_app() -> Flask:
|
|
| 260 |
|
| 261 |
store = _get_store()
|
| 262 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 263 |
# Sync to DB
|
| 264 |
import asyncio
|
| 265 |
loop = asyncio.new_event_loop()
|
| 266 |
asyncio.set_event_loop(loop)
|
| 267 |
try:
|
| 268 |
-
loop.run_until_complete(store.
|
| 269 |
finally:
|
| 270 |
loop.close()
|
| 271 |
|
| 272 |
return jsonify({
|
| 273 |
"status": "success",
|
|
|
|
| 274 |
"skill_id": meta.skill_id,
|
| 275 |
"name": meta.name,
|
| 276 |
"local_path": str(final_dir)
|
| 277 |
-
})
|
| 278 |
|
| 279 |
@app.route(f"{API_PREFIX}/workflows", methods=["GET"])
|
| 280 |
def list_workflows() -> Any:
|
|
|
|
| 234 |
if not stage_dir.exists():
|
| 235 |
abort(404, description="Artifact not found")
|
| 236 |
|
| 237 |
+
from openspace.skill_engine.skill_utils import parse_frontmatter
|
| 238 |
+
skill_file = stage_dir / "SKILL.md"
|
| 239 |
+
if not skill_file.exists():
|
| 240 |
+
abort(400, description="Missing SKILL.md")
|
| 241 |
+
|
| 242 |
+
try:
|
| 243 |
+
content = skill_file.read_text(encoding="utf-8")
|
| 244 |
+
fm = parse_frontmatter(content)
|
| 245 |
+
skill_name = fm.get("name", artifact_id)
|
| 246 |
+
except Exception:
|
| 247 |
+
skill_name = artifact_id
|
| 248 |
+
|
| 249 |
final_dir = PROJECT_ROOT / "skills" / skill_name
|
| 250 |
|
| 251 |
# If final_dir exists, we might need to overwrite or abort
|
|
|
|
| 270 |
|
| 271 |
store = _get_store()
|
| 272 |
|
| 273 |
+
from openspace.skill_engine.types import SkillRecord, SkillLineage, SkillOrigin, SkillVisibility
|
| 274 |
+
from openspace.skill_engine.patch import collect_skill_snapshot, compute_unified_diff
|
| 275 |
+
|
| 276 |
+
try:
|
| 277 |
+
origin_val = data.get("origin", "imported").upper()
|
| 278 |
+
origin = SkillOrigin[origin_val]
|
| 279 |
+
except KeyError:
|
| 280 |
+
origin = SkillOrigin.IMPORTED
|
| 281 |
+
|
| 282 |
+
try:
|
| 283 |
+
vis_val = data.get("visibility", "public").upper()
|
| 284 |
+
if vis_val == "GROUP_ONLY":
|
| 285 |
+
vis_val = "PRIVATE"
|
| 286 |
+
visibility = SkillVisibility[vis_val]
|
| 287 |
+
except KeyError:
|
| 288 |
+
visibility = SkillVisibility.PUBLIC
|
| 289 |
+
|
| 290 |
+
snapshot = {}
|
| 291 |
+
content_diff = ""
|
| 292 |
+
try:
|
| 293 |
+
snapshot = collect_skill_snapshot(final_dir)
|
| 294 |
+
content_diff = "\n".join(
|
| 295 |
+
compute_unified_diff("", text, filename=name)
|
| 296 |
+
for name, text in sorted(snapshot.items())
|
| 297 |
+
if compute_unified_diff("", text, filename=name)
|
| 298 |
+
)
|
| 299 |
+
except Exception:
|
| 300 |
+
pass
|
| 301 |
+
|
| 302 |
+
record = SkillRecord(
|
| 303 |
+
skill_id=meta.skill_id,
|
| 304 |
+
name=meta.name,
|
| 305 |
+
description=meta.description,
|
| 306 |
+
path=str(meta.path),
|
| 307 |
+
is_active=True,
|
| 308 |
+
visibility=visibility,
|
| 309 |
+
tags=set(data.get("tags", [])),
|
| 310 |
+
lineage=SkillLineage(
|
| 311 |
+
origin=origin,
|
| 312 |
+
generation=0,
|
| 313 |
+
parent_skill_ids=data.get("parent_skill_ids", []),
|
| 314 |
+
content_snapshot=snapshot,
|
| 315 |
+
content_diff=content_diff,
|
| 316 |
+
),
|
| 317 |
+
)
|
| 318 |
+
|
| 319 |
# Sync to DB
|
| 320 |
import asyncio
|
| 321 |
loop = asyncio.new_event_loop()
|
| 322 |
asyncio.set_event_loop(loop)
|
| 323 |
try:
|
| 324 |
+
loop.run_until_complete(store.save_record(record))
|
| 325 |
finally:
|
| 326 |
loop.close()
|
| 327 |
|
| 328 |
return jsonify({
|
| 329 |
"status": "success",
|
| 330 |
+
"record_id": meta.skill_id,
|
| 331 |
"skill_id": meta.skill_id,
|
| 332 |
"name": meta.name,
|
| 333 |
"local_path": str(final_dir)
|
| 334 |
+
}), 201
|
| 335 |
|
| 336 |
@app.route(f"{API_PREFIX}/workflows", methods=["GET"])
|
| 337 |
def list_workflows() -> Any:
|
openspace/grounding/core/__pycache__/types.cpython-311.pyc
ADDED
|
Binary file (15.1 kB). View file
|
|
|
openspace/host_detection/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (3.17 kB). View file
|
|
|
openspace/host_detection/__pycache__/nanobot.cpython-311.pyc
ADDED
|
Binary file (9.95 kB). View file
|
|
|
openspace/host_detection/__pycache__/openclaw.cpython-311.pyc
ADDED
|
Binary file (6.47 kB). View file
|
|
|
openspace/host_detection/__pycache__/resolver.cpython-311.pyc
ADDED
|
Binary file (9.78 kB). View file
|
|
|
openspace/recording/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.83 kB). View file
|
|
|
openspace/recording/__pycache__/action_recorder.cpython-311.pyc
ADDED
|
Binary file (12.4 kB). View file
|
|
|
openspace/recording/__pycache__/utils.cpython-311.pyc
ADDED
|
Binary file (24 kB). View file
|
|
|
openspace/skill_engine/__pycache__/__init__.cpython-311.pyc
ADDED
|
Binary file (1.93 kB). View file
|
|
|
openspace/skill_engine/__pycache__/fuzzy_match.cpython-311.pyc
ADDED
|
Binary file (17.2 kB). View file
|
|
|
openspace/skill_engine/__pycache__/patch.cpython-311.pyc
ADDED
|
Binary file (47.1 kB). View file
|
|
|
openspace/skill_engine/__pycache__/registry.cpython-311.pyc
ADDED
|
Binary file (36.6 kB). View file
|
|
|
openspace/skill_engine/__pycache__/skill_ranker.cpython-311.pyc
ADDED
|
Binary file (20.5 kB). View file
|
|
|
openspace/skill_engine/__pycache__/skill_utils.cpython-311.pyc
ADDED
|
Binary file (15.3 kB). View file
|
|
|
openspace/skill_engine/__pycache__/store.cpython-311.pyc
ADDED
|
Binary file (76 kB). View file
|
|
|
openspace/skill_engine/__pycache__/types.cpython-311.pyc
ADDED
|
Binary file (25.5 kB). View file
|
|
|
openspace/skill_engine/store.py
CHANGED
|
@@ -13,8 +13,7 @@ from __future__ import annotations
|
|
| 13 |
|
| 14 |
import asyncio
|
| 15 |
import json
|
| 16 |
-
import
|
| 17 |
-
import libsql_experimental as sqlite3
|
| 18 |
import threading
|
| 19 |
import time
|
| 20 |
from contextlib import contextmanager
|
|
@@ -58,26 +57,19 @@ def _db_retry(
|
|
| 58 |
for attempt in range(max_retries):
|
| 59 |
try:
|
| 60 |
return func(*args, **kwargs)
|
| 61 |
-
except
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
| 66 |
-
if attempt == max_retries - 1:
|
| 67 |
-
logger.error(
|
| 68 |
-
f"DB {func.__name__} failed after "
|
| 69 |
-
f"{max_retries} retries: {exc}"
|
| 70 |
-
)
|
| 71 |
-
raise
|
| 72 |
-
logger.warning(
|
| 73 |
-
f"DB {func.__name__} retry {attempt + 1}"
|
| 74 |
-
f"/{max_retries}: {exc}"
|
| 75 |
)
|
| 76 |
-
time.sleep(delay)
|
| 77 |
-
delay *= backoff
|
| 78 |
-
else:
|
| 79 |
-
# If it's not a known transient error, raise immediately
|
| 80 |
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
|
| 82 |
return wrapper
|
| 83 |
|
|
@@ -173,69 +165,6 @@ CREATE TABLE IF NOT EXISTS skill_tags (
|
|
| 173 |
"""
|
| 174 |
|
| 175 |
|
| 176 |
-
class _DictCursorWrapper:
|
| 177 |
-
def __init__(self, cursor):
|
| 178 |
-
self._cursor = cursor
|
| 179 |
-
|
| 180 |
-
def __getattr__(self, name):
|
| 181 |
-
return getattr(self._cursor, name)
|
| 182 |
-
|
| 183 |
-
def _dict_factory(self, row):
|
| 184 |
-
if row is None:
|
| 185 |
-
return None
|
| 186 |
-
if self._cursor.description is None:
|
| 187 |
-
return row
|
| 188 |
-
fields = [column[0] for column in self._cursor.description]
|
| 189 |
-
return {key: value for key, value in zip(fields, row)}
|
| 190 |
-
|
| 191 |
-
def fetchone(self):
|
| 192 |
-
return self._dict_factory(self._cursor.fetchone())
|
| 193 |
-
|
| 194 |
-
def fetchall(self):
|
| 195 |
-
rows = self._cursor.fetchall()
|
| 196 |
-
if not rows:
|
| 197 |
-
return []
|
| 198 |
-
return [self._dict_factory(row) for row in rows]
|
| 199 |
-
|
| 200 |
-
def fetchmany(self, size=None):
|
| 201 |
-
rows = self._cursor.fetchmany(size) if size is not None else self._cursor.fetchmany()
|
| 202 |
-
if not rows:
|
| 203 |
-
return []
|
| 204 |
-
return [self._dict_factory(row) for row in rows]
|
| 205 |
-
|
| 206 |
-
def __iter__(self):
|
| 207 |
-
for row in self._cursor:
|
| 208 |
-
yield self._dict_factory(row)
|
| 209 |
-
|
| 210 |
-
class _DictConnectionWrapper:
|
| 211 |
-
def __init__(self, conn):
|
| 212 |
-
self._conn = conn
|
| 213 |
-
|
| 214 |
-
def __getattr__(self, name):
|
| 215 |
-
return getattr(self._conn, name)
|
| 216 |
-
|
| 217 |
-
def cursor(self):
|
| 218 |
-
return _DictCursorWrapper(self._conn.cursor())
|
| 219 |
-
|
| 220 |
-
def execute(self, *args, **kwargs):
|
| 221 |
-
return _DictCursorWrapper(self._conn.execute(*args, **kwargs))
|
| 222 |
-
|
| 223 |
-
def executemany(self, *args, **kwargs):
|
| 224 |
-
return _DictCursorWrapper(self._conn.executemany(*args, **kwargs))
|
| 225 |
-
|
| 226 |
-
def executescript(self, *args, **kwargs):
|
| 227 |
-
return _DictCursorWrapper(self._conn.executescript(*args, **kwargs))
|
| 228 |
-
|
| 229 |
-
def __enter__(self):
|
| 230 |
-
if hasattr(self._conn, '__enter__'):
|
| 231 |
-
self._conn.__enter__()
|
| 232 |
-
return self
|
| 233 |
-
|
| 234 |
-
def __exit__(self, exc_type, exc_val, exc_tb):
|
| 235 |
-
if hasattr(self._conn, '__exit__'):
|
| 236 |
-
return self._conn.__exit__(exc_type, exc_val, exc_tb)
|
| 237 |
-
return False
|
| 238 |
-
|
| 239 |
class SkillStore:
|
| 240 |
"""SQLite persistence engine — Skill quality tracking and evolution ledger.
|
| 241 |
|
|
@@ -276,29 +205,21 @@ class SkillStore:
|
|
| 276 |
|
| 277 |
Read connection: ``query_only=ON`` pragma for safety.
|
| 278 |
"""
|
| 279 |
-
|
| 280 |
-
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
|
| 285 |
-
|
| 286 |
-
|
| 287 |
-
|
| 288 |
-
|
| 289 |
-
|
| 290 |
-
|
| 291 |
-
conn.execute("PRAGMA
|
| 292 |
-
|
| 293 |
-
|
| 294 |
-
conn.execute("PRAGMA temp_store=MEMORY")
|
| 295 |
-
conn.execute("PRAGMA foreign_keys=ON")
|
| 296 |
-
if read_only:
|
| 297 |
-
conn.execute("PRAGMA query_only=ON")
|
| 298 |
-
|
| 299 |
-
# libsql-experimental doesn't support setting row_factory directly on the connection
|
| 300 |
-
# We wrap it to handle row-to-dict conversion manually where cursor.fetchall() is called
|
| 301 |
-
return _DictConnectionWrapper(conn)
|
| 302 |
|
| 303 |
@contextmanager
|
| 304 |
def _reader(self) -> Generator[sqlite3.Connection, None, None]:
|
|
@@ -357,9 +278,7 @@ class SkillStore:
|
|
| 357 |
self._closed = True
|
| 358 |
try:
|
| 359 |
# Flush WAL → main DB so external readers see all data
|
| 360 |
-
|
| 361 |
-
if not db_url:
|
| 362 |
-
self._conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
|
| 363 |
self._conn.close()
|
| 364 |
except Exception:
|
| 365 |
pass
|
|
@@ -978,8 +897,8 @@ class SkillStore:
|
|
| 978 |
with self._reader() as conn:
|
| 979 |
where = " WHERE is_active=1" if active_only else ""
|
| 980 |
total = conn.execute(
|
| 981 |
-
f"SELECT COUNT(*)
|
| 982 |
-
).fetchone()[
|
| 983 |
|
| 984 |
by_category = {
|
| 985 |
r["category"]: r["cnt"]
|
|
@@ -996,12 +915,12 @@ class SkillStore:
|
|
| 996 |
).fetchall()
|
| 997 |
}
|
| 998 |
n_analyses = conn.execute(
|
| 999 |
-
"SELECT COUNT(*)
|
| 1000 |
-
).fetchone()[
|
| 1001 |
n_candidates = conn.execute(
|
| 1002 |
-
"SELECT COUNT(*)
|
| 1003 |
"WHERE candidate_for_evolution=1"
|
| 1004 |
-
).fetchone()[
|
| 1005 |
agg = conn.execute(
|
| 1006 |
f"""
|
| 1007 |
SELECT SUM(total_selections) AS sel,
|
|
@@ -1014,8 +933,8 @@ class SkillStore:
|
|
| 1014 |
|
| 1015 |
# Also report total (including inactive) for context
|
| 1016 |
total_all = conn.execute(
|
| 1017 |
-
"SELECT COUNT(*)
|
| 1018 |
-
).fetchone()[
|
| 1019 |
|
| 1020 |
return {
|
| 1021 |
"total_skills": total,
|
|
|
|
| 13 |
|
| 14 |
import asyncio
|
| 15 |
import json
|
| 16 |
+
import sqlite3
|
|
|
|
| 17 |
import threading
|
| 18 |
import time
|
| 19 |
from contextlib import contextmanager
|
|
|
|
| 57 |
for attempt in range(max_retries):
|
| 58 |
try:
|
| 59 |
return func(*args, **kwargs)
|
| 60 |
+
except (sqlite3.OperationalError, sqlite3.DatabaseError) as exc:
|
| 61 |
+
if attempt == max_retries - 1:
|
| 62 |
+
logger.error(
|
| 63 |
+
f"DB {func.__name__} failed after "
|
| 64 |
+
f"{max_retries} retries: {exc}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 66 |
raise
|
| 67 |
+
logger.warning(
|
| 68 |
+
f"DB {func.__name__} retry {attempt + 1}"
|
| 69 |
+
f"/{max_retries}: {exc}"
|
| 70 |
+
)
|
| 71 |
+
time.sleep(delay)
|
| 72 |
+
delay *= backoff
|
| 73 |
|
| 74 |
return wrapper
|
| 75 |
|
|
|
|
| 165 |
"""
|
| 166 |
|
| 167 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
class SkillStore:
|
| 169 |
"""SQLite persistence engine — Skill quality tracking and evolution ledger.
|
| 170 |
|
|
|
|
| 205 |
|
| 206 |
Read connection: ``query_only=ON`` pragma for safety.
|
| 207 |
"""
|
| 208 |
+
conn = sqlite3.connect(
|
| 209 |
+
str(self._db_path),
|
| 210 |
+
timeout=30.0,
|
| 211 |
+
check_same_thread=False,
|
| 212 |
+
)
|
| 213 |
+
conn.execute("PRAGMA journal_mode=WAL")
|
| 214 |
+
conn.execute("PRAGMA busy_timeout=30000")
|
| 215 |
+
conn.execute("PRAGMA synchronous=NORMAL")
|
| 216 |
+
conn.execute("PRAGMA cache_size=-16000") # 16 MB
|
| 217 |
+
conn.execute("PRAGMA temp_store=MEMORY")
|
| 218 |
+
conn.execute("PRAGMA foreign_keys=ON")
|
| 219 |
+
if read_only:
|
| 220 |
+
conn.execute("PRAGMA query_only=ON")
|
| 221 |
+
conn.row_factory = sqlite3.Row
|
| 222 |
+
return conn
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 223 |
|
| 224 |
@contextmanager
|
| 225 |
def _reader(self) -> Generator[sqlite3.Connection, None, None]:
|
|
|
|
| 278 |
self._closed = True
|
| 279 |
try:
|
| 280 |
# Flush WAL → main DB so external readers see all data
|
| 281 |
+
self._conn.execute("PRAGMA wal_checkpoint(TRUNCATE)")
|
|
|
|
|
|
|
| 282 |
self._conn.close()
|
| 283 |
except Exception:
|
| 284 |
pass
|
|
|
|
| 897 |
with self._reader() as conn:
|
| 898 |
where = " WHERE is_active=1" if active_only else ""
|
| 899 |
total = conn.execute(
|
| 900 |
+
f"SELECT COUNT(*) FROM skill_records{where}"
|
| 901 |
+
).fetchone()[0]
|
| 902 |
|
| 903 |
by_category = {
|
| 904 |
r["category"]: r["cnt"]
|
|
|
|
| 915 |
).fetchall()
|
| 916 |
}
|
| 917 |
n_analyses = conn.execute(
|
| 918 |
+
"SELECT COUNT(*) FROM execution_analyses"
|
| 919 |
+
).fetchone()[0]
|
| 920 |
n_candidates = conn.execute(
|
| 921 |
+
"SELECT COUNT(*) FROM execution_analyses "
|
| 922 |
"WHERE candidate_for_evolution=1"
|
| 923 |
+
).fetchone()[0]
|
| 924 |
agg = conn.execute(
|
| 925 |
f"""
|
| 926 |
SELECT SUM(total_selections) AS sel,
|
|
|
|
| 933 |
|
| 934 |
# Also report total (including inactive) for context
|
| 935 |
total_all = conn.execute(
|
| 936 |
+
"SELECT COUNT(*) FROM skill_records"
|
| 937 |
+
).fetchone()[0]
|
| 938 |
|
| 939 |
return {
|
| 940 |
"total_skills": total,
|
openspace/utils/__pycache__/logging.cpython-311.pyc
ADDED
|
Binary file (15.9 kB). View file
|
|
|
requirements.txt
CHANGED
|
@@ -12,7 +12,7 @@ colorama>=0.4.6
|
|
| 12 |
# Local server dependencies (cross-platform)
|
| 13 |
flask>=3.1.0
|
| 14 |
flask-cors>=4.0.0
|
| 15 |
-
pyautogui>=0.9.54
|
| 16 |
pydantic>=2.12.0
|
| 17 |
requests>=2.32.0
|
| 18 |
libsql-experimental
|
|
|
|
| 12 |
# Local server dependencies (cross-platform)
|
| 13 |
flask>=3.1.0
|
| 14 |
flask-cors>=4.0.0
|
| 15 |
+
# pyautogui>=0.9.54 # Removed for cloud deployment
|
| 16 |
pydantic>=2.12.0
|
| 17 |
requests>=2.32.0
|
| 18 |
libsql-experimental
|