mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-11 03:00:45 +08:00
feat(persistence): PostgreSQL assistant store, chat persist fixes, gateway scripts
- 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>
This commit is contained in:
parent
2b32d11f43
commit
d14e9d3596
103 changed files with 7574 additions and 1641 deletions
|
|
@ -31,10 +31,12 @@ from svc.files.file_attachments import (
|
|||
from interfaces.ws.common import normalize_ws_attachments
|
||||
from svc.files.session_export import export_session_json, export_session_markdown
|
||||
from svc.persistence.sqlite_store import SqliteStore
|
||||
from svc.persistence.assistant_store import get_assistant_store
|
||||
from runtime.gateway import OclawGateway
|
||||
from runtime.plan_agent_v2.switch import v2_feature_enabled
|
||||
from runtime.types import StandardMessage, normalize_interaction_mode, normalize_requested_specialist
|
||||
from runtime.chat.history_tool_result_compact import compact_tool_results_in_session_history
|
||||
from runtime.chat.persist_terminal_fallback import persist_assistant_text_if_turn_missing
|
||||
|
||||
|
||||
def _oclaw_config_path() -> Path:
|
||||
|
|
@ -233,6 +235,23 @@ def _extract_manager_instruction_text(reasoning_content: str) -> str | None:
|
|||
return instr or None
|
||||
|
||||
|
||||
def _instruction_from_dispatch_assignment_block(text: str) -> str | None:
|
||||
"""Extract manager instruction only from **comprehensive dispatch** reasoning blocks.
|
||||
|
||||
Without this guard, any assistant ``reasoning_content`` that contains ``instruction:\\n`` followed
|
||||
by the user's literal question (common in thinking traces) would register as a duplicate
|
||||
``instruction_text`` and :func:`_filter_internal_instruction_user_messages` would drop the real
|
||||
``user_text`` row — after a failed assistant persist the API could return **zero** messages on refresh.
|
||||
"""
|
||||
if not text or "instruction:\n" not in text:
|
||||
return None
|
||||
s = str(text)
|
||||
tl = s.lower()
|
||||
if "任务分配" not in s and "task assignment" not in tl:
|
||||
return None
|
||||
return _extract_manager_instruction_text(s)
|
||||
|
||||
|
||||
def _filter_internal_instruction_user_messages(msgs: list[Any]) -> list[Any]:
|
||||
"""Hide legacy polluted user rows that equal manager dispatch instruction text."""
|
||||
instruction_texts: set[str] = set()
|
||||
|
|
@ -249,7 +268,7 @@ def _filter_internal_instruction_user_messages(msgs: list[Any]) -> list[Any]:
|
|||
if rc:
|
||||
candidates.append(rc)
|
||||
for text in candidates:
|
||||
instr = _extract_manager_instruction_text(text)
|
||||
instr = _instruction_from_dispatch_assignment_block(text)
|
||||
if instr:
|
||||
instruction_texts.add(instr)
|
||||
if not instruction_texts:
|
||||
|
|
@ -362,6 +381,32 @@ def _resolve_chat_session(store: SqliteStore, ctx: dict[str, Any], session_id: s
|
|||
return store.get_session_for_user(session_id=session_id, tenant_id=tenant_id, user_id=user_id)
|
||||
|
||||
|
||||
def _resolve_chat_session_allow_claim_orphan(
|
||||
store: SqliteStore, ctx: dict[str, Any], session_id: str
|
||||
):
|
||||
"""Like :func:`_resolve_chat_session`, but if ``chat_session`` exists with **no** ``ui_session_owner`` row
|
||||
(common after SQLite→PG imports that only copied ``chat_session`` / ``chat_message``), attach the current
|
||||
user once so ``get_session_for_user`` JOIN succeeds. Does **not** override an existing owner (wrong user).
|
||||
"""
|
||||
resolve = _resolve_chat_session
|
||||
sess = resolve(store, ctx, session_id)
|
||||
if sess is not None:
|
||||
return sess
|
||||
if _is_administrator_chat_viewer(ctx):
|
||||
return None
|
||||
sid = str(session_id or "").strip()
|
||||
tenant_id = str(ctx.get("tenant_id") or "").strip()
|
||||
user_id = str(ctx.get("user_id") or "").strip()
|
||||
if not sid or not tenant_id or not user_id:
|
||||
return None
|
||||
if store.get_session(sid) is None:
|
||||
return None
|
||||
if store.get_ui_session_owner(session_id=sid) is not None:
|
||||
return None
|
||||
store.ensure_ui_session_owner(session_id=sid, tenant_id=tenant_id, user_id=user_id)
|
||||
return resolve(store, ctx, session_id)
|
||||
|
||||
|
||||
def _effective_user_text(*, text: str, attachments: list[dict[str, Any]] | None, store: SqliteStore) -> str:
|
||||
"""Streamlit 等价:仅有附件时也要落库一条用户消息,否则模型侧无输入。"""
|
||||
t = (text or "").strip()
|
||||
|
|
@ -825,7 +870,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
offset: int = Query(default=0, ge=0),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
|
|
@ -855,7 +900,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
|
|
@ -881,18 +926,18 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
title = str(payload.get("title") or "").strip()[:_SESSION_TITLE_MAX_LEN] or (
|
||||
"新会话" if _api_lang(store) == "zh" else "New Chat"
|
||||
)
|
||||
store.rename_session(session_id, title)
|
||||
s = _resolve_chat_session(store, ctx, session_id)
|
||||
s = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
return {
|
||||
"ok": True,
|
||||
"session": {
|
||||
|
|
@ -908,11 +953,11 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
session_id: str,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
if _is_administrator_chat_viewer(ctx):
|
||||
|
|
@ -941,11 +986,11 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
last_id = store.get_last_message_id(session_id)
|
||||
|
|
@ -976,11 +1021,11 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
format: str = Query(default="md", description="md or json"),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> Response:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
fmt = (format or "md").strip().lower()
|
||||
|
|
@ -1006,11 +1051,11 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
limit: int = Query(default=_CHAT_MSG_LIMIT, ge=1, le=20000),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
meta = store.get_session_messages_meta(session_id)
|
||||
|
|
@ -1034,9 +1079,9 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
performance remains stable. It only touches `role=tool` chat_message rows.
|
||||
"""
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
limit_messages = int(payload.get("limit_messages") or 5000)
|
||||
|
|
@ -1072,9 +1117,9 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
message_id: int,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
ok = store.delete_message(session_id=str(session_id), message_id=int(message_id))
|
||||
|
|
@ -1089,12 +1134,12 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
limit: int = Query(default=20, ge=1, le=200),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
after_iso = str(after or "").strip()
|
||||
|
|
@ -1138,12 +1183,12 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
max_chars: int = Query(default=80_000, ge=1_000, le=200_000),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
_ = str(ctx.get("user_id") or "")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
wiki_root = _wiki_root_from_config()
|
||||
|
|
@ -1177,11 +1222,11 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
session_id: str,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
s_mm, s_em = _resolve_session_dialog_chat_settings(
|
||||
|
|
@ -1212,11 +1257,11 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
memory_mode = _normalize_memory_mode(payload)
|
||||
|
|
@ -1254,7 +1299,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
def api_chat_user_mode_get(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
|
|
@ -1274,7 +1319,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
|
|
@ -1311,7 +1356,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
offset: int = Query(default=0, ge=0),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
|
|
@ -1333,7 +1378,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
offset: int = Query(default=0, ge=0),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
|
|
@ -1353,7 +1398,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
limit: int = Query(default=200, ge=20, le=1000),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
lang = _api_lang(store)
|
||||
labels = _dispatch_reason_labels_with_overrides(store)
|
||||
|
|
@ -1392,7 +1437,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
def api_chat_get_ui_lang(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
_ = resolve_auth(store, authorization)
|
||||
return {"ok": True, "lang": _api_lang(store)}
|
||||
|
||||
|
|
@ -1402,7 +1447,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
_ = resolve_auth(store, authorization)
|
||||
lang = str(payload.get("lang") or "").strip().lower()
|
||||
if lang not in ("zh", "en"):
|
||||
|
|
@ -1414,7 +1459,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
def api_chat_get_dispatch_reason_labels(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
raw = str(store.get_setting(_DISPATCH_REASON_LABELS_SETTING_KEY) or "").strip()
|
||||
|
|
@ -1439,7 +1484,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
body = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
overrides = body.get("overrides")
|
||||
|
|
@ -1465,7 +1510,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
def api_chat_get_specialist_flags(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
flags = _specialist_flags_with_overrides(store)
|
||||
|
|
@ -1483,7 +1528,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
body = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
raw_flags = body.get("flags")
|
||||
|
|
@ -1507,7 +1552,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
channel: str,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
ch = _normalize_channel_dispatch_channel(channel)
|
||||
|
|
@ -1533,7 +1578,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
body = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
ch = _normalize_channel_dispatch_channel(channel)
|
||||
|
|
@ -1553,7 +1598,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
def api_chat_get_attachment_limits(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
limits = _tabular_limits_from_oclaw_config()
|
||||
|
|
@ -1565,7 +1610,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
body = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
raw = body.get("limits")
|
||||
|
|
@ -1639,7 +1684,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
def api_chat_profile_get(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "").strip()
|
||||
user_id = str(ctx.get("user_id") or "").strip()
|
||||
|
|
@ -1671,7 +1716,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "").strip()
|
||||
user_id = str(ctx.get("user_id") or "").strip()
|
||||
|
|
@ -1702,7 +1747,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
file: UploadFile = File(...),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "").strip()
|
||||
user_id = str(ctx.get("user_id") or "").strip()
|
||||
|
|
@ -1735,7 +1780,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
def api_chat_profile_avatar_delete(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "").strip()
|
||||
user_id = str(ctx.get("user_id") or "").strip()
|
||||
|
|
@ -1749,9 +1794,9 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
session_id: str,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
sid = str(session_id)
|
||||
|
|
@ -1766,7 +1811,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
attachment_id: str,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> Response:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "").strip()
|
||||
user_id = str(ctx.get("user_id") or "").strip()
|
||||
|
|
@ -1828,7 +1873,7 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
limit_messages: int = Query(default=50_000, ge=1, le=500_000),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_administrator_chat_viewer(ctx)
|
||||
tenant_id = str(ctx.get("tenant_id") or "").strip()
|
||||
|
|
@ -1854,12 +1899,12 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
role = str(ctx.get("role") or "member")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
text_raw = str(payload.get("text") or "").strip()
|
||||
|
|
@ -1971,6 +2016,13 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
specialist_executor_factory=specialist_factory,
|
||||
)
|
||||
reply = gw_result.reply_text
|
||||
persist_assistant_text_if_turn_missing(
|
||||
store=store,
|
||||
session_id=str(session_id),
|
||||
turn_uuid=str(getattr(gw_result, "turn_uuid", "") or ""),
|
||||
final_text=str(reply or ""),
|
||||
log_prefix="admin_chat_http_text_fallback_persisted",
|
||||
)
|
||||
except GenerationInterrupted:
|
||||
msg = "已中断回答。" if lang == "zh" else "Response stopped."
|
||||
store.add_message(session_id=session_id, role="assistant", content=msg)
|
||||
|
|
@ -1992,12 +2044,12 @@ def include_chat_routes(router: APIRouter, *, resolve_auth: Callable[[SqliteStor
|
|||
) -> StreamingResponse:
|
||||
"""Server-Sent Events: token deltas + progress + tool_ui, then done (assistant persisted by run_turn)."""
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
user_id = str(ctx.get("user_id") or "")
|
||||
role = str(ctx.get("role") or "member")
|
||||
sess = _resolve_chat_session(store, ctx, session_id)
|
||||
sess = _resolve_chat_session_allow_claim_orphan(store, ctx, session_id)
|
||||
if not sess:
|
||||
raise HTTPException(status_code=404, detail="session_not_found")
|
||||
text_raw = str(payload.get("text") or "").strip()
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ from runtime.agents.specialists import (
|
|||
)
|
||||
from svc.config.paths import db_path
|
||||
from runtime.orchestration.evaluation import eval_summary
|
||||
from svc.persistence.assistant_store import get_assistant_store
|
||||
from svc.persistence.sqlite_store import (
|
||||
LLM_BUILTIN_OLLAMA_PROFILE_ID,
|
||||
SqliteStore,
|
||||
|
|
@ -169,7 +170,7 @@ def include_model_mgmt_routes(
|
|||
def api_models_state(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
uid = str(ctx.get("user_id") or "").strip()
|
||||
|
|
@ -224,7 +225,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
# 与能进入控制台一致:切换「当前选用」不写密钥,仅需读权限即可。
|
||||
_require_permission(ctx, "admin:read")
|
||||
|
|
@ -242,7 +243,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_models_mutate(ctx)
|
||||
profiles = store.list_llm_profiles(visible_only=True, **_models_list_kwargs(ctx))
|
||||
|
|
@ -268,7 +269,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_models_mutate(ctx)
|
||||
profiles = store.list_llm_profiles(visible_only=True, **_models_list_kwargs(ctx))
|
||||
|
|
@ -285,7 +286,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_models_mutate(ctx)
|
||||
profiles = store.list_llm_profiles(visible_only=True, **_models_list_kwargs(ctx))
|
||||
|
|
@ -311,7 +312,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
visible = _truthy(payload.get("chat_model_selector_visible"), default=True)
|
||||
|
|
@ -324,7 +325,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_models_mutate(ctx)
|
||||
name = str(payload.get("name") or "").strip() or "新配置"
|
||||
|
|
@ -354,7 +355,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_models_mutate(ctx)
|
||||
pid = str(profile_id or "").strip()
|
||||
|
|
@ -401,7 +402,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_models_mutate(ctx)
|
||||
pid = str(profile_id or "").strip()
|
||||
|
|
@ -428,7 +429,7 @@ def include_model_mgmt_routes(
|
|||
profile_id: str,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_models_mutate(ctx)
|
||||
pid = str(profile_id or "").strip()
|
||||
|
|
@ -446,7 +447,7 @@ def include_model_mgmt_routes(
|
|||
def api_models_members(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_grant_manager(ctx)
|
||||
tid = str(ctx.get("tenant_id") or "").strip()
|
||||
|
|
@ -481,7 +482,7 @@ def include_model_mgmt_routes(
|
|||
profile_id: str = Query(...),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_grant_manager(ctx)
|
||||
tid = str(ctx.get("tenant_id") or "").strip()
|
||||
|
|
@ -497,7 +498,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_grant_manager(ctx)
|
||||
tid = str(ctx.get("tenant_id") or "").strip()
|
||||
|
|
@ -524,7 +525,7 @@ def include_model_mgmt_routes(
|
|||
profile_id: str = Query(...),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_grant_manager(ctx)
|
||||
tid = str(ctx.get("tenant_id") or "").strip()
|
||||
|
|
@ -539,7 +540,7 @@ def include_model_mgmt_routes(
|
|||
profile_id: str = Query(..., description="llm_profile id"),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_grant_manager(ctx)
|
||||
tid = str(ctx.get("tenant_id") or "").strip()
|
||||
|
|
@ -555,7 +556,7 @@ def include_model_mgmt_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_grant_manager(ctx)
|
||||
tid = str(ctx.get("tenant_id") or "").strip()
|
||||
|
|
@ -589,7 +590,7 @@ def include_model_mgmt_routes(
|
|||
user_id: str = Query(...),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_grant_manager(ctx)
|
||||
tid = str(ctx.get("tenant_id") or "").strip()
|
||||
|
|
@ -606,7 +607,7 @@ def include_model_mgmt_routes(
|
|||
limit_logs: int = Query(default=100, ge=1, le=500),
|
||||
limit_summary: int = Query(default=500, ge=1, le=5000),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
summary = eval_summary(store, limit=limit_summary)
|
||||
|
|
@ -630,7 +631,7 @@ def include_model_mgmt_routes(
|
|||
format: str = Query(default="csv", description="csv or json"),
|
||||
limit: int = Query(default=100_000, ge=1, le=200_000),
|
||||
) -> Response:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
fmt = str(format or "csv").strip().lower()
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ from runtime.orchestration.vector_store import read_vector_memory_runtime
|
|||
from svc.config.paths import PROJECT_ROOT, db_path
|
||||
from svc.config.passwords import load_expected_password
|
||||
from svc.persistence.sqlite_store import SqliteStore
|
||||
from svc.persistence.assistant_store import get_assistant_store
|
||||
from runtime.agents.specialists import discover_specialist_ids, parse_agent_profile_bindings
|
||||
from runtime.tools.mcp.installer import (
|
||||
_safe_server_id,
|
||||
|
|
@ -442,7 +443,7 @@ def build_admin_router() -> APIRouter:
|
|||
def api_internal_tools_reload(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
|
||||
|
|
@ -467,7 +468,7 @@ def build_admin_router() -> APIRouter:
|
|||
def api_tools_exposure_trace_setting_get(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
raw = str(store.get_setting("AIA_TRACE_TOOL_EXPOSURE_PLAN") or "").strip().lower()
|
||||
|
|
@ -479,7 +480,7 @@ def build_admin_router() -> APIRouter:
|
|||
payload: dict[str, Any] | None = Body(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
payload = payload or {}
|
||||
|
|
@ -501,7 +502,7 @@ def build_admin_router() -> APIRouter:
|
|||
role: str | None = Query(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
from runtime.tools.exposure_plan import build_internal_tool_specs
|
||||
|
|
@ -539,7 +540,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
"""One-shot diagnostic summary for role-based tool exposure."""
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
|
||||
|
|
@ -604,7 +605,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
"""Preview the final tools injected to LLM for a role (internal + MCP + wire policy)."""
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
from runtime.tools.exposure_plan import build_llm_tools_plan
|
||||
|
|
@ -726,7 +727,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/channels")
|
||||
def api_channels(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
ctx = _resolve_auth(SqliteStore(db_path()), authorization)
|
||||
ctx = _resolve_auth(get_assistant_store(), authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
reg = build_channel_registry()
|
||||
items = [{"name": k, "type": reg[k].__class__.__name__} for k in sorted(reg.keys())]
|
||||
|
|
@ -734,7 +735,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/stack/status")
|
||||
def api_stack_status(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
ctx = _resolve_auth(SqliteStore(db_path()), authorization)
|
||||
ctx = _resolve_auth(get_assistant_store(), authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
items = []
|
||||
for s in status_services():
|
||||
|
|
@ -786,7 +787,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.post("/admin/api/stack/up")
|
||||
def api_stack_up(channel: str = "wecom", authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
ctx = _resolve_auth(SqliteStore(db_path()), authorization)
|
||||
ctx = _resolve_auth(get_assistant_store(), authorization)
|
||||
_require_permission(ctx, "admin:runtime:write")
|
||||
# Use same defaults as CLI; this starts detached processes and writes runtime state.
|
||||
import argparse
|
||||
|
|
@ -807,7 +808,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.post("/admin/api/stack/down")
|
||||
def api_stack_down(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
ctx = _resolve_auth(SqliteStore(db_path()), authorization)
|
||||
ctx = _resolve_auth(get_assistant_store(), authorization)
|
||||
_require_permission(ctx, "admin:runtime:write")
|
||||
import argparse
|
||||
|
||||
|
|
@ -884,14 +885,14 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/runtime/anomalies")
|
||||
def api_runtime_anomalies(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
return _collect_runtime_anomalies(store)
|
||||
|
||||
@router.post("/admin/api/runtime/cleanup")
|
||||
def api_runtime_cleanup(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:runtime:write")
|
||||
killed: list[dict[str, Any]] = []
|
||||
|
|
@ -928,7 +929,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/runtime/prewarm/status")
|
||||
def api_runtime_prewarm_status(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
return runtime_prewarm_status(store=store)
|
||||
|
|
@ -938,7 +939,7 @@ def build_admin_router() -> APIRouter:
|
|||
payload: dict[str, Any] | None = Body(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:runtime:write")
|
||||
body = payload or {}
|
||||
|
|
@ -950,7 +951,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
def _run() -> None:
|
||||
try:
|
||||
_ = run_runtime_prewarm(reason=reason, store=SqliteStore(db_path()))
|
||||
_ = run_runtime_prewarm(reason=reason, store=get_assistant_store())
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
|
@ -963,14 +964,14 @@ def build_admin_router() -> APIRouter:
|
|||
role: str | None = Query(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
return runtime_prewarm_prompts_snapshot(store=store, role=str(role or "").strip().lower() or None)
|
||||
|
||||
@router.get("/admin/api/runtime/scan-artifacts")
|
||||
def api_runtime_scan_artifacts(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
ctx = _resolve_auth(SqliteStore(db_path()), authorization)
|
||||
ctx = _resolve_auth(get_assistant_store(), authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
root = (PROJECT_ROOT / "runtime" / "data" / "scan").resolve()
|
||||
allowed_prefixes = ("history_entries_", "state_scan_")
|
||||
|
|
@ -995,7 +996,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.post("/admin/api/runtime/scan-artifacts/cleanup")
|
||||
def api_runtime_scan_artifacts_cleanup(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
ctx = _resolve_auth(SqliteStore(db_path()), authorization)
|
||||
ctx = _resolve_auth(get_assistant_store(), authorization)
|
||||
_require_permission(ctx, "admin:runtime:write")
|
||||
root = (PROJECT_ROOT / "runtime" / "data" / "scan").resolve()
|
||||
allowed_prefixes = ("history_entries_", "state_scan_")
|
||||
|
|
@ -1017,7 +1018,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
body = payload or {}
|
||||
ctx = _resolve_auth(SqliteStore(db_path()), authorization)
|
||||
ctx = _resolve_auth(get_assistant_store(), authorization)
|
||||
_require_permission(ctx, "admin:runtime:write")
|
||||
root = (PROJECT_ROOT / "runtime" / "data" / "scan").resolve()
|
||||
try:
|
||||
|
|
@ -1068,7 +1069,7 @@ def build_admin_router() -> APIRouter:
|
|||
scope: str | None = Query(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:read")
|
||||
rows = store.list_tenants(limit=500)
|
||||
|
|
@ -1083,7 +1084,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
name = str(payload.get("name") or "").strip() or "Team"
|
||||
|
|
@ -1096,7 +1097,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip()
|
||||
|
|
@ -1128,7 +1129,7 @@ def build_admin_router() -> APIRouter:
|
|||
user_id: str | None = Query(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:read")
|
||||
_require_tenant_scope(ctx, tenant_id)
|
||||
|
|
@ -1152,7 +1153,7 @@ def build_admin_router() -> APIRouter:
|
|||
include_inactive: int = Query(default=1),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:read")
|
||||
_require_tenant_scope(ctx, tenant_id)
|
||||
|
|
@ -1172,7 +1173,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:write")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip()
|
||||
|
|
@ -1234,7 +1235,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:write")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip()
|
||||
|
|
@ -1269,7 +1270,7 @@ def build_admin_router() -> APIRouter:
|
|||
limit: int = Query(default=1000),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:read")
|
||||
_require_tenant_scope(ctx, tenant_id)
|
||||
|
|
@ -1288,7 +1289,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:write")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip() or str(ctx.get("tenant_id") or "").strip()
|
||||
|
|
@ -1327,7 +1328,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:write")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip()
|
||||
|
|
@ -1366,7 +1367,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:delete")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip()
|
||||
|
|
@ -1435,7 +1436,7 @@ def build_admin_router() -> APIRouter:
|
|||
user_id: str = Query(default=""),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
rmode = _workspace_path_policy_read_mode(ctx)
|
||||
if rmode is None:
|
||||
|
|
@ -1468,7 +1469,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
wmode = _workspace_path_policy_write_mode(ctx)
|
||||
if wmode is None:
|
||||
|
|
@ -1518,7 +1519,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:delete")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip()
|
||||
|
|
@ -1557,7 +1558,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/bind-codes")
|
||||
def api_bind_codes(tenant_id: str, authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:read")
|
||||
_require_tenant_scope(ctx, tenant_id)
|
||||
|
|
@ -1570,7 +1571,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:write")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip()
|
||||
|
|
@ -1588,7 +1589,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:write")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip()
|
||||
|
|
@ -1608,7 +1609,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/wecom/config")
|
||||
def api_wecom_config(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
return {
|
||||
|
|
@ -1625,7 +1626,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/wecom/health")
|
||||
def api_wecom_health(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
svc = next((s for s in status_services() if str(s.name) == "channel:wecom"), None)
|
||||
|
|
@ -1672,7 +1673,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
store.set_setting("wecom_mode", "bot_api")
|
||||
|
|
@ -1695,7 +1696,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.post("/admin/api/wecom/unbind")
|
||||
def api_wecom_unbind(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
with store._connect() as conn:
|
||||
|
|
@ -1722,7 +1723,7 @@ def build_admin_router() -> APIRouter:
|
|||
session_id: str | None = Query(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
rows = store.list_agent_audit_logs(limit=200, session_id=session_id)
|
||||
|
|
@ -1734,7 +1735,7 @@ def build_admin_router() -> APIRouter:
|
|||
limit: int = Query(default=80),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
rows = store.list_session_tool_health(session_id=session_id, limit=limit)
|
||||
|
|
@ -1746,7 +1747,7 @@ def build_admin_router() -> APIRouter:
|
|||
session_id: str | None = Query(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
if not session_id:
|
||||
|
|
@ -1763,7 +1764,7 @@ def build_admin_router() -> APIRouter:
|
|||
limit: int = Query(default=80),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
|
|
@ -1794,7 +1795,7 @@ def build_admin_router() -> APIRouter:
|
|||
include_attempts: int = Query(default=1),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
tenant_id = str(ctx.get("tenant_id") or "")
|
||||
|
|
@ -1840,7 +1841,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
"""Return a best-effort replay bundle for a single turn (trace_id)."""
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
sid = str(session_id or "").strip()
|
||||
|
|
@ -1940,7 +1941,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/plugins")
|
||||
def api_plugins(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:write")
|
||||
rows = store.list_tool_plugins()
|
||||
|
|
@ -1948,7 +1949,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/tool-policy")
|
||||
def api_tool_policy(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
# Oclaw takeover: legacy tool-policy switches are disconnected (kept in DB for later).
|
||||
|
|
@ -2047,7 +2048,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
# Oclaw takeover: legacy tool-policy switches are disconnected (kept in DB for later).
|
||||
|
|
@ -2240,7 +2241,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/mcp/servers")
|
||||
def api_mcp_servers(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
rows = McpRegistry(store).list_servers(enabled_only=False)
|
||||
|
|
@ -2253,7 +2254,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/mcp/export")
|
||||
def api_mcp_export(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
return {
|
||||
|
|
@ -2267,7 +2268,7 @@ def build_admin_router() -> APIRouter:
|
|||
limit: int = Query(default=20),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
return {"ok": True, "items": store.list_mcp_install_failure_summary(limit=limit)}
|
||||
|
|
@ -2278,7 +2279,7 @@ def build_admin_router() -> APIRouter:
|
|||
limit: int = Query(default=200),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
return {
|
||||
|
|
@ -2292,7 +2293,7 @@ def build_admin_router() -> APIRouter:
|
|||
role: str | None = Query(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
from svc.llm.tool_wire_policy import build_tool_wire_snapshot
|
||||
|
|
@ -2307,7 +2308,7 @@ def build_admin_router() -> APIRouter:
|
|||
from svc.llm.tool_wire_policy import SETTINGS_KEY_ADMIN_CONFIG, load_merged_admin_config
|
||||
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
raw = store.get_setting(SETTINGS_KEY_ADMIN_CONFIG)
|
||||
|
|
@ -2359,7 +2360,7 @@ def build_admin_router() -> APIRouter:
|
|||
from svc.llm.tool_wire_policy import SETTINGS_KEY_ROLE_MODE_BY_ROLE
|
||||
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
role = str(payload.get("role") or "").strip().lower()
|
||||
|
|
@ -2398,7 +2399,7 @@ def build_admin_router() -> APIRouter:
|
|||
) -> dict[str, Any]:
|
||||
from svc.llm.tool_wire_policy import SETTINGS_KEY_PENALTY_STATE, SETTINGS_KEY_PENALTY_STATE_BY_ROLE
|
||||
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
r = str(role or "").strip().lower()
|
||||
|
|
@ -2440,7 +2441,7 @@ def build_admin_router() -> APIRouter:
|
|||
)
|
||||
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
role = str(payload.get("role") or "").strip().lower()
|
||||
|
|
@ -2511,7 +2512,7 @@ def build_admin_router() -> APIRouter:
|
|||
)
|
||||
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
try:
|
||||
|
|
@ -2570,7 +2571,7 @@ def build_admin_router() -> APIRouter:
|
|||
per_source_limit: int = Query(default=6),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
query = str(q or "").strip()
|
||||
|
|
@ -2585,7 +2586,7 @@ def build_admin_router() -> APIRouter:
|
|||
refresh: int = Query(default=0),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
items = trending_mcp_market(force_refresh=bool(int(refresh or 0)), per_source_limit=per_source_limit)
|
||||
|
|
@ -2593,14 +2594,14 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/mcp/dependencies")
|
||||
def api_mcp_dependencies(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
return {"ok": True, "items": detect_local_dependencies()}
|
||||
|
||||
@router.get("/admin/api/mcp/binding")
|
||||
def api_mcp_binding(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
available = _ordered_mcp_roles()
|
||||
|
|
@ -2627,7 +2628,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
available = _ordered_mcp_roles()
|
||||
|
|
@ -2661,7 +2662,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/mcp/specialists")
|
||||
def api_mcp_specialists(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
available = _ordered_mcp_roles()
|
||||
|
|
@ -2679,7 +2680,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
available = _ordered_mcp_roles()
|
||||
|
|
@ -2705,7 +2706,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/experts")
|
||||
def api_experts_list(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
items = [_serialize_expert_row(x) for x in list_experts()]
|
||||
|
|
@ -2717,7 +2718,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
expert_id = normalize_expert_id(payload.get("id"))
|
||||
|
|
@ -2755,7 +2756,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
files_raw = payload.get("files") if isinstance(payload.get("files"), dict) else {}
|
||||
|
|
@ -2788,7 +2789,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.delete("/admin/api/experts/{expert_id}")
|
||||
def api_experts_delete(expert_id: str, authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
eid = normalize_expert_id(expert_id)
|
||||
|
|
@ -2814,7 +2815,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
source_type = str(payload.get("source_type") or "").strip().lower()
|
||||
|
|
@ -2882,7 +2883,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
server_id = str(payload.get("server_id") or "").strip()
|
||||
|
|
@ -2952,7 +2953,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
server_id = str(payload.get("server_id") or "").strip()
|
||||
|
|
@ -3073,7 +3074,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
server_id = str(payload.get("server_id") or "").strip()
|
||||
|
|
@ -3133,7 +3134,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
server_id = str(payload.get("server_id") or "").strip()
|
||||
|
|
@ -3163,7 +3164,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
source_type = str(payload.get("source_type") or "").strip().lower()
|
||||
|
|
@ -3192,7 +3193,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
server_id = str(payload.get("server_id") or "").strip()
|
||||
|
|
@ -3217,7 +3218,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
apply_gateway_mcp_env_to_os()
|
||||
|
|
@ -3268,7 +3269,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
apply_gateway_mcp_env_to_os()
|
||||
|
|
@ -3327,7 +3328,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
apply_gateway_mcp_env_to_os()
|
||||
|
|
@ -3347,7 +3348,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
enabled_only = bool(payload.get("enabled_only", True))
|
||||
|
|
@ -3372,7 +3373,7 @@ def build_admin_router() -> APIRouter:
|
|||
run the same health + tools/list + replace flow as ``check-all`` (only those servers).
|
||||
"""
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
apply_gateway_mcp_env_to_os()
|
||||
|
|
@ -3426,7 +3427,7 @@ def build_admin_router() -> APIRouter:
|
|||
def api_mcp_e2e_check(
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
apply_gateway_mcp_env_to_os()
|
||||
|
|
@ -3520,7 +3521,7 @@ def build_admin_router() -> APIRouter:
|
|||
limit: int = Query(default=100),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
if tenant_id:
|
||||
|
|
@ -3535,7 +3536,7 @@ def build_admin_router() -> APIRouter:
|
|||
limit: int = Query(default=300),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
if tenant_id:
|
||||
|
|
@ -3567,7 +3568,7 @@ def build_admin_router() -> APIRouter:
|
|||
offset: int = Query(default=0),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
if tenant_id:
|
||||
|
|
@ -3582,7 +3583,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:memory:write")
|
||||
memory_id = str(payload.get("memory_id") or "").strip()
|
||||
|
|
@ -3597,7 +3598,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:memory:write")
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip() or None
|
||||
|
|
@ -3635,7 +3636,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/memory/config")
|
||||
def api_memory_config(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
runtime = read_vector_memory_runtime(store)
|
||||
|
|
@ -3666,7 +3667,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:memory:write")
|
||||
store.set_setting("MEMORY_VECTOR_ENABLED", "1" if str(payload.get("enabled") or "").lower() in ("1", "true", "yes", "on") else "0")
|
||||
|
|
@ -3704,7 +3705,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:memory:write")
|
||||
try:
|
||||
|
|
@ -3717,7 +3718,7 @@ def build_admin_router() -> APIRouter:
|
|||
@router.post("/admin/api/secrets/migrate")
|
||||
def api_secrets_migrate(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
"""Migrate legacy b64 secrets to fernet (requires AIA_ASSISTANT_MASTER_KEY)."""
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:tenant:write")
|
||||
try:
|
||||
|
|
@ -3738,7 +3739,7 @@ def build_admin_router() -> APIRouter:
|
|||
@router.get("/admin/api/secrets/status")
|
||||
def api_secrets_status(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
"""Expose legacy secret stats for admin UI warnings."""
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
stats = store.legacy_secret_stats()
|
||||
|
|
@ -3749,14 +3750,14 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.post("/admin/api/auth/bootstrap")
|
||||
def api_auth_bootstrap() -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
_ensure_admin_bootstrap(store)
|
||||
return {"ok": True}
|
||||
|
||||
@router.post("/admin/api/auth/login")
|
||||
def api_auth_login(payload: dict[str, Any] | None = Body(default=None)) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
_ensure_admin_bootstrap(store)
|
||||
tenant_id = str(payload.get("tenant_id") or "").strip()
|
||||
if not tenant_id:
|
||||
|
|
@ -3820,13 +3821,13 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/auth/me")
|
||||
def api_auth_me(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
return {"ok": True, "session": ctx}
|
||||
|
||||
@router.post("/admin/api/auth/logout")
|
||||
def api_auth_logout(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
token = _extract_bearer(authorization)
|
||||
if token:
|
||||
store.revoke_auth_session(session_token_hash=_sha256_hex(token))
|
||||
|
|
@ -3838,7 +3839,7 @@ def build_admin_router() -> APIRouter:
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_ops_ai_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
|
||||
|
|
@ -4045,7 +4046,7 @@ def build_admin_router() -> APIRouter:
|
|||
offset: int = Query(default=0),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_ops_ai_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
lim = max(1, min(int(limit), 200))
|
||||
|
|
@ -4068,7 +4069,7 @@ def build_admin_router() -> APIRouter:
|
|||
|
||||
@router.get("/admin/api/ops-ai/health")
|
||||
def api_ops_ai_health(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_ops_ai_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:read")
|
||||
return {"ok": True, "service": "oclaw", "component": "ops-ai", "status": "ok", "caller": str(ctx.get("username") or "")}
|
||||
|
|
@ -4082,7 +4083,7 @@ def build_admin_router() -> APIRouter:
|
|||
status: str | None = Query(default=None),
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = _resolve_auth(store, authorization)
|
||||
_require_permission(ctx, "admin:user:write")
|
||||
a = str(action or "").strip()
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ from runtime.skills_market import get_market_adapter, normalize_skill_market_pro
|
|||
from runtime.tools.skills_runtime.subprocess_exec import run_skill_runtime_entry
|
||||
from svc.config.paths import db_path
|
||||
from svc.persistence.sqlite_store import SqliteStore
|
||||
from svc.persistence.assistant_store import get_assistant_store
|
||||
|
||||
_SKILL_MARKET_PROVIDER_KEY = "AIA_SKILL_MARKET_PROVIDER"
|
||||
|
||||
|
|
@ -85,7 +86,7 @@ def include_skill_routes(
|
|||
|
||||
@sk.get("")
|
||||
def api_skills_list(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
items = list_skills_with_status(store=store)
|
||||
|
|
@ -93,7 +94,7 @@ def include_skill_routes(
|
|||
|
||||
@sk.get("/mode")
|
||||
def api_skills_mode_get(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
raw_prompt = str(store.get_setting("AIA_SKILLS_PROMPT_IN_SYSTEM") or "").strip().lower()
|
||||
|
|
@ -114,7 +115,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
if "prompt_in_system" in payload:
|
||||
|
|
@ -153,7 +154,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
source_dir = str(payload.get("source_dir") or "").strip()
|
||||
|
|
@ -187,7 +188,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
archive_url = str(payload.get("archive_url") or "").strip()
|
||||
|
|
@ -221,7 +222,7 @@ def include_skill_routes(
|
|||
limit: int | None = None,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
query = str(q or "").strip()
|
||||
|
|
@ -236,7 +237,7 @@ def include_skill_routes(
|
|||
slug: str | None = None,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
s = str(slug or "").strip()
|
||||
|
|
@ -252,7 +253,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
s = str(payload.get("slug") or "").strip()
|
||||
|
|
@ -309,7 +310,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
name = str(payload.get("name") or "").strip()
|
||||
|
|
@ -346,7 +347,7 @@ def include_skill_routes(
|
|||
|
||||
@sk.get("/binding")
|
||||
def api_skills_binding_get(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_tenant_write(ctx)
|
||||
roles, mapping, _valid = _normalized_skill_binding(store)
|
||||
|
|
@ -368,7 +369,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_tenant_write(ctx)
|
||||
if "enabled" in payload:
|
||||
|
|
@ -403,7 +404,7 @@ def include_skill_routes(
|
|||
|
||||
@sk.get("/effective")
|
||||
def api_skills_effective(authorization: str | None = Header(default=None)) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
roles, mapping, _valid = _normalized_skill_binding(store)
|
||||
|
|
@ -494,7 +495,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
name = str(payload.get("name") or "").strip()
|
||||
|
|
@ -538,7 +539,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
name = str(payload.get("name") or "").strip()
|
||||
|
|
@ -554,7 +555,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
name = str(payload.get("name") or "").strip()
|
||||
|
|
@ -570,7 +571,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
name = str(payload.get("name") or "").strip()
|
||||
|
|
@ -604,7 +605,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
auto_name = str(payload.get("name") or "")
|
||||
|
|
@ -642,7 +643,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
source = str(payload.get("source") or "").strip().lower()
|
||||
|
|
@ -706,7 +707,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
name = str(payload.get("name") or "").strip()
|
||||
|
|
@ -736,7 +737,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
_payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
items = list_skills_with_status(store=store)
|
||||
|
|
@ -798,7 +799,7 @@ def include_skill_routes(
|
|||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
payload = payload or {}
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
name = str(payload.get("name") or "").strip()
|
||||
|
|
@ -850,7 +851,7 @@ def include_skill_routes(
|
|||
include_execution: bool = False,
|
||||
authorization: str | None = Header(default=None),
|
||||
) -> dict[str, Any]:
|
||||
store = SqliteStore(db_path())
|
||||
store = get_assistant_store()
|
||||
ctx = resolve_auth(store, authorization)
|
||||
_require_admin(ctx)
|
||||
manifests = discover_workspace_skill_manifests()
|
||||
|
|
|
|||
|
|
@ -688,6 +688,28 @@ function _buildRenderRows(msgs) {
|
|||
return rows;
|
||||
}
|
||||
|
||||
/** True when persisted history does not show a completed assistant reply for the latest turn (e.g. ends with user only). */
|
||||
function _needsWsTextFallbackFromRenderRows(renderRows) {
|
||||
const rows = Array.isArray(renderRows) ? renderRows : [];
|
||||
if (!rows.length) return true;
|
||||
const last = rows[rows.length - 1];
|
||||
const lr = String((last && last.role) || "").toLowerCase();
|
||||
if (lr === "user") return true;
|
||||
if (lr !== "assistant") return true;
|
||||
const items = last && Array.isArray(last._items) ? last._items : [];
|
||||
for (const it of items) {
|
||||
const k = String((it && it.kind) || "").toLowerCase();
|
||||
if (k === "assistant_text" && String((it && it.text) || "").trim()) return false;
|
||||
if (k === "reasoning" && String((it && it.text) || "").trim()) return false;
|
||||
if (k === "tool_result") {
|
||||
if (String((it && it.text) || "").trim()) return false;
|
||||
if (Array.isArray(it.attachments) && it.attachments.length) return false;
|
||||
}
|
||||
}
|
||||
if (String((last && last.content) || "").trim()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function t(key, vars) {
|
||||
let s = (I18N[currentLang] && I18N[currentLang][key]) || (I18N.en && I18N.en[key]) || key;
|
||||
if (vars && typeof s === "string") {
|
||||
|
|
@ -3739,25 +3761,35 @@ async function renderChatUi() {
|
|||
},
|
||||
};
|
||||
|
||||
const loadMessagesForActive = async () => {
|
||||
const loadMessagesForActive = async (opts = {}) => {
|
||||
loadMessagesForActive._rid = (loadMessagesForActive._rid || 0) + 1;
|
||||
const rid = loadMessagesForActive._rid;
|
||||
shouldFollowMessages = true;
|
||||
if (!activeId) {
|
||||
loadMessagesForActive._needsWsTextFallback = true;
|
||||
messagesEl.innerHTML = "";
|
||||
statusBar.textContent = sessions.length ? "" : t("chat.noSessions");
|
||||
if (!sessions.length) messagesEl.appendChild(el("div", { class: "muted", text: t("chat.empty") }));
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
statusBar.textContent = t("chat.loading");
|
||||
loadMessagesForActive._needsWsTextFallback = true;
|
||||
try {
|
||||
const resp = await apiGet(
|
||||
`/admin/api/chat/sessions/${encodeURIComponent(activeId)}/messages?limit=${CHAT_MESSAGES_FETCH_LIMIT}`,
|
||||
);
|
||||
if (rid !== loadMessagesForActive._rid) return;
|
||||
if (rid !== loadMessagesForActive._rid) return 0;
|
||||
const msgs = Array.isArray(resp.messages) ? resp.messages : [];
|
||||
const renderRows = _buildRenderRows(msgs);
|
||||
loadMessagesForActive._needsWsTextFallback = _needsWsTextFallbackFromRenderRows(renderRows);
|
||||
const total = intOr(resp.message_count, msgs.length);
|
||||
// End-of-turn hydrate: if the server returns nothing renderable yet (PG commit lag, transient API
|
||||
// glitch) but the caller still has a live stream worth keeping, do not wipe messagesEl — that
|
||||
// caused "stream flashes then entire dialog is empty" when reasoning/tool-output mode reloads.
|
||||
if (!renderRows.length && opts.keepDomIfNoHistoryRows) {
|
||||
statusBar.textContent = "";
|
||||
return 0;
|
||||
}
|
||||
messagesEl.innerHTML = "";
|
||||
if (total > msgs.length) {
|
||||
messagesEl.appendChild(
|
||||
|
|
@ -3776,10 +3808,13 @@ async function renderChatUi() {
|
|||
}
|
||||
statusBar.textContent = "";
|
||||
scrollMessagesToBottom(true);
|
||||
return renderRows.length;
|
||||
} catch (e) {
|
||||
// Keep existing message list on reload failure (e.g. toggle reload races),
|
||||
// so users don't perceive "messages disappeared".
|
||||
loadMessagesForActive._needsWsTextFallback = true;
|
||||
statusBar.textContent = `${t("chat.error")}: ${String(e)}`;
|
||||
return -1;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -4847,25 +4882,39 @@ ${autoLimit ? `<div style="margin-top:8px;"><span class="muted">auto-added claus
|
|||
renderStreamComposite();
|
||||
_markStreamTerminal("end", t("chat.status.end"));
|
||||
ok = true;
|
||||
} else if (adminChatShowToolOutput) {
|
||||
// WS final message may only contain the last assistant_text snapshot and
|
||||
// omit persisted reasoning rows. Hydrate from history to avoid
|
||||
// end-of-turn "reasoning disappears until refresh".
|
||||
try {
|
||||
if (streamRow && streamRow.parentNode) streamRow.remove();
|
||||
} catch (_) {}
|
||||
await loadMessagesForActive();
|
||||
} else if (adminChatShowToolOutput || sawStreamToolRefAttachments) {
|
||||
// Hydrate from history for reasoning / tool panels / ref attachments. If the DB has not
|
||||
// yet persisted the assistant reply (or only has the user row), n>0 used to skip
|
||||
// appendFinalAssistant and removed the stream bubble — leaving an empty pane like the
|
||||
// user screenshot. Use _needsWsTextFallback when WS still holds usable text.
|
||||
const hadStream =
|
||||
hasRealStreamText ||
|
||||
(Array.isArray(chatStreamSegments) && chatStreamSegments.length > 0) ||
|
||||
!!String(chatStream || "").trim();
|
||||
const fbLine = chatStream || extractWsAssistantText(payload.message || {});
|
||||
const fbTrim = String(fbLine || "").trim();
|
||||
const fbOk = !!fbTrim && !_isSilentReplyStream(fbTrim);
|
||||
const n = await loadMessagesForActive({ keepDomIfNoHistoryRows: hadStream });
|
||||
const needWsFallback =
|
||||
hadStream && fbOk && (n < 0 || !!loadMessagesForActive._needsWsTextFallback);
|
||||
if (n >= 0) {
|
||||
try {
|
||||
if (streamRow && streamRow.parentNode) streamRow.remove();
|
||||
} catch (_) {}
|
||||
}
|
||||
if (needWsFallback) {
|
||||
if (n < 0) {
|
||||
try {
|
||||
if (streamRow && streamRow.parentNode) streamRow.remove();
|
||||
} catch (_) {}
|
||||
}
|
||||
ok = await appendFinalAssistant(payload.message, fbLine);
|
||||
} else if (n > 0) {
|
||||
ok = true;
|
||||
} else {
|
||||
ok = n === 0;
|
||||
}
|
||||
scrollMessagesToBottom(true);
|
||||
ok = true;
|
||||
} else if (sawStreamToolRefAttachments) {
|
||||
// Streaming UI cannot render image_ref/relay_pointer; recover from persisted history so images appear
|
||||
// without requiring a manual refresh.
|
||||
try {
|
||||
if (streamRow && streamRow.parentNode) streamRow.remove();
|
||||
} catch (_) {}
|
||||
await loadMessagesForActive();
|
||||
scrollMessagesToBottom(true);
|
||||
ok = true;
|
||||
} else {
|
||||
ok = await appendFinalAssistant(payload.message, chatStream || extractWsAssistantText(payload.message || {}));
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue