fix: preserve Chatwoot message visibility and identity
This commit is contained in:
@@ -25,6 +25,92 @@ def _uuid_or_none(value):
|
||||
return value or None
|
||||
|
||||
|
||||
def _lock_source_identity(conn: Any, source_system: str, source_event_id: str | None) -> None:
|
||||
if source_event_id:
|
||||
conn.execute(text("SELECT pg_advisory_xact_lock(hashtext(:identity))"), {
|
||||
"identity": f"message:{source_system}:{source_event_id}",
|
||||
})
|
||||
|
||||
|
||||
def save_inbound_message(
|
||||
*,
|
||||
request: AnalyzeRequest,
|
||||
raw_event_id: Optional[str] = None,
|
||||
source_event_id: Optional[str] = None,
|
||||
raw_body: Optional[str] = None,
|
||||
clean_body: Optional[str] = None,
|
||||
) -> Tuple[str, Optional[str]]:
|
||||
"""Persist the canonical inbound message before classification.
|
||||
|
||||
Chatwoot identity comes from its raw event when callers do not pass it.
|
||||
The advisory lock makes retries safe even while older databases are waiting
|
||||
for the canonical identity index migration.
|
||||
"""
|
||||
if not settings.clientflow_persist:
|
||||
return "", source_event_id
|
||||
source_system = request.source or "manual"
|
||||
conversation_id = request.conversation_id or "manual"
|
||||
with engine.begin() as conn:
|
||||
if raw_event_id:
|
||||
raw = conn.execute(text("""
|
||||
SELECT source_system, source_event_id
|
||||
FROM raw_events WHERE id = CAST(:raw_event_id AS UUID)
|
||||
"""), {"raw_event_id": raw_event_id}).mappings().first()
|
||||
if raw:
|
||||
source_system = str(raw.get("source_system") or source_system)
|
||||
source_event_id = source_event_id or raw.get("source_event_id")
|
||||
_lock_source_identity(conn, source_system, source_event_id)
|
||||
existing = None
|
||||
if source_event_id:
|
||||
existing = conn.execute(text("""
|
||||
SELECT id::text FROM messages
|
||||
WHERE source_system=:source_system AND source_event_id=:source_event_id
|
||||
LIMIT 1
|
||||
"""), {"source_system": source_system, "source_event_id": source_event_id}).first()
|
||||
if not existing and raw_event_id:
|
||||
existing = conn.execute(text("""
|
||||
SELECT id::text FROM messages WHERE raw_event_id=CAST(:raw_event_id AS UUID) LIMIT 1
|
||||
"""), {"raw_event_id": raw_event_id}).first()
|
||||
if existing:
|
||||
message_id = str(existing[0])
|
||||
conn.execute(text("""
|
||||
UPDATE messages SET
|
||||
source_event_id=COALESCE(source_event_id, :source_event_id),
|
||||
raw_event_id=COALESCE(raw_event_id, CAST(:raw_event_id AS UUID)),
|
||||
conversation_id=COALESCE(NULLIF(conversation_id,''), :conversation_id),
|
||||
contact_id=COALESCE(NULLIF(contact_id,''), :contact_id),
|
||||
raw_body=COALESCE(NULLIF(raw_body,''), :raw_body),
|
||||
clean_body=COALESCE(NULLIF(clean_body,''), :clean_body)
|
||||
WHERE id=CAST(:message_id AS UUID)
|
||||
"""), {"message_id": message_id, "source_event_id": source_event_id,
|
||||
"raw_event_id": raw_event_id, "conversation_id": conversation_id,
|
||||
"contact_id": request.contact_id, "raw_body": raw_body or request.last_customer_message,
|
||||
"clean_body": clean_body or request.last_customer_message})
|
||||
else:
|
||||
row = conn.execute(text("""
|
||||
INSERT INTO messages (
|
||||
raw_event_id, source_system, source_event_id, conversation_id,
|
||||
contact_id, direction, raw_body, clean_body, previous_context, metadata
|
||||
) VALUES (
|
||||
CAST(:raw_event_id AS UUID), :source_system, :source_event_id,
|
||||
:conversation_id, :contact_id, 'inbound', :raw_body, :clean_body,
|
||||
:previous_context, CAST(:metadata AS JSONB)
|
||||
) RETURNING id::text
|
||||
"""), {"raw_event_id": raw_event_id, "source_system": source_system,
|
||||
"source_event_id": source_event_id, "conversation_id": conversation_id,
|
||||
"contact_id": request.contact_id, "raw_body": raw_body or request.last_customer_message,
|
||||
"clean_body": clean_body or request.last_customer_message,
|
||||
"previous_context": request.previous_context,
|
||||
"metadata": _json({"current_state": request.current_state.model_dump()})}).first()
|
||||
message_id = str(row[0])
|
||||
if raw_event_id:
|
||||
conn.execute(text("""UPDATE raw_events SET message_id=CAST(:message_id AS UUID)
|
||||
WHERE id=CAST(:raw_event_id AS UUID)"""), {
|
||||
"message_id": message_id, "raw_event_id": raw_event_id,
|
||||
})
|
||||
return message_id, source_event_id
|
||||
|
||||
|
||||
def save_action_run(
|
||||
*,
|
||||
request: AnalyzeRequest,
|
||||
@@ -37,53 +123,30 @@ def save_action_run(
|
||||
raw_body: Optional[str] = None,
|
||||
clean_body: Optional[str] = None,
|
||||
raw_event_id: Optional[str] = None,
|
||||
source_event_id: Optional[str] = None,
|
||||
message_id: Optional[str] = None,
|
||||
) -> Tuple[str, str]:
|
||||
if not settings.clientflow_persist:
|
||||
return "", ""
|
||||
|
||||
conversation_id = request.conversation_id or "manual"
|
||||
|
||||
with engine.begin() as conn:
|
||||
message_row = conn.execute(text("""
|
||||
INSERT INTO messages (
|
||||
raw_event_id,
|
||||
source_system,
|
||||
source_event_id,
|
||||
conversation_id,
|
||||
contact_id,
|
||||
direction,
|
||||
raw_body,
|
||||
clean_body,
|
||||
previous_context,
|
||||
metadata
|
||||
)
|
||||
VALUES (
|
||||
CAST(:raw_event_id AS UUID),
|
||||
:source_system,
|
||||
NULL,
|
||||
:conversation_id,
|
||||
:contact_id,
|
||||
'inbound',
|
||||
:raw_body,
|
||||
:clean_body,
|
||||
:previous_context,
|
||||
CAST(:metadata AS JSONB)
|
||||
)
|
||||
RETURNING id::text
|
||||
"""), {
|
||||
"raw_event_id": raw_event_id,
|
||||
"source_system": request.source or "manual",
|
||||
"conversation_id": conversation_id,
|
||||
"contact_id": request.contact_id,
|
||||
"raw_body": raw_body or request.last_customer_message,
|
||||
"clean_body": clean_body or request.last_customer_message,
|
||||
"previous_context": request.previous_context,
|
||||
"metadata": _json({
|
||||
"current_state": request.current_state.model_dump(),
|
||||
}),
|
||||
}).fetchone()
|
||||
if not message_id:
|
||||
message_id, source_event_id = save_inbound_message(
|
||||
request=request, raw_event_id=raw_event_id, source_event_id=source_event_id,
|
||||
raw_body=raw_body, clean_body=clean_body,
|
||||
)
|
||||
|
||||
message_id = message_row[0]
|
||||
with engine.begin() as conn:
|
||||
_lock_source_identity(conn, request.source or "manual", source_event_id)
|
||||
existing_run = conn.execute(text("""
|
||||
SELECT id::text FROM action_runs
|
||||
WHERE message_id=CAST(:message_id AS UUID)
|
||||
OR (CAST(:raw_event_id AS UUID) IS NOT NULL AND raw_event_id=CAST(:raw_event_id AS UUID))
|
||||
ORDER BY created_at LIMIT 1
|
||||
"""), {"message_id": message_id, "raw_event_id": raw_event_id}).first()
|
||||
if existing_run:
|
||||
return str(existing_run[0]), str(message_id)
|
||||
|
||||
run_row = conn.execute(text("""
|
||||
INSERT INTO action_runs (
|
||||
|
||||
Reference in New Issue
Block a user