mirror of
https://github.com/hansjone/oclaw.git
synced 2026-10-10 04:00:44 +08:00
feat(chat): image specialist legacy lane, Responses fixes, ACL + UI attachments
- Image expert: DashScope-style /chat/completions via image_legacy_client; early exit in direct_loop when skill_binding_role is image; shared placeholder helper; docs/IMAGE_SPECIALIST_LANE.md. - Strict attachment ACL: link_attachment_acl on assistant chat_message rows (sqlite_store); chat attachment rate limit when user_id empty; admin chat tests updated. - Admin chat UI: aggregate bubbles render assistant_text attachments (image_ref); WS expand path. - turn_runner: persisted_chat_attachments_nonempty for final_msg selection. - OpenAI Responses transport + agent_messages/agent_core_attempt adjustments; env docs and tests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
d1bcc4debe
commit
faeb067856
21 changed files with 2516 additions and 331 deletions
|
|
@ -3,6 +3,7 @@ from __future__ import annotations
|
|||
import base64
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sqlite3
|
||||
import sys
|
||||
import hashlib
|
||||
|
|
@ -1436,6 +1437,78 @@ class SqliteStore:
|
|||
(aid, tid, uid, sid, src, ts),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _attachment_ids_for_acl_from_payload_items(items: list[dict[str, Any]]) -> list[str]:
|
||||
"""Collect stable attachment ids from message attachment JSON (for ACL rows)."""
|
||||
out: list[str] = []
|
||||
seen: set[str] = set()
|
||||
ref_types = {"image_ref", "video_ref", "text_ref", "binary_ref"}
|
||||
relay_re = re.compile(r"^relay://attachments/[^/]+/([a-f0-9]{8,64})$", re.IGNORECASE)
|
||||
for a in items:
|
||||
if not isinstance(a, dict):
|
||||
continue
|
||||
typ = str(a.get("type") or "").strip().lower()
|
||||
aid = str(a.get("attachment_id") or a.get("attachmentId") or "").strip().lower()
|
||||
if typ in ref_types and aid:
|
||||
if aid not in seen:
|
||||
seen.add(aid)
|
||||
out.append(aid)
|
||||
continue
|
||||
if typ != "relay_pointer":
|
||||
continue
|
||||
if not aid:
|
||||
uri = str(a.get("pointer_uri") or "").strip()
|
||||
m = relay_re.match(uri)
|
||||
if m:
|
||||
aid = str(m.group(1) or "").strip().lower()
|
||||
if aid and aid not in seen:
|
||||
seen.add(aid)
|
||||
out.append(aid)
|
||||
return out
|
||||
|
||||
def sync_attachment_acl_from_chat_message_attachments(
|
||||
self,
|
||||
*,
|
||||
session_id: str,
|
||||
role: str,
|
||||
event_type: str | None,
|
||||
attachments: Any,
|
||||
) -> None:
|
||||
"""Best-effort: link chat_message attachments to session owner so strict ACL downloads work.
|
||||
|
||||
Tool results already link in runtime; assistant rows (image specialist, inbound, etc.)
|
||||
historically did not, which breaks ``AIA_ATTACHMENT_ACL_STRICT=1``.
|
||||
"""
|
||||
sid = str(session_id or "").strip()
|
||||
if not sid or attachments is None:
|
||||
return
|
||||
owner = self.get_ui_session_owner(session_id=sid) or {}
|
||||
tid = str(owner.get("tenant_id") or "").strip()
|
||||
uid = str(owner.get("user_id") or "").strip()
|
||||
if not tid or not uid:
|
||||
return
|
||||
items: list[dict[str, Any]] = []
|
||||
if isinstance(attachments, list):
|
||||
items = [x for x in attachments if isinstance(x, dict)]
|
||||
elif isinstance(attachments, dict):
|
||||
items = [attachments]
|
||||
else:
|
||||
return
|
||||
ids = self._attachment_ids_for_acl_from_payload_items(items)
|
||||
if not ids:
|
||||
return
|
||||
r = str(role or "").strip() or "-"
|
||||
ev = str(event_type or "").strip() or "-"
|
||||
src = f"chat_message:{r}:{ev}"[:240]
|
||||
for aid in ids:
|
||||
self.link_attachment_acl(
|
||||
tenant_id=tid,
|
||||
user_id=uid,
|
||||
session_id=sid,
|
||||
attachment_id=aid,
|
||||
source=src,
|
||||
)
|
||||
|
||||
def attachment_acl_allows_user(self, *, tenant_id: str, user_id: str, attachment_id: str) -> bool:
|
||||
tid = str(tenant_id or "").strip()
|
||||
uid = str(user_id or "").strip()
|
||||
|
|
@ -1922,6 +1995,15 @@ class SqliteStore:
|
|||
"UPDATE chat_session SET last_message_at = ? WHERE id = ?",
|
||||
(ts, session_id),
|
||||
)
|
||||
try:
|
||||
self.sync_attachment_acl_from_chat_message_attachments(
|
||||
session_id=str(session_id or "").strip(),
|
||||
role=str(role or ""),
|
||||
event_type=str(event_type or "").strip() or None,
|
||||
attachments=attachments,
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
return ChatMessage(
|
||||
id=msg_id,
|
||||
session_id=session_id,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue