@@ -25,6 +25,54 @@ def session_dir() -> Path:
2525 return config .SESSION_DIR / config .SESSION_SUBDIR
2626
2727
28+ # The roles the save format delimits blocks with (`**<role>**: `).
29+ SAVED_ROLES = ("user" , "assistant" , "system" , "tool" )
30+
31+
32+ def split_role_header (line : str ) -> tuple [str , str ] | None :
33+ """``(role, rest)`` when LINE is a ``**role**: `` block header, else None.
34+
35+ The single source of truth for the save format's block delimiter:
36+ the renderer escapes what this would match and the parser splits on
37+ exactly what this accepts, so the two can never drift apart.
38+ """
39+ if not line .startswith ("**" ) or "**: " not in line :
40+ return None
41+ prefix , _ , rest = line .partition ("**: " )
42+ role = prefix .strip ("*" ).strip ()
43+ return (role , rest ) if role in SAVED_ROLES else None
44+
45+
46+ def _is_escapable (line : str ) -> bool :
47+ """Whether LINE is a block header, or an already-escaped one."""
48+ return split_role_header (line .lstrip ("\\ " )) is not None
49+
50+
51+ def escape_role_headers (body : str ) -> str :
52+ r"""Backslash-escape message-body lines that look like block headers.
53+
54+ Blocks are delimited by ``**<role>**: `` at the start of a line, so a
55+ message whose own text contains such a line — the agent explaining
56+ this very format, or a pasted transcript — would otherwise be split
57+ into extra (and misattributed) messages on restore. ``\**user**: ``
58+ still reads as the literal text in markdown and is reversed by
59+ `unescape_role_header`; already-escaped lines gain another backslash
60+ so the round trip is exact at any nesting depth.
61+ """
62+ if "**" not in body :
63+ return body
64+ return "\n " .join ("\\ " + ln if _is_escapable (ln ) else ln for ln in body .split ("\n " ))
65+
66+
67+ def unescape_role_header (line : str ) -> str :
68+ r"""Reverse one level of `escape_role_headers` for a single line.
69+
70+ Only lines that would otherwise be read as block headers are
71+ touched, so a literal ``\**note**: `` in a message survives intact.
72+ """
73+ return line [1 :] if line .startswith ("\\ " ) and _is_escapable (line ) else line
74+
75+
2876def sanitize_title (title : str ) -> str :
2977 """Sanitize a generated title (mirrors the elisp semantics)."""
3078 t = title .strip ()
0 commit comments