use anyhow::Result;
use regex::Regex;
use serde_json::Value;
use std::sync::OnceLock;
use crate::codex::Manifest;
use crate::codex::export::include::ExportIncludeSet;
use crate::codex::export::read::LoadedArchive;
static SYSTEM_REMINDER_RE: OnceLock<Regex> = OnceLock::new();
fn system_reminder_re() -> &'static Regex {
SYSTEM_REMINDER_RE.get_or_init(|| {
Regex::new(r"(?s)<system-reminder>.*?</system-reminder>")
.expect("system-reminder regex must compile")
})
}
pub(crate) fn system_reminder_re_for_export() -> &'static Regex {
system_reminder_re()
}
pub fn render(
archive: &LoadedArchive,
manifest: &Manifest,
include: &ExportIncludeSet,
) -> Result<String> {
let mut out = String::new();
out.push_str(&format!("# Session {}\n\n", manifest.session_id));
out.push_str(&format!(
"- **Started:** {}\n",
manifest.session_start.to_rfc3339()
));
out.push_str(&format!(
"- **Ended:** {}\n",
manifest.session_end.to_rfc3339()
));
if let Some(p) = &manifest.project_path {
out.push_str(&format!("- **Project:** `{}`\n", p));
}
out.push_str(&format!(
"- **Messages:** {} (across {} agent(s))\n",
manifest.message_count, manifest.agent_count
));
out.push('\n');
if let Some(md) = &archive.conversation_md {
out.push_str("## Conversation\n\n");
if include.system_reminders || include.tools {
out.push_str(
"_Note: this archive was captured in clean mode. \
`tools` / `system-reminders` filters cannot reintroduce \
content that was stripped at archive time; rerun with a \
full-mode archive if you need them._\n\n",
);
}
out.push_str(md);
if !md.ends_with('\n') {
out.push('\n');
}
out.push('\n');
} else if let Some(jsonl) = &archive.session_jsonl {
out.push_str("## Conversation\n\n");
out.push_str(&render_jsonl_body(jsonl, include)?);
if include.subagents {
for (name, content) in &archive.agents {
let agent_md = render_jsonl_body(content, include)?;
if !agent_md.trim().is_empty() {
out.push_str("\n---\n\n");
out.push_str(&format!("## Agent: {}\n\n", name));
out.push_str(&agent_md);
}
}
}
} else {
out.push_str("## Conversation\n\n_(no transcript available)_\n\n");
}
if include.mcp && !archive.mcp_logs.is_empty() {
out.push_str("\n## MCP Logs\n\n");
for path in &archive.mcp_logs {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("(unnamed)");
out.push_str(&format!("### {}\n\n", name));
match std::fs::read_to_string(path) {
Ok(content) => {
out.push_str("```jsonl\n");
out.push_str(content.trim_end_matches('\n'));
out.push_str("\n```\n\n");
}
Err(e) => {
out.push_str(&format!("_(failed to read: {})_\n\n", e));
}
}
}
}
if include.tool_output && !archive.tool_outputs.is_empty() {
out.push_str("\n## Tool Output\n\n");
for path in &archive.tool_outputs {
let name = path
.file_name()
.and_then(|n| n.to_str())
.unwrap_or("(unnamed)");
out.push_str(&format!("### {}\n\n", name));
match std::fs::read_to_string(path) {
Ok(content) => {
out.push_str("```\n");
out.push_str(content.trim_end_matches('\n'));
out.push_str("\n```\n\n");
}
Err(e) => {
out.push_str(&format!("_(failed to read: {})_\n\n", e));
}
}
}
}
if include.history
&& let Some(history) = &archive.history_jsonl
&& !history.trim().is_empty()
{
out.push_str("\n## History Slice\n\n");
out.push_str("```jsonl\n");
out.push_str(history.trim_end_matches('\n'));
out.push_str("\n```\n");
}
Ok(out)
}
fn render_jsonl_body(content: &str, include: &ExportIncludeSet) -> Result<String> {
let mut out = String::new();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let msg: Value = match serde_json::from_str(trimmed) {
Ok(v) => v,
Err(_) => continue,
};
let msg_type = match msg["type"].as_str() {
Some(t) => t,
None => continue,
};
match msg_type {
"user" => {
let content_node = &msg["message"]["content"];
if let Some(text) = content_node.as_str() {
let processed = if include.system_reminders {
text.to_string()
} else {
system_reminder_re().replace_all(text, "").to_string()
};
let trimmed = processed.trim();
if !trimmed.is_empty() {
out.push_str(&format!("**User:** {}\n\n", trimmed));
}
}
}
"assistant" => {
if let Some(blocks) = msg["message"]["content"].as_array() {
let mut text_parts = Vec::new();
let mut tool_parts = Vec::new();
for block in blocks {
match block["type"].as_str() {
Some("text") => {
if let Some(text) = block["text"].as_str() {
let processed = if include.system_reminders {
text.to_string()
} else {
system_reminder_re().replace_all(text, "").to_string()
};
let trimmed = processed.trim();
if !trimmed.is_empty() {
text_parts.push(trimmed.to_string());
}
}
}
Some("tool_use") if include.tools => {
let name = block["name"].as_str().unwrap_or("(tool)");
let id = block["id"].as_str().unwrap_or("");
let input = serde_json::to_string_pretty(&block["input"])
.unwrap_or_else(|_| "{}".to_string());
tool_parts.push(format!(
"**Tool use:** `{}` (id: `{}`)\n\n```json\n{}\n```",
name, id, input
));
}
_ => {}
}
}
let prose = text_parts.join("\n\n");
if !prose.is_empty() {
out.push_str(&format!("**Assistant:** {}\n\n", prose));
}
for tool in tool_parts {
out.push_str(&tool);
out.push_str("\n\n");
}
}
}
_ => {}
}
}
Ok(out)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::codex::MANIFEST_WRITE_VERSION;
use chrono::Utc;
use std::path::PathBuf;
fn fixture_manifest() -> Manifest {
Manifest {
version: MANIFEST_WRITE_VERSION,
session_id: "test-session-id".to_string(),
archived_at: Utc::now(),
session_start: "2026-04-29T10:00:00Z".parse().unwrap(),
session_end: "2026-04-29T10:30:00Z".parse().unwrap(),
project_path: Some("/home/charlie/work/mx".to_string()),
message_count: 4,
agent_count: 1,
agents: vec![],
size_bytes: 0,
checksum: "sha256:zero".to_string(),
image_count: None,
images: None,
has_clean_transcript: None,
user_name: None,
assistant_name: None,
tool_output_count: None,
mcp_log_count: None,
history_lines: None,
source_breakdown: None,
}
}
fn fixture_jsonl() -> &'static str {
concat!(
r#"{"type":"user","message":{"content":"hello there"}}"#,
"\n",
r#"{"type":"assistant","message":{"content":[{"type":"text","text":"hi back"}]}}"#,
"\n",
r#"{"type":"user","message":{"content":"<system-reminder>secret</system-reminder>visible"}}"#,
"\n",
r#"{"type":"assistant","message":{"content":[{"type":"tool_use","id":"toolu_1","name":"Bash","input":{"command":"ls"}},{"type":"text","text":"running"}]}}"#,
"\n",
)
}
fn empty_archive_with_jsonl(jsonl: &str) -> LoadedArchive {
LoadedArchive {
archive_dir: PathBuf::from("/nonexistent"),
session_jsonl: Some(jsonl.to_string()),
conversation_md: None,
agents: vec![],
mcp_logs: vec![],
tool_outputs: vec![],
history_jsonl: None,
}
}
#[test]
fn render_default_clean_strips_system_reminders_and_tools() {
let m = fixture_manifest();
let arc = empty_archive_with_jsonl(fixture_jsonl());
let out = render(&arc, &m, &ExportIncludeSet::default_clean()).unwrap();
assert!(out.contains("**User:** hello there"));
assert!(out.contains("**Assistant:** hi back"));
assert!(out.contains("visible"));
assert!(!out.contains("secret"), "system reminder leaked");
assert!(!out.contains("Tool use"), "tool_use leaked");
}
#[test]
fn render_with_tools_shows_tool_use_block() {
let m = fixture_manifest();
let arc = empty_archive_with_jsonl(fixture_jsonl());
let mut inc = ExportIncludeSet::default_clean();
inc.tools = true;
let out = render(&arc, &m, &inc).unwrap();
assert!(out.contains("Tool use"));
assert!(out.contains("Bash"));
assert!(out.contains("toolu_1"));
}
#[test]
fn render_with_system_reminders_keeps_them() {
let m = fixture_manifest();
let arc = empty_archive_with_jsonl(fixture_jsonl());
let mut inc = ExportIncludeSet::default_clean();
inc.system_reminders = true;
let out = render(&arc, &m, &inc).unwrap();
assert!(out.contains("secret"));
}
#[test]
fn render_xfail_strips_legitimately_quoted_system_reminder() {
let jsonl = concat!(
r#"{"type":"user","message":{"content":"For context, the platform sometimes injects <system-reminder>do X</system-reminder> blocks — please ignore them."}}"#,
"\n",
r#"{"type":"assistant","message":{"content":[{"type":"text","text":"understood"}]}}"#,
"\n",
);
let m = fixture_manifest();
let arc = empty_archive_with_jsonl(jsonl);
let inc = ExportIncludeSet::default_clean(); let out = render(&arc, &m, &inc).unwrap();
assert!(
!out.contains("do X"),
"v1 textual heuristic strips the quoted reminder; if this assertion \
starts failing, the limitation has been lifted — update the doc \
comment on system_reminder_re()"
);
assert!(out.contains("the platform sometimes injects"));
assert!(out.contains("please ignore them"));
}
#[test]
fn render_inlines_subagents_when_enabled() {
let m = fixture_manifest();
let agent_jsonl = concat!(
r#"{"type":"user","message":{"content":"agent prompt"}}"#,
"\n",
r#"{"type":"assistant","message":{"content":[{"type":"text","text":"agent answer"}]}}"#,
"\n",
);
let mut arc = empty_archive_with_jsonl(fixture_jsonl());
arc.agents = vec![("agent-aaaa.jsonl".to_string(), agent_jsonl.to_string())];
let inc = ExportIncludeSet::default_clean();
let out = render(&arc, &m, &inc).unwrap();
assert!(out.contains("## Agent: agent-aaaa.jsonl"));
assert!(out.contains("agent answer"));
}
#[test]
fn render_skips_subagents_when_disabled() {
let m = fixture_manifest();
let agent_jsonl =
r#"{"type":"assistant","message":{"content":[{"type":"text","text":"agent answer"}]}}"#;
let mut arc = empty_archive_with_jsonl(fixture_jsonl());
arc.agents = vec![("agent-aaaa.jsonl".to_string(), agent_jsonl.to_string())];
let inc = ExportIncludeSet::none();
let out = render(&arc, &m, &inc).unwrap();
assert!(!out.contains("## Agent"));
assert!(!out.contains("agent answer"));
}
#[test]
fn render_appends_history_section_when_enabled() {
let m = fixture_manifest();
let mut arc = empty_archive_with_jsonl(fixture_jsonl());
arc.history_jsonl = Some("{\"timestamp\": \"2026-04-29T10:15:00Z\"}\n".to_string());
let mut inc = ExportIncludeSet::default_clean();
inc.history = true;
let out = render(&arc, &m, &inc).unwrap();
assert!(out.contains("## History Slice"));
}
#[test]
fn render_uses_conversation_md_when_present() {
let m = fixture_manifest();
let arc = LoadedArchive {
archive_dir: PathBuf::from("/nonexistent"),
session_jsonl: None,
conversation_md: Some("**Charlie:** prebaked\n".to_string()),
agents: vec![],
mcp_logs: vec![],
tool_outputs: vec![],
history_jsonl: None,
};
let out = render(&arc, &m, &ExportIncludeSet::default_clean()).unwrap();
assert!(out.contains("prebaked"));
}
}