mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-09 01:50:44 +08:00
- Add SQLAlchemy Core repos, pg adapter/compat, assistant_store factory, Alembic bootstrap and migration/cutover scripts. - Harden chat_message writes (NUL scrub for PG), turn_uuid on attempt failure, WS turn_runner fallbacks and gateway executed_turn_uuid init. - start_gateway: log paths, PS7 stderr handling via cmd, background stdout/stderr redirect; runtime assistant_runtime_log_dir export. - Ops: clear_all_chat_sessions with PG-only --postgresql and env-gated wipe; clear_postgres_chat_sessions.ps1. - Tests: SA repos, pg compat, persist fallback, smoke env isolation; CI and docs touch-ups. Co-authored-by: Cursor <cursoragent@cursor.com>
178 lines
5.7 KiB
Python
178 lines
5.7 KiB
Python
#!/usr/bin/env python3
|
||
"""HTTP 冒烟:加载 _local/system.env → 用当前配置的 PG → 登录 → 建会话 → POST 一条消息 → 拉历史。
|
||
|
||
不依赖外网 LLM:强制 ``AIA_ASSISTANT_MODE=rule``(本地规则回复)。
|
||
|
||
用法(仓库根)::
|
||
|
||
python runtime/operations/scripts/smoke_admin_chat_postgres.py
|
||
|
||
退出码 0 表示全流程成功且 ``messages`` 里同时有 user 与 assistant。
|
||
"""
|
||
|
||
from __future__ import annotations
|
||
|
||
import hashlib
|
||
import os
|
||
import shutil
|
||
import sys
|
||
import tempfile
|
||
import uuid
|
||
from pathlib import Path
|
||
|
||
|
||
def _repo_root() -> Path:
|
||
return Path(__file__).resolve().parents[3]
|
||
|
||
|
||
def main() -> int:
|
||
root = _repo_root()
|
||
os.chdir(root)
|
||
if str(root) not in sys.path:
|
||
sys.path.insert(0, str(root))
|
||
|
||
from svc.config.bootstrap_env import load_system_env
|
||
|
||
load_system_env(force=True)
|
||
|
||
if (os.getenv("AIA_ASSISTANT_DB_BACKEND") or "").strip().lower() not in (
|
||
"postgresql",
|
||
"pg",
|
||
"postgres",
|
||
):
|
||
print("AIA_ASSISTANT_DB_BACKEND 不是 postgresql,本脚本用于测 PG。", file=sys.stderr)
|
||
return 2
|
||
|
||
# 不覆盖已有 shell 变量;仅保证规则模式(免外网)
|
||
os.environ.setdefault("AIA_ASSISTANT_MODE", "rule")
|
||
os.environ["AIA_ASSISTANT_MODE"] = "rule"
|
||
|
||
tmp = tempfile.mkdtemp(prefix="oclaw-smoke-ws-")
|
||
os.environ["OPS_WORKSPACE_ROOT"] = tmp
|
||
|
||
from svc.config import database as db_cfg
|
||
from svc.persistence.assistant_store import get_assistant_store, reset_assistant_store_singleton
|
||
from svc.persistence.db.engine import clear_assistant_engine_cache
|
||
|
||
clear_assistant_engine_cache()
|
||
reset_assistant_store_singleton()
|
||
|
||
if db_cfg.assistant_db_backend() != "postgresql":
|
||
print("assistant_db_backend() 不是 postgresql", file=sys.stderr)
|
||
return 2
|
||
|
||
from svc.persistence.sqlite_store import (
|
||
LLM_BUILTIN_RULE_PROFILE_ID,
|
||
SqliteStore,
|
||
active_llm_profile_setting_key,
|
||
)
|
||
|
||
store = get_assistant_store()
|
||
assert isinstance(store, SqliteStore) and store._use_pg
|
||
|
||
tag = uuid.uuid4().hex[:10]
|
||
pw = f"smoke-{tag}"
|
||
t = store.create_tenant(f"pg-smoke-{tag}")
|
||
tenant_id = str(t["id"])
|
||
# 不用 administrator:共享库里会走全局模型池与已导入的 OpenAI profile,易在无 Key 时得到空 reply。
|
||
u = store.create_user_account(
|
||
tenant_id=tenant_id,
|
||
username=f"smoke_{tag}",
|
||
display_name="Smoke",
|
||
role="owner",
|
||
password_hash=hashlib.sha256(pw.encode("utf-8")).hexdigest(),
|
||
is_active=True,
|
||
)
|
||
user_id = str(u.get("id") or "").strip()
|
||
if not user_id:
|
||
print("create_user_account returned no id", u, file=sys.stderr)
|
||
return 1
|
||
store.grant_llm_profile_to_user(
|
||
tenant_id=tenant_id,
|
||
profile_id=LLM_BUILTIN_RULE_PROFILE_ID,
|
||
user_id=user_id,
|
||
)
|
||
store.set_setting(
|
||
active_llm_profile_setting_key(user_id, f"smoke_{tag}"),
|
||
LLM_BUILTIN_RULE_PROFILE_ID,
|
||
)
|
||
|
||
from fastapi.testclient import TestClient
|
||
|
||
from interfaces.http.fastapi_app import create_app
|
||
|
||
client = TestClient(create_app())
|
||
try:
|
||
client.post("/admin/api/auth/bootstrap", json={})
|
||
lr = client.post(
|
||
"/admin/api/auth/login",
|
||
json={
|
||
"tenant_id": tenant_id,
|
||
"username": f"smoke_{tag}",
|
||
"password": pw,
|
||
"purpose": "console",
|
||
},
|
||
)
|
||
lj = lr.json()
|
||
if not lj.get("ok"):
|
||
print("login failed:", lr.status_code, lj, file=sys.stderr)
|
||
return 1
|
||
token = str(lj.get("token") or "")
|
||
h = {"authorization": f"Bearer {token}"}
|
||
|
||
cr = client.post("/admin/api/chat/sessions", json={"title": f"smoke-{tag}"}, headers=h)
|
||
if cr.status_code != 200:
|
||
print("create session:", cr.status_code, cr.text, file=sys.stderr)
|
||
return 1
|
||
cj = cr.json()
|
||
if not cj.get("ok"):
|
||
print("create session body:", cj, file=sys.stderr)
|
||
return 1
|
||
sid = str((cj.get("session") or {}).get("id") or "")
|
||
if not sid:
|
||
print("no session id", cj, file=sys.stderr)
|
||
return 1
|
||
|
||
mr = client.post(
|
||
f"/admin/api/chat/sessions/{sid}/messages",
|
||
json={"text": "ping-smoke-pg"},
|
||
headers=h,
|
||
)
|
||
if mr.status_code != 200:
|
||
print("send message:", mr.status_code, mr.text, file=sys.stderr)
|
||
return 1
|
||
mj = mr.json()
|
||
if not mj.get("ok"):
|
||
print("send message body:", mj, file=sys.stderr)
|
||
return 1
|
||
reply = str(mj.get("reply") or "")
|
||
if not reply.strip():
|
||
print("empty reply (unexpected for rule mode)", mj, file=sys.stderr)
|
||
return 1
|
||
|
||
gr = client.get(f"/admin/api/chat/sessions/{sid}/messages", headers=h)
|
||
if gr.status_code != 200:
|
||
print("list messages:", gr.status_code, gr.text, file=sys.stderr)
|
||
return 1
|
||
gj = gr.json()
|
||
msgs = gj.get("messages") or []
|
||
roles = [str(m.get("role") or "") for m in msgs if isinstance(m, dict)]
|
||
if "user" not in roles or "assistant" not in roles:
|
||
print("roles mismatch:", roles, "full ok=", gj.get("ok"), file=sys.stderr)
|
||
return 1
|
||
|
||
print("OK smoke_admin_chat_postgres")
|
||
print(" tenant_id=", tenant_id)
|
||
print(" session_id=", sid)
|
||
print(" reply_len=", len(reply))
|
||
print(" message_count=", len(msgs), "roles=", roles)
|
||
return 0
|
||
finally:
|
||
try:
|
||
shutil.rmtree(tmp, ignore_errors=True)
|
||
except Exception:
|
||
pass
|
||
|
||
|
||
if __name__ == "__main__":
|
||
raise SystemExit(main())
|