oclaw/runtime/hooks/user_config_hooks.py
oliver 420abac9f1 refactor: root-package imports (svc/runtime/interfaces) and fix PYTHONPATH
- Rename platform/ to svc/ to avoid shadowing stdlib platform.
- Replace from oclaw.* with from svc/runtime/interfaces; update -m CLI paths.
- tests/conftest: prepend repo root to sys.path (no parent-folder package name).
- CI: paths and offline_eval script under repo root.
- Ops scripts: PYTHONPATH must be repo root for python -m runtime.* (fixes gateway/WhatsApp sidecar startup).
- Fix default oclaw.json path in tabular/file attachment limits; stabilize attachment test config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-13 14:51:17 +08:00

81 lines
2.3 KiB
Python

from __future__ import annotations
import json
import os
from pathlib import Path
from typing import Any
from svc.config.paths import PROJECT_ROOT
def resolve_hooks_config_storage_path() -> Path:
"""
Path used for persistent ``hooks.internal.entries`` edits.
Matches ``resolve_runtime_config`` file resolution: ``OCLAW_CONFIG_PATH`` (optional
relative to ``PROJECT_ROOT``), else ``<PROJECT_ROOT>/oclaw.json``.
"""
raw = str(os.getenv("OCLAW_CONFIG_PATH") or "").strip()
if raw:
p = Path(raw).expanduser()
if not p.is_absolute():
p = (Path(PROJECT_ROOT) / p).resolve()
return p
return (Path(PROJECT_ROOT) / "oclaw.json").resolve()
def load_storage_config_document() -> dict[str, Any]:
p = resolve_hooks_config_storage_path()
if not p.is_file():
return {}
try:
raw = p.read_text(encoding="utf-8")
obj = json.loads(raw)
return dict(obj) if isinstance(obj, dict) else {}
except Exception:
return {}
def save_storage_config_document(doc: dict[str, Any]) -> None:
p = resolve_hooks_config_storage_path()
p.parent.mkdir(parents=True, exist_ok=True)
tmp = p.with_suffix(p.suffix + ".tmp")
tmp.write_text(json.dumps(doc, indent=2, ensure_ascii=False) + "\n", encoding="utf-8")
tmp.replace(p)
def apply_hook_entry_enabled(
doc: dict[str, Any],
hook_key: str,
enabled: bool,
*,
ensure_internal_hooks_enabled: bool = False,
) -> None:
"""Mutate *doc* in place (shallow structure for ``hooks.internal`` only)."""
key = str(hook_key or "").strip()
if not key:
raise ValueError("hook_key is empty")
hooks = doc.setdefault("hooks", {})
if not isinstance(hooks, dict):
doc["hooks"] = {}
hooks = doc["hooks"]
internal = hooks.setdefault("internal", {})
if not isinstance(internal, dict):
hooks["internal"] = {}
internal = hooks["internal"]
if ensure_internal_hooks_enabled and enabled:
internal["enabled"] = True
entries = internal.setdefault("entries", {})
if not isinstance(entries, dict):
internal["entries"] = {}
entries = internal["entries"]
row = entries.get(key)
if not isinstance(row, dict):
entries[key] = {}
row = entries[key]
row["enabled"] = bool(enabled)