Implement workspace history reconciliation

This commit is contained in:
2026-03-03 17:35:45 +00:00
parent 2898d9e832
commit 18351abb00
14 changed files with 556 additions and 57 deletions

View File

@@ -1025,7 +1025,18 @@ class SignalClient(ClientBase):
text=text,
attachments=attachments,
metadata=metadata,
detailed=True,
)
if isinstance(result, dict) and (not bool(result.get("ok"))):
status_value = int(result.get("status") or 0)
error_text = str(result.get("error") or "").strip()
recipient_value = str(result.get("recipient") or recipient).strip()
raise RuntimeError(
"signal_send_failed"
f" status={status_value or 'unknown'}"
f" recipient={recipient_value or 'unknown'}"
f" error={error_text or 'unknown'}"
)
if result is False or result is None:
raise RuntimeError("signal_send_failed")
transport.set_runtime_command_result(

View File

@@ -1,6 +1,7 @@
import asyncio
import base64
import logging
import re
import aiohttp
import orjson
@@ -9,6 +10,25 @@ from django.conf import settings
from rest_framework import status
log = logging.getLogger(__name__)
SIGNAL_UUID_PATTERN = re.compile(
r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$",
re.IGNORECASE,
)
def normalize_signal_recipient(recipient: str) -> str:
raw = str(recipient or "").strip()
if not raw:
return ""
if SIGNAL_UUID_PATTERN.fullmatch(raw):
return raw
if raw.startswith("+"):
digits = re.sub(r"[^0-9]", "", raw)
return f"+{digits}" if digits else raw
digits_only = re.sub(r"[^0-9]", "", raw)
if digits_only and raw.isdigit():
return f"+{digits_only}"
return raw
async def start_typing(uuid):
@@ -73,7 +93,9 @@ async def download_and_encode_base64(file_url, filename, content_type, session=N
return None
async def send_message_raw(recipient_uuid, text=None, attachments=None, metadata=None):
async def send_message_raw(
recipient_uuid, text=None, attachments=None, metadata=None, detailed=False
):
"""
Sends a message using the Signal REST API, ensuring attachment links are not included in the text body.
@@ -88,8 +110,9 @@ async def send_message_raw(recipient_uuid, text=None, attachments=None, metadata
base = getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080").rstrip("/")
url = f"{base}/v2/send"
normalized_recipient = normalize_signal_recipient(recipient_uuid)
data = {
"recipients": [recipient_uuid],
"recipients": [normalized_recipient],
"number": settings.SIGNAL_NUMBER,
"base64_attachments": [],
}
@@ -168,8 +191,37 @@ async def send_message_raw(recipient_uuid, text=None, attachments=None, metadata
ts = orjson.loads(response_text).get("timestamp", None)
return ts if ts else False
if index == len(payloads) - 1:
log.warning(
"Signal send failed status=%s recipient=%s body=%s",
response_status,
normalized_recipient,
response_text[:300],
)
if detailed:
return {
"ok": False,
"status": int(response_status),
"error": str(response_text or "").strip()[:500],
"recipient": normalized_recipient,
}
return False
if response_status not in {status.HTTP_400_BAD_REQUEST, status.HTTP_422_UNPROCESSABLE_ENTITY}:
if response_status not in {
status.HTTP_400_BAD_REQUEST,
status.HTTP_422_UNPROCESSABLE_ENTITY,
}:
log.warning(
"Signal send failed early status=%s recipient=%s body=%s",
response_status,
normalized_recipient,
response_text[:300],
)
if detailed:
return {
"ok": False,
"status": int(response_status),
"error": str(response_text or "").strip()[:500],
"recipient": normalized_recipient,
}
return False
log.warning(
"signal send quote payload rejected (%s), trying fallback shape: %s",
@@ -305,7 +357,7 @@ def send_message_raw_sync(recipient_uuid, text=None, attachments=None):
base = getattr(settings, "SIGNAL_HTTP_URL", "http://signal:8080").rstrip("/")
url = f"{base}/v2/send"
data = {
"recipients": [recipient_uuid],
"recipients": [normalize_signal_recipient(recipient_uuid)],
"number": settings.SIGNAL_NUMBER,
"base64_attachments": [],
}