feat: /save exports the current session as json, md, or html on all platforms
Rework of salvaged PR #6372 (@ag9920) onto current main: - /save promoted from CLI-only JSON snapshot to a cross-platform session export: `/save [json|md|html] [filename] [redact]` on CLI and every gateway platform (sent as a document via adapter.send_document). - Rendering routes through the existing shared renderers (hermes_cli/session_export.py + session_export_html.py) instead of the PR's new hermes_state formatter — new helpers normalize_save_format / render_session_for_save / default_save_filename are shared by both surfaces. - `redact` arg runs the export through the force-mode secret redaction pass (session_export_md.redact_session_data) before writing. - Gateway handler awaits AsyncSessionDB correctly, sanitizes user-supplied filenames with basename, and lands in gateway/slash_commands.py (the handlers moved out of gateway/run.py since the PR was authored). - /export stays profile export (name collision resolved: session export lives on /save). - Slack 50-slash cap curation: /platform moves to the /hermes-only set to free a native slot for /save (parity test updated rationale comment). - Folds in PR #62268 (@briandevans): None title/model coalescing in the single-session HTML export. Closes #4249. Closes #51200.
This commit is contained in:
@@ -291,42 +291,6 @@ class SessionPortabilityMixin:
|
||||
base["messages"] = [msg for seg in segments for msg in (seg.get("messages") or [])]
|
||||
return base
|
||||
|
||||
@staticmethod
|
||||
def format_session_as_markdown(export_data: Dict[str, Any]) -> str:
|
||||
"""Format exported session data as a readable Markdown document."""
|
||||
lines = []
|
||||
session_id = export_data.get("id", "Unknown")
|
||||
lines.append(f"# Session Export: {session_id}\n")
|
||||
|
||||
title = export_data.get("title")
|
||||
if title:
|
||||
lines.append(f"**Title**: {title}")
|
||||
|
||||
created_at = export_data.get("created_at")
|
||||
if created_at:
|
||||
lines.append(f"**Started**: {created_at}")
|
||||
|
||||
if title or created_at:
|
||||
lines.append("\n---\n")
|
||||
|
||||
for msg in export_data.get("messages", []):
|
||||
role = msg.get("role", "unknown").capitalize()
|
||||
content = msg.get("content", "")
|
||||
|
||||
if role == "User":
|
||||
lines.append(f"## {role}\n\n{content}\n")
|
||||
elif role == "Assistant":
|
||||
reasoning = msg.get("reasoning", "")
|
||||
if reasoning:
|
||||
lines.append(f"## {role} (Reasoning)\n\n{reasoning}\n")
|
||||
lines.append(f"## {role}\n\n{content}\n")
|
||||
elif role == "Tool":
|
||||
lines.append(f"### Tool Result\n\n```\n{content}\n```\n")
|
||||
else:
|
||||
lines.append(f"## {role}\n\n{content}\n")
|
||||
|
||||
return "\n".join(lines)
|
||||
|
||||
def export_all(self, source: str = None) -> List[Dict[str, Any]]:
|
||||
"""
|
||||
Export all sessions (with messages) as a list of dicts.
|
||||
|
||||
Reference in New Issue
Block a user