"""Private, immutable snapshots for explicitly shared, read-only conversations. This module does not discover sessions, read transcripts, authenticate requests, or choose a public origin. The caller must project and review the user/assistant text before calling ``create_share``. Only the creation result contains the raw bearer token; reopening or listing the store cannot recover a share URL. """ from __future__ import annotations import base64 from contextlib import contextmanager from datetime import datetime, timezone import hashlib import hmac import html import json import math import os from pathlib import Path import re import secrets import sqlite3 import stat import time from typing import Any, Callable, Iterator from shared_chat_videos import normalize_shared_chat_videos MAX_MESSAGE_TEXT_BYTES = 256 * 1024 MAX_SNAPSHOT_BYTES = 2 * 1024 * 1024 MAX_RENDER_BYTES = 16 * 1024 * 1024 MAX_SNAPSHOT_PAGE_MESSAGES = 100 MAX_TITLE_CHARACTERS = 256 MAX_LIST_LIMIT = 100 MAX_UNIX_TIMESTAMP = 253402300799 DEFAULT_TITLE = "Shared conversation" TOKEN_PATTERN = re.compile(r"[A-Za-z0-9_-]{43}\Z", re.ASCII) SHARE_ID_PATTERN = re.compile(r"share_[a-f0-9]{32}\Z", re.ASCII) class PublicChatShareValidationError(ValueError): """Input is not an already-projected, bounded public conversation.""" class PublicChatShareUnavailable(LookupError): """Use the same public 404 response for every unavailable share.""" def __init__(self) -> None: super().__init__("Shared conversation is unavailable.") def _timestamp(value: Any, label: str) -> int | float: if ( isinstance(value, bool) or not isinstance(value, (int, float)) or not 0 <= value <= MAX_UNIX_TIMESTAMP or not math.isfinite(value) ): raise PublicChatShareValidationError(f"{label} must be finite Unix seconds.") return value def _utf8_size(value: str, label: str, limit: int) -> int: # Check character count first to avoid encoding a grossly oversized input. if len(value) > limit: raise PublicChatShareValidationError(f"{label} is too large.") try: length = len(value.encode("utf-8")) except UnicodeEncodeError as exc: raise PublicChatShareValidationError(f"{label} must be valid UTF-8.") from exc if length > limit: raise PublicChatShareValidationError(f"{label} is too large.") return length def _session_id(value: Any) -> str: if not isinstance(value, str) or not value or "\x00" in value: raise PublicChatShareValidationError("session_id must be a nonempty string.") _utf8_size(value, "session_id", 1024) return value def _snapshot(messages: Any, title: Any, created_at: Any) -> tuple[dict[str, Any], bytes]: if title is None: title = DEFAULT_TITLE if not isinstance(title, str) or not title.strip() or len(title) > MAX_TITLE_CHARACTERS: raise PublicChatShareValidationError("title must contain 1 to 256 characters.") total_bytes = _utf8_size(title, "title", MAX_TITLE_CHARACTERS * 4) _timestamp(created_at, "created_at") if not isinstance(messages, list) or not messages: raise PublicChatShareValidationError("messages must contain at least one item.") serialized_bytes = len(json.dumps({"title": title, "created_at": created_at, "messages": []}, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8")) projected: list[dict[str, Any]] = [] for message in messages: if not isinstance(message, dict) or set(message) - {"role", "text", "timestamp", "videos"}: raise PublicChatShareValidationError("Messages may contain only role, text, timestamp, and videos.") role = message.get("role") text = message.get("text") if role not in ("user", "assistant") or not isinstance(text, str): raise PublicChatShareValidationError("Each message needs a user/assistant role and text.") total_bytes += _utf8_size(text, "message text", MAX_MESSAGE_TEXT_BYTES) if total_bytes > MAX_SNAPSHOT_BYTES: raise PublicChatShareValidationError("Snapshot is too large.") item = {"role": role, "text": text} if "videos" in message: try: videos = normalize_shared_chat_videos(message["videos"]) except ValueError as exc: raise PublicChatShareValidationError("Invalid shared video metadata.") from exc if videos: item["videos"] = videos if "timestamp" in message: item["timestamp"] = _timestamp(message["timestamp"], "message timestamp") serialized_bytes += len(json.dumps(item, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8")) + bool(projected) if serialized_bytes > MAX_SNAPSHOT_BYTES: raise PublicChatShareValidationError("Snapshot is too large.") projected.append(item) snapshot = {"title": title, "created_at": created_at, "messages": projected} encoded = json.dumps(snapshot, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8") if len(encoded) > MAX_SNAPSHOT_BYTES: raise PublicChatShareValidationError("Snapshot is too large.") return snapshot, encoded class PublicChatShareStore: """A dedicated owner-only directory containing a durable SQLite database. A fresh SQLite connection is used per operation, so instances can be shared between request threads. Snapshot rows and revocations are append-only. Revocation blocks subsequent lookups; it cannot retract bytes already sent by a request that read the snapshot before the revocation committed. """ def __init__(self, storage_root: str | os.PathLike[str], *, now: Callable[[], float] = time.time): root = Path(storage_root) if not root.is_absolute(): raise PublicChatShareValidationError("storage_root must be an explicit absolute directory.") root.mkdir(mode=0o700, parents=True, exist_ok=True) self.storage_root = root self.database_path = root / "snapshots.sqlite3" self._now = now self._check_directory() flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_NOFOLLOW", 0) try: descriptor = os.open(self.database_path, flags, 0o600) except FileExistsError: # Inspect existing paths before opening them: a FIFO or device must # not be opened, and an existing unsafe file must not be chmodded. self._check_database_stat(self.database_path.lstat()) else: try: self._check_database_stat(os.fstat(descriptor)) finally: os.close(descriptor) with self._connection(write=True) as connection: connection.execute("BEGIN IMMEDIATE") self._ensure_schema(connection) @staticmethod def _create_tables(connection, suffix=""): connection.execute( f"""CREATE TABLE IF NOT EXISTS public_chat_shares{suffix} ( share_id TEXT PRIMARY KEY, session_id TEXT NOT NULL, token_hash BLOB NOT NULL UNIQUE CHECK(length(token_hash) = 32), title TEXT NOT NULL, created_at REAL NOT NULL, expires_at REAL, message_count INTEGER NOT NULL CHECK(message_count >= 1), snapshot_json BLOB NOT NULL CHECK(length(snapshot_json) <= 2097152), snapshot_sha256 BLOB NOT NULL CHECK(length(snapshot_sha256) = 32) )""" ) connection.execute( f"""CREATE TABLE IF NOT EXISTS public_chat_share_revocations{suffix} ( share_id TEXT PRIMARY KEY REFERENCES public_chat_shares{suffix}(share_id), revoked_at REAL NOT NULL )""" ) @classmethod def _ensure_schema(cls, connection): """Authenticated-write-only, atomic preservation of immutable v1 rows.""" version = connection.execute("PRAGMA user_version").fetchone()[0] if version not in (0, 1, 2, 3): raise OSError("Unsupported public chat share database version.") if version == 1: cls._create_tables(connection, "_v2") connection.execute("INSERT INTO public_chat_shares_v2 SELECT * FROM public_chat_shares") connection.execute("INSERT INTO public_chat_share_revocations_v2 SELECT * FROM public_chat_share_revocations") connection.execute("DROP TABLE public_chat_share_revocations") connection.execute("DROP TABLE public_chat_shares") connection.execute("ALTER TABLE public_chat_shares_v2 RENAME TO public_chat_shares") connection.execute("ALTER TABLE public_chat_share_revocations_v2 RENAME TO public_chat_share_revocations") else: cls._create_tables(connection) connection.execute( "CREATE INDEX IF NOT EXISTS public_chat_shares_session ON public_chat_shares(session_id, created_at DESC)" ) for table in ("public_chat_shares", "public_chat_share_revocations"): for operation in ("UPDATE", "DELETE"): connection.execute( f"CREATE TRIGGER IF NOT EXISTS {table}_no_{operation.lower()} " f"BEFORE {operation} ON {table} BEGIN " "SELECT RAISE(ABORT, 'Public chat shares are immutable'); END" ) connection.execute("""CREATE TABLE IF NOT EXISTS public_chat_share_pages ( share_id TEXT NOT NULL REFERENCES public_chat_shares(share_id) DEFERRABLE INITIALLY DEFERRED, page_index INTEGER NOT NULL CHECK(page_index >= 1), snapshot_json BLOB NOT NULL CHECK(length(snapshot_json) <= 2097152), snapshot_sha256 BLOB NOT NULL CHECK(length(snapshot_sha256) = 32), PRIMARY KEY(share_id, page_index))""") for operation in ("UPDATE", "DELETE"): connection.execute(f"CREATE TRIGGER IF NOT EXISTS public_chat_share_pages_no_{operation.lower()} " f"BEFORE {operation} ON public_chat_share_pages BEGIN " "SELECT RAISE(ABORT, 'Public chat shares are immutable'); END") connection.execute("PRAGMA user_version = 3") @classmethod def open_existing( cls, storage_root: str | os.PathLike[str], *, now: Callable[[], float] = time.time, ) -> "PublicChatShareStore": """Open an initialized store without creating paths or writing schema. Used by cold public viewers after restart. A damaged/missing/unknown schema is not repaired by an anonymous request; only authenticated creation may initialize a new store. """ root = Path(storage_root) if not root.is_absolute(): raise PublicChatShareValidationError("storage_root must be an explicit absolute directory.") instance = cls.__new__(cls) instance.storage_root = root instance.database_path = root / "snapshots.sqlite3" instance._now = now with instance._connection() as connection: if connection.execute("PRAGMA user_version").fetchone()[0] not in (1, 2, 3): raise OSError("Unsupported public chat share database version.") return instance def _check_directory(self) -> None: info = self.storage_root.lstat() if not stat.S_ISDIR(info.st_mode) or info.st_mode & 0o077 or info.st_uid != os.getuid(): raise OSError("Public chat share storage must be a private, owned, non-symlink directory.") @staticmethod def _check_database_stat(info: os.stat_result) -> None: if ( not stat.S_ISREG(info.st_mode) or info.st_mode & 0o077 or info.st_uid != os.getuid() or info.st_nlink != 1 ): raise OSError("Public chat share database must be a private, owned, single-link regular file.") @contextmanager def _connection(self, *, write: bool = False) -> Iterator[sqlite3.Connection]: self._check_directory() self._check_database_stat(self.database_path.lstat()) # The checked database must already exist; SQLite must not create an # alternate path. The private directory also protects rollback journals. mode = "rw" if write else "ro" connection = sqlite3.connect(f"{self.database_path.as_uri()}?mode={mode}", uri=True, timeout=5) connection.row_factory = sqlite3.Row try: connection.execute("PRAGMA foreign_keys = ON") connection.execute("PRAGMA trusted_schema = OFF") if write: connection.execute("PRAGMA synchronous = FULL") with connection: yield connection finally: connection.close() @staticmethod def _metadata(row: sqlite3.Row) -> dict[str, Any]: return {key: row[key] for key in ( "share_id", "session_id", "title", "created_at", "expires_at", "revoked_at", "message_count" )} def create_share( self, session_id: str, messages: list[dict[str, Any]], *, title: str | None = None, expires_at: int | float | None = None, ) -> dict[str, Any]: session_id = _session_id(session_id) created_at = _timestamp(self._now(), "current time") if expires_at is not None and _timestamp(expires_at, "expires_at") <= created_at: raise PublicChatShareValidationError("expires_at must be in the future.") snapshot, encoded = _snapshot(messages, title, created_at) token = secrets.token_urlsafe(32) share_id = "share_" + secrets.token_hex(16) token_hash = hashlib.sha256(token.encode("ascii")).digest() with self._connection(write=True) as connection: connection.execute("BEGIN IMMEDIATE") # An anonymous view may already have cached a read-only v1 store. self._ensure_schema(connection) connection.execute( """INSERT INTO public_chat_shares (share_id, session_id, token_hash, title, created_at, expires_at, message_count, snapshot_json, snapshot_sha256) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)""", (share_id, session_id, token_hash, snapshot["title"], created_at, expires_at, len(snapshot["messages"]), encoded, hashlib.sha256(encoded).digest()), ) return { "share_id": share_id, "session_id": session_id, "title": snapshot["title"], "created_at": created_at, "expires_at": expires_at, "revoked_at": None, "message_count": len(snapshot["messages"]), "token": token, } def list_shares(self, session_id: str, *, limit: int = MAX_LIST_LIMIT) -> list[dict[str, Any]]: session_id = _session_id(session_id) if isinstance(limit, bool) or not isinstance(limit, int) or not 1 <= limit <= MAX_LIST_LIMIT: raise PublicChatShareValidationError("limit must be an integer between 1 and 100.") with self._connection() as connection: rows = connection.execute( """SELECT s.share_id, s.session_id, s.title, s.created_at, s.expires_at, s.message_count, r.revoked_at FROM public_chat_shares AS s LEFT JOIN public_chat_share_revocations AS r USING (share_id) WHERE s.session_id = ? ORDER BY s.created_at DESC, s.share_id DESC LIMIT ?""", (session_id, limit), ).fetchall() return [self._metadata(row) for row in rows] def revoke_share(self, share_id: str, *, session_id: str) -> bool: session_id = _session_id(session_id) if not isinstance(share_id, str) or SHARE_ID_PATTERN.fullmatch(share_id) is None: return False revoked_at = _timestamp(self._now(), "current time") with self._connection(write=True) as connection: connection.execute("BEGIN IMMEDIATE") exists = connection.execute( "SELECT 1 FROM public_chat_shares WHERE share_id = ? AND session_id = ?", (share_id, session_id), ).fetchone() if exists is None: return False connection.execute( "INSERT OR IGNORE INTO public_chat_share_revocations (share_id, revoked_at) VALUES (?, ?)", (share_id, revoked_at), ) return True def create_streamed_share(self, session_id: str, load_messages: Callable, *, title=None, expires_at=None) -> dict[str, Any]: """Capture arbitrarily many bounded pages atomically from a message sink. The loader must emit projected messages synchronously and raise before returning if a reviewed digest changed. No whole-chat list/JSON is kept. Existing snapshots retain their original first-page representation. """ session_id = _session_id(session_id) created_at = _timestamp(self._now(), "current time") if expires_at is not None and _timestamp(expires_at, "expires_at") <= created_at: raise PublicChatShareValidationError("expires_at must be in the future.") # Validate metadata before starting the transcript scan. seed, _ = _snapshot([{"role": "user", "text": ""}], title, created_at) title = seed["title"] token, share_id = secrets.token_urlsafe(32), "share_" + secrets.token_hex(16) first_page = None page: list[dict[str, Any]] = [] page_bytes = 0 page_index = 0 total = 0 overhead = len(json.dumps({"title": title, "created_at": created_at, "messages": []}, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8")) with self._connection(write=True) as connection: connection.execute("BEGIN IMMEDIATE") self._ensure_schema(connection) def flush() -> None: nonlocal first_page, page_index, page_bytes if not page: return _, encoded = _snapshot(page, title, created_at) if page_index == 0: first_page = encoded else: connection.execute("INSERT INTO public_chat_share_pages VALUES(?,?,?,?)", (share_id, page_index, encoded, hashlib.sha256(encoded).digest())) page_index += 1 page.clear() page_bytes = 0 def emit(message) -> None: nonlocal total, page_bytes checked, _ = _snapshot([message], title, created_at) item = checked["messages"][0] size = len(json.dumps(item, ensure_ascii=False, separators=(",", ":"), allow_nan=False).encode("utf-8")) if page and (len(page) >= MAX_SNAPSHOT_PAGE_MESSAGES or overhead + page_bytes + size + len(page) > MAX_SNAPSHOT_BYTES): flush() page.append(item) page_bytes += size total += 1 load_messages(emit) flush() if first_page is None: raise PublicChatShareValidationError("messages must contain at least one item.") connection.execute("""INSERT INTO public_chat_shares (share_id,session_id,token_hash,title,created_at,expires_at,message_count,snapshot_json,snapshot_sha256) VALUES(?,?,?,?,?,?,?,?,?)""", (share_id, session_id, hashlib.sha256(token.encode("ascii")).digest(), title, created_at, expires_at, total, first_page, hashlib.sha256(first_page).digest())) return {"share_id": share_id, "session_id": session_id, "title": title, "created_at": created_at, "expires_at": expires_at, "revoked_at": None, "message_count": total, "token": token} def get_snapshot(self, token: str, *, share_id: str | None = None) -> dict[str, Any]: return self.get_snapshot_page(token, share_id=share_id, page=0)["snapshot"] def get_snapshot_page(self, token: str, *, share_id: str | None = None, page: int | None = None) -> dict[str, Any]: value = self._get_snapshot_page(token, share_id=share_id, page=page) value.pop("session_id") return value def _get_snapshot_page(self, token: str, *, share_id: str | None = None, page: int | None = None) -> dict[str, Any]: # Invalid input is rejected before storage I/O, without echoing a token. if not isinstance(token, str) or TOKEN_PATTERN.fullmatch(token) is None: raise PublicChatShareUnavailable() if share_id is not None and (not isinstance(share_id, str) or SHARE_ID_PATTERN.fullmatch(share_id) is None): raise PublicChatShareUnavailable() if page is not None and (type(page) is not int or not 0 <= page <= 2**31 - 1): raise PublicChatShareUnavailable() token_hash = hashlib.sha256(token.encode("ascii")).digest() current_time = _timestamp(self._now(), "current time") with self._connection() as connection: row = connection.execute( """SELECT s.share_id, s.session_id, s.message_count, s.snapshot_json, s.snapshot_sha256 FROM public_chat_shares AS s WHERE s.token_hash = ? AND (? IS NULL OR s.share_id = ?) AND (s.expires_at IS NULL OR s.expires_at > ?) AND NOT EXISTS (SELECT 1 FROM public_chat_share_revocations AS r WHERE r.share_id = s.share_id)""", (token_hash, share_id, share_id, current_time), ).fetchone() if row is None: raise PublicChatShareUnavailable() session_id = row["session_id"] message_count = row["message_count"] has_pages = connection.execute("SELECT 1 FROM sqlite_master WHERE type='table' AND name='public_chat_share_pages'").fetchone() page_count = 1 + (connection.execute("SELECT count(*) FROM public_chat_share_pages WHERE share_id=?", (row["share_id"],)).fetchone()[0] if has_pages else 0) page = page_count - 1 if page is None else page if page >= page_count: raise PublicChatShareUnavailable() if page: row = connection.execute("SELECT snapshot_json,snapshot_sha256 FROM public_chat_share_pages WHERE share_id=? AND page_index=?", (row["share_id"], page)).fetchone() if row is None: raise PublicChatShareUnavailable() encoded = row["snapshot_json"] digest = row["snapshot_sha256"] if ( not isinstance(encoded, bytes) or len(encoded) > MAX_SNAPSHOT_BYTES or not isinstance(digest, bytes) or not hmac.compare_digest(hashlib.sha256(encoded).digest(), digest) ): raise PublicChatShareUnavailable() try: decoded = json.loads(encoded) if not isinstance(decoded, dict) or set(decoded) != {"title", "created_at", "messages"}: raise PublicChatShareUnavailable() snapshot, _ = _snapshot(decoded["messages"], decoded["title"], decoded["created_at"]) except (ValueError, UnicodeError, RecursionError) as exc: raise PublicChatShareUnavailable() from exc return {"snapshot": snapshot, "page": page, "page_count": page_count, "message_count": message_count, "session_id": session_id} def get_snapshot_video(self, token: str, *, page: int, message_index: int, video_index: int, share_id: str | None = None) -> dict[str, Any]: """Resolve only a descriptor already frozen in this exact bounded page.""" if any(type(index) is not int or not 0 <= index <= 2**31 - 1 for index in (page, message_index, video_index)): raise PublicChatShareUnavailable() value = self._get_snapshot_page(token, share_id=share_id, page=page) try: video = value["snapshot"]["messages"][message_index]["videos"][video_index] except (IndexError, KeyError): raise PublicChatShareUnavailable() from None return {"session_id": value["session_id"], "video": video} def authorize_access(self, token: str, *, share_id: str | None = None) -> None: """Cheap read-only stream recheck; never reload or render a transcript page.""" if (not isinstance(token, str) or TOKEN_PATTERN.fullmatch(token) is None or share_id is not None and (not isinstance(share_id, str) or SHARE_ID_PATTERN.fullmatch(share_id) is None)): raise PublicChatShareUnavailable() token_hash = hashlib.sha256(token.encode("ascii")).digest() current_time = _timestamp(self._now(), "current time") with self._connection() as connection: row = connection.execute( """SELECT 1 FROM public_chat_shares AS s WHERE s.token_hash = ? AND (? IS NULL OR s.share_id = ?) AND (s.expires_at IS NULL OR s.expires_at > ?) AND NOT EXISTS (SELECT 1 FROM public_chat_share_revocations AS r WHERE r.share_id = s.share_id)""", (token_hash, share_id, share_id, current_time), ).fetchone() if row is None: raise PublicChatShareUnavailable() _STYLE = """html{color-scheme:light dark;--page:#f7f8fa;--surface:#fff;--ink:#222831;--muted:#69727e;--line:#e4e7eb;--green:#287346;--user:#e6f3e9;--user-line:#d1e7d8;--code:#f3f5f7;--mark:#ecf5ee} *{box-sizing:border-box}body{margin:0;background:var(--page);color:var(--ink);font:15px/1.7 -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;-webkit-font-smoothing:antialiased}::selection{background:#9cceb1;color:#14261c} .masthead{height:76px;border-bottom:1px solid var(--line);background:var(--surface)}.masthead-inner{max-width:1008px;height:100%;margin:0 auto;padding:0 32px;display:flex;align-items:center;justify-content:space-between;gap:16px}.brand{display:flex;align-items:center;gap:10px;font-size:15px;font-weight:650;letter-spacing:-.35px}.brand-mark{width:26px;height:26px;border-radius:8px;background:var(--green);color:#fff;display:grid;place-items:center;font-size:17px;font-weight:750;line-height:1}.masthead-label{color:var(--muted);font-size:12px} main{max-width:864px;margin:0 auto;padding:54px 32px 36px}.conversation-header{padding-bottom:30px;border-bottom:1px solid var(--line);margin-bottom:32px}.eyebrow{display:flex;align-items:center;gap:12px;margin-bottom:16px;color:var(--muted);font-size:11px;font-weight:650;letter-spacing:1.4px;text-transform:uppercase}.view-badge{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border:1px solid var(--user-line);border-radius:6px;background:var(--mark);color:var(--green);font-size:11px;line-height:1.5;letter-spacing:0;text-transform:none}.view-badge:before{content:"";width:5px;height:5px;border-radius:50%;background:currentColor}h1{margin:0;font-size:34px;line-height:1.2;font-weight:650;letter-spacing:-1.15px;overflow-wrap:anywhere}.description{margin:13px 0 17px;color:var(--muted);font-size:14px}.metadata{display:flex;flex-wrap:wrap;align-items:center;gap:6px 10px;color:var(--muted);font-size:12px}.metadata-separator{opacity:.5} .transcript{display:flex;flex-direction:column;gap:28px}.message{min-width:0}.message-header{display:flex;align-items:center;gap:8px;margin-bottom:9px}.avatar{display:grid;place-items:center;width:25px;height:25px;border-radius:8px;background:var(--surface);border:1px solid var(--line);color:var(--muted);font-size:11px;font-weight:700}.message-label{margin:0;font-size:12px;font-weight:650;letter-spacing:.05px}.message time{font-size:11px;color:var(--muted);margin-left:3px}.message-body{padding:21px 24px;border:1px solid var(--line);border-radius:4px 16px 16px 16px;background:var(--surface);overflow-wrap:anywhere;min-width:0}.user{width:88%;align-self:flex-end}.user .message-header{justify-content:flex-end}.user .avatar{background:var(--mark);border-color:var(--user-line);color:var(--green)}.user .message-body{background:var(--user);border-color:var(--user-line);border-radius:16px 4px 16px 16px}.plain-text{white-space:pre-wrap;tab-size:4} .markdown>:first-child{margin-top:0}.markdown>:last-child{margin-bottom:0}.markdown p{margin:0 0 16px;white-space:pre-wrap}.markdown h2,.markdown h3,.markdown h4,.markdown h5,.markdown h6{margin:24px 0 10px;font-weight:650;line-height:1.4;letter-spacing:-.25px}.markdown h2{font-size:21px}.markdown h3{font-size:18px}.markdown h4,.markdown h5,.markdown h6{font-size:16px}.markdown ul,.markdown ol{padding-left:23px;margin:10px 0 18px}.markdown li{padding-left:3px;margin:5px 0;white-space:pre-wrap}.markdown li::marker{color:var(--muted)}.markdown blockquote{margin:18px 0;padding:3px 0 3px 17px;border-left:3px solid var(--user-line);color:var(--muted);white-space:pre-wrap}.markdown code{font:12.5px/1.65 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;background:var(--code);border-radius:4px;padding:2px 5px}.code-block{margin:18px 0;border:1px solid var(--line);border-radius:10px;overflow:hidden;background:var(--code)}.code-language{border-bottom:1px solid var(--line);padding:7px 14px;font-size:11px;color:var(--muted)}.code-block pre{margin:0;padding:14px 16px;overflow:auto;tab-size:4;white-space:pre;overscroll-behavior-x:contain}.code-block code{padding:0;border-radius:0;font-size:12px;background:transparent}.table-wrap{max-width:100%;overflow:auto;margin:18px 0;border:1px solid var(--line);border-radius:9px;overscroll-behavior-x:contain}.markdown table{width:100%;border-collapse:collapse;font-size:13px;line-height:1.55}.markdown th,.markdown td{padding:11px 13px;text-align:left;border-bottom:1px solid var(--line);min-width:90px;overflow-wrap:anywhere}.markdown th{background:var(--code);font-weight:600}.markdown tbody tr:last-child td{border-bottom:0}.markdown hr{border:0;border-top:1px solid var(--line);margin:24px 0} .conversation-footer{margin-top:38px;padding-top:23px;border-top:1px solid var(--line);text-align:center;color:var(--muted);font-size:12px}.conversation-footer p{margin:0 0 4px}.footer-brand{font-size:11px;opacity:.8} .unlock-main{max-width:520px;padding-top:76px}.unlock-card{padding:30px;border:1px solid var(--line);border-radius:18px;background:var(--surface)}.unlock-card h1{font-size:27px;letter-spacing:-.7px}.unlock-card label{display:block;font-size:13px;font-weight:600;margin:20px 0 7px}.unlock-card input[type=password]{width:100%;min-width:0;padding:12px;border:1px solid var(--line);border-radius:9px;background:var(--page);color:var(--ink);font:14px ui-monospace,SFMono-Regular,Menlo,monospace}.unlock-card input:focus-visible,.unlock-card button:focus-visible{outline:2px solid var(--green);outline-offset:3px}.unlock-card .remember{display:flex;gap:8px;align-items:center;margin:16px 0;color:var(--muted);font-size:12px;font-weight:400}.remember input{accent-color:var(--green)}.unlock-card button{width:100%;padding:12px 16px;border:1px solid var(--user-line);border-radius:9px;background:var(--user);color:var(--green);font:600 14px -apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;cursor:pointer}.unlock-error{font-size:13px;color:#bd4444}.unlock-help{margin:18px 0 0;font-size:12px;color:var(--muted)} .pagination{display:flex;justify-content:space-between;align-items:center;gap:12px;margin:22px 0;font-size:12px;color:var(--muted)}.pagination a{display:inline-block;border:1px solid var(--line);border-radius:8px;padding:7px 12px;color:var(--green);background:var(--surface);text-decoration:none}.pagination a:hover{background:var(--mark)}.pagination a:focus-visible{outline:2px solid var(--green);outline-offset:3px}.pagination span{flex:1;text-align:center} @media(prefers-color-scheme:dark){html{--page:#141619;--surface:#1b1e22;--ink:#e7e9ed;--muted:#9da5b0;--line:#2b3037;--green:#8bd5a5;--user:#213b2c;--user-line:#31513c;--code:#171a1e;--mark:#22372b}.brand-mark{background:#397f51;color:#f4fff7}} @media(max-width:600px){.masthead{height:62px}.masthead-inner{padding:0 20px}.masthead-label{font-size:11px}main{padding:32px 18px 28px}h1{font-size:27px;letter-spacing:-.8px}.conversation-header{padding-bottom:24px;margin-bottom:25px}.eyebrow{margin-bottom:13px;font-size:10px}.description{font-size:13px}.metadata{font-size:11px;gap:5px 7px}.transcript{gap:24px}.user{width:94%}.message-body{padding:17px 18px;font-size:14px}.message time{font-size:10px}.markdown h2{font-size:19px}.markdown h3{font-size:17px}.code-block pre{padding:12px}.markdown th,.markdown td{padding:9px 11px}.conversation-footer{font-size:11px}} @media print{html{color-scheme:light;--page:#fff;--surface:#fff;--ink:#111;--muted:#555;--line:#ddd;--user:#f0f7f2;--user-line:#ddd;--code:#f5f5f5;--mark:#f0f7f2;--green:#286b43}body{font-size:11pt}.masthead{height:48px}main{max-width:none;padding:24px 0}.message-body{break-inside:avoid}.code-block pre{white-space:pre-wrap;overflow-wrap:anywhere}.table-wrap{overflow:visible}.conversation-footer{margin-top:24px}} """ _STYLE += "\n.shared-video{margin:16px 0 0;white-space:normal}.shared-video video{display:block;width:100%;max-height:65vh;background:#111;border-radius:10px}.shared-video figcaption,.video-caption{margin-top:7px;color:var(--muted);font-size:12px;overflow-wrap:anywhere}" _STYLE_HASH = base64.b64encode(hashlib.sha256(_STYLE.encode("utf-8")).digest()).decode("ascii") # A deliberately small formatting vocabulary. Every text fragment is escaped; # raw HTML, links, images, embeds, and arbitrary attributes are never emitted. # Keeping this local also avoids a Markdown dependency in the server runtime. _INLINE_MARKDOWN = re.compile(r"`([^`\n]+)`|\*\*([^*\n]+)\*\*|\*([^*\n]+)\*") _FENCE = re.compile(r"^ {0,3}(`{3,}|~{3,})(.*)$") _HEADING = re.compile(r"^ {0,3}(#{1,6})[ \t]+(.+)$") _LIST_ITEM = re.compile(r"^ {0,3}(?:([-+*])|([0-9]{1,9})[.)])\s+(.+)$") _TABLE_DIVIDER = re.compile(r"^:?-{3,}:?$") def _render_inline_text(text: str) -> str: parts = [] position = 0 for match in _INLINE_MARKDOWN.finditer(text): parts.append(html.escape(text[position:match.start()], quote=True)) for index, tag in ((1, "code"), (2, "strong"), (3, "em")): if match[index] is not None: parts.append(f"<{tag}>{html.escape(match[index], quote=True)}") break position = match.end() parts.append(html.escape(text[position:], quote=True)) return "".join(parts) def _table_cells(line: str) -> list[str]: return [cell.strip() for cell in line.strip().strip("|").split("|")] def _render_message_markdown(text: str) -> str: """Format common assistant Markdown without interpreting HTML or URLs.""" lines = text.splitlines() parts: list[str] = [] paragraph: list[str] = [] index = 0 def flush_paragraph() -> None: if paragraph: parts.append(f'

{_render_inline_text(chr(10).join(paragraph))}

') paragraph.clear() while index < len(lines): line = lines[index] fence = _FENCE.match(line) heading = _HEADING.match(line) item = _LIST_ITEM.match(line) if not line.strip(): flush_paragraph() index += 1 continue if fence: flush_paragraph() marker = fence[1] label = html.escape(fence[2].strip(), quote=True) code = [] index += 1 while index < len(lines): closing = lines[index].strip() if len(closing) >= len(marker) and set(closing) == {marker[0]}: index += 1 break code.append(lines[index]) index += 1 parts.append('
') if label: parts.append(f'
{label}
') parts.append(f'
{html.escape(chr(10).join(code), quote=True)}
') continue if heading: flush_paragraph() level = min(len(heading[1]) + 1, 6) parts.append(f"{_render_inline_text(heading[2])}") elif line.strip() in ("---", "***", "___"): flush_paragraph() parts.append("
") elif item: flush_paragraph() tag = "ul" if item[1] else "ol" start = f' start="{int(item[2])}"' if item[2] and item[2] != "1" else "" parts.append(f"<{tag}{start}>") while index < len(lines): match = _LIST_ITEM.match(lines[index]) if not match or ("ul" if match[1] else "ol") != tag: break parts.append(f"
  • {_render_inline_text(match[3])}
  • ") index += 1 parts.append(f"") continue elif line.lstrip().startswith("> "): flush_paragraph() quote = [] while index < len(lines) and lines[index].lstrip().startswith("> "): quote.append(lines[index].lstrip()[2:]) index += 1 parts.append(f'
    {_render_inline_text(chr(10).join(quote))}
    ') continue elif "|" in line and index + 1 < len(lines) and "|" in lines[index + 1]: columns = _table_cells(line) dividers = _table_cells(lines[index + 1]) if len(columns) == len(dividers) and 1 < len(columns) <= 64 and all( _TABLE_DIVIDER.fullmatch(cell) for cell in dividers ): flush_paragraph() parts.append('
    ') parts.extend(f'' for cell in columns) parts.append("") index += 2 while index < len(lines) and "|" in lines[index]: cells = _table_cells(lines[index]) if len(cells) != len(columns): break parts.append("" + "".join(f"" for cell in cells) + "") index += 1 parts.append("
    {_render_inline_text(cell)}
    {_render_inline_text(cell)}
    ") continue paragraph.append(line) else: paragraph.append(line) index += 1 flush_paragraph() return "".join(parts) def public_chat_share_headers(*, allow_unlock_form: bool = False) -> dict[str, str]: """Use these headers for both successful viewer pages and unavailable pages.""" return { "Content-Type": "text/html; charset=utf-8", "Content-Security-Policy": ( "default-src 'none'; script-src 'none'; " f"style-src 'sha256-{_STYLE_HASH}'; " "img-src 'none'; media-src 'self'; connect-src 'none'; base-uri 'none'; " + ("form-action 'self'; frame-ancestors 'none'; sandbox allow-forms allow-same-origin" if allow_unlock_form else "form-action 'none'; frame-ancestors 'none'; sandbox allow-same-origin") ), "Cache-Control": "no-store", # A native navigation form under no-referrer submits Origin: null in # Chromium. Keep an origin on this token-free, same-origin entry page # so strict CSRF validation works; transcript pages disclose no referrer. "Referrer-Policy": "same-origin" if allow_unlock_form else "no-referrer", "X-Content-Type-Options": "nosniff", "X-Frame-Options": "DENY", "X-Robots-Tag": "noindex, nofollow, noarchive", } def render_public_chat_unlock_html(share_id: str, *, invalid_token: bool = False) -> bytes: """A generic token prompt; it never reads or discloses snapshot metadata.""" if not isinstance(share_id, str) or SHARE_ID_PATTERN.fullmatch(share_id) is None: raise PublicChatShareUnavailable() error = '' if invalid_token else "" return ( '' '' '' f'Open shared chat · AgentsDock' '
    ' 'AgentsDock
    ' 'Shared conversations
    ' '
    Shared conversation' 'View only

    Open your shared chat

    ' '

    Enter the access token provided by the person who shared this conversation.

    ' f'{error}
    ' '' '' '' '
    ' '

    This opens a saved, read-only conversation.

    ' '
    ' ).encode("utf-8") def render_public_chat_html(snapshot: dict[str, Any], *, page: int = 0, page_count: int = 1, message_count: int | None = None, navigation_base: str | None = None) -> bytes: """Render a static transcript with escaped, presentation-only Markdown.""" if not isinstance(snapshot, dict) or set(snapshot) != {"title", "created_at", "messages"}: raise PublicChatShareValidationError("Invalid public snapshot fields.") snapshot, _ = _snapshot(snapshot["messages"], snapshot["title"], snapshot["created_at"]) title = html.escape(snapshot["title"], quote=True) created_at = datetime.fromtimestamp(snapshot["created_at"], timezone.utc) created = created_at.strftime("%b %d, %Y · %H:%M UTC") if (type(page) is not int or type(page_count) is not int or not 0 <= page < page_count or message_count is not None and (type(message_count) is not int or message_count < len(snapshot["messages"]))): raise PublicChatShareValidationError("Invalid snapshot page.") if navigation_base is not None and re.fullmatch(r"/(?:shared-chat/share_[a-f0-9]{32}|share/[A-Za-z0-9_-]{43})", navigation_base) is None: raise PublicChatShareValidationError("Invalid snapshot navigation.") navigation = "" if navigation_base is not None and page_count > 1: older = f'' if page else "" newer = f'' if page + 1 < page_count else "" navigation = f'' count = len(snapshot["messages"]) if message_count is None else message_count count_label = "message" if count == 1 else "messages" parts = [ '', '', '', f"{title} · AgentsDock", '
    ', 'AgentsDock
    ', 'Shared conversations
    ', '
    Conversation', 'View only
    ', f'

    {title}

    A saved conversation, shared for reading.

    ', f'{navigation}
    ', ] for index, message in enumerate(snapshot["messages"]): role = message["role"] label = "User" if role == "user" else "Assistant" parts.append(f'
    ') parts.append(f'

    {label}

    ') if "timestamp" in message: timestamp = datetime.fromtimestamp(message["timestamp"], timezone.utc) stamp = timestamp.strftime("%b %d · %H:%M UTC") parts.append(f'') parts.append('
    ') content = (html.escape(message["text"], quote=True) if role == "user" else _render_message_markdown(message["text"])) parts.append(f'
    {content}') for video_index, video in enumerate(message.get("videos", [])): filename = html.escape(video["filename"], quote=True) if navigation_base is None: parts.append(f'

    Video: {filename}

    ') continue source = f"{navigation_base}/media/{page}/{index}/{video_index}" parts.append(f'
    ' f'
    {filename}
    ') parts.append('
    ') parts.append('
    ') parts.append(f'
    {navigation}
    ') rendered = "".join(parts).encode("utf-8") if len(rendered) > MAX_RENDER_BYTES: raise PublicChatShareValidationError("Rendered snapshot is too large.") return rendered