use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::io::{BufRead, BufReader, Read, Seek, SeekFrom};
use std::path::{Path, PathBuf};
use rusqlite::Connection;
use serde_json::Value;
use crate::{
ChatMessage, Fidelity, FunctionCall, InterchangeError as Error, Result, Role, ToolCall,
};
mod claude_code;
pub(crate) use claude_code::ClaudeAppendState;
#[doc(hidden)]
pub use claude_code::ClaudeReadIndex;
mod codex;
mod detect;
mod gemini;
mod goose;
mod grok;
mod helpers;
mod hermes;
mod native;
mod openclaw;
mod opencode;
mod pi;
pub(crate) use claude_code::*;
use codex::*;
pub use detect::*;
use gemini::*;
use grok::*;
pub use helpers::*;
pub use hermes::*;
use native::*;
pub use openclaw::*;
pub use opencode::*;
pub use pi::*;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SessionSource {
ClaudeCode,
Codex,
OpenCode,
Pi,
Grok,
Gemini,
Goose,
OpenClaw,
Hermes,
Native,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SessionFormat {
ClaudeCode,
Codex,
OpenCode,
Pi,
Grok,
Gemini,
Goose,
}
impl SessionFormat {
pub fn source(self) -> SessionSource {
match self {
SessionFormat::ClaudeCode => SessionSource::ClaudeCode,
SessionFormat::Codex => SessionSource::Codex,
SessionFormat::OpenCode => SessionSource::OpenCode,
SessionFormat::Pi => SessionSource::Pi,
SessionFormat::Grok => SessionSource::Grok,
SessionFormat::Gemini => SessionSource::Gemini,
SessionFormat::Goose => SessionSource::Goose,
}
}
}
pub use crate::ontology::surface::{
CrossSurface, Recurrence, SurfaceKey, Trigger, WorkspaceKind, WorkspaceRef,
};
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct OrchestrationNouns {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub trigger: Option<Trigger>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub surface: Option<SurfaceKey>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub profile: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub recurrence: Option<Recurrence>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub cross_surface: Option<CrossSurface>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub workspace: Option<WorkspaceRef>,
}
impl OrchestrationNouns {
pub fn from_meta(meta: &SessionMeta) -> Self {
Self {
trigger: Some(meta.trigger_or_default()),
surface: meta.surface.clone(),
profile: meta.profile.clone(),
recurrence: meta.recurrence.clone(),
cross_surface: meta.cross_surface.clone(),
workspace: Some(meta.workspace_ref()),
}
}
}
#[derive(Debug, Clone)]
#[non_exhaustive]
pub struct SessionMeta {
pub source: SessionSource,
pub session_id: Option<String>,
pub model: Option<String>,
pub cwd: Option<PathBuf>,
pub system_prompt: Option<String>,
pub codex_headers: Vec<Value>,
pub codex_provenance: Vec<Value>,
pub native_residue: Vec<Value>,
pub native_residue_source: Option<String>,
pub opencode_headers: Vec<Value>,
pub goose_header: Option<Value>,
pub agent_id: Option<String>,
pub parent_tool_use_id: Option<String>,
pub lineage: std::collections::BTreeMap<String, String>,
pub trigger: Option<Trigger>,
pub surface: Option<SurfaceKey>,
pub profile: Option<String>,
pub recurrence: Option<Recurrence>,
pub cross_surface: Option<CrossSurface>,
}
impl SessionMeta {
pub(crate) fn new(source: SessionSource) -> Self {
SessionMeta {
source,
session_id: None,
model: None,
cwd: None,
system_prompt: None,
codex_headers: Vec::new(),
codex_provenance: Vec::new(),
native_residue: Vec::new(),
native_residue_source: None,
opencode_headers: Vec::new(),
goose_header: None,
agent_id: None,
parent_tool_use_id: None,
lineage: std::collections::BTreeMap::new(),
trigger: None,
surface: None,
profile: None,
recurrence: None,
cross_surface: None,
}
}
pub fn trigger_or_default(&self) -> Trigger {
if let Some(t) = self.trigger {
return t;
}
let delegate = self
.lineage
.get("hermes_lineage_kind")
.map(|k| k == "delegate")
.unwrap_or(false);
if self.agent_id.is_some() || self.parent_tool_use_id.is_some() || delegate {
Trigger::Parent
} else {
Trigger::Human
}
}
pub fn workspace(&self) -> (WorkspaceKind, Option<String>) {
if let Some(cwd) = &self.cwd {
return (
WorkspaceKind::Repo,
Some(cwd.to_string_lossy().into_owned()),
);
}
if let Some(surface) = self.surface.as_ref().filter(|s| s.is_channel()) {
let label = match (&surface.platform, &surface.chat_id) {
(Some(p), Some(c)) => format!("{p}:{c}"),
(Some(p), None) => p.clone(),
_ => String::new(),
};
return (WorkspaceKind::Channel, Some(label));
}
(WorkspaceKind::None, None)
}
pub fn workspace_ref(&self) -> WorkspaceRef {
let (kind, value) = self.workspace();
WorkspaceRef { kind, value }
}
}
#[derive(Debug, Clone)]
pub struct Session {
pub meta: SessionMeta,
pub messages: Vec<ChatMessage>,
pub subagents: Vec<Session>,
pub raw: Vec<String>,
pub raw_trailing_newline: bool,
pub imported_message_count: Option<usize>,
pub raw_is_verbatim: bool,
pub parse_error_lines: usize,
pub load_residue: Vec<String>,
}
impl Session {
pub fn load_fidelity(&self) -> Fidelity {
let own = if !self.load_residue.is_empty() {
Fidelity::Semantic
} else if self.raw_is_verbatim {
Fidelity::ByteLossless
} else {
Fidelity::ValueLossless
};
if own != Fidelity::Semantic
&& self
.subagents
.iter()
.any(|subagent| subagent.load_fidelity() == Fidelity::Semantic)
{
return Fidelity::Semantic;
}
own
}
pub fn from_native_messages(messages: Vec<ChatMessage>) -> Session {
Session {
meta: SessionMeta::new(SessionSource::Native),
messages,
subagents: Vec::new(),
raw: Vec::new(),
raw_trailing_newline: true,
imported_message_count: None,
raw_is_verbatim: false,
parse_error_lines: 0,
load_residue: Vec::new(),
}
}
pub fn load(path: impl AsRef<Path>) -> Result<Session> {
Self::load_with_fidelity(path, Fidelity::ByteLossless)
}
pub fn load_with_fidelity(path: impl AsRef<Path>, fidelity: Fidelity) -> Result<Session> {
Self::load_with_fidelity_and_subagents(path, fidelity, true)
}
#[doc(hidden)]
pub fn load_parent_with_fidelity(
path: impl AsRef<Path>,
fidelity: Fidelity,
) -> Result<Session> {
Self::load_with_fidelity_and_subagents(path, fidelity, false)
}
#[doc(hidden)]
pub fn load_display_view(
path: impl AsRef<Path>,
fidelity: Fidelity,
message_limit: usize,
) -> Result<Session> {
let path = path.as_ref();
if path.is_dir() || looks_like_sqlite(path) {
let mut session = Self::load_parent_with_fidelity(path, fidelity)?;
truncate_session_messages(&mut session, message_limit);
return Ok(session);
}
let mut read_limit = message_limit.max(1);
let mut previous_window_len = 0usize;
let (mut session, omitted_prefix) = loop {
let (source, text, omitted_prefix) = read_display_jsonl(path, read_limit)?;
let mut candidate = match source {
Some(SessionSource::Codex) => Self::from_codex_display_str(&text, message_limit)?,
Some(SessionSource::Gemini) => {
let mut session = Self::from_gemini_str(&text)?;
session.raw_is_verbatim = false;
session.load_residue.push(
"display history is a bounded native-record projection, not a complete Gemini artifact"
.to_string(),
);
session
}
Some(SessionSource::Pi) => Self::from_pi_str(&text)?,
Some(SessionSource::Grok) => {
let mut session = Self::from_grok_str(&text)?;
session.capture_grok_path_metadata(path);
session
}
Some(SessionSource::OpenCode) => Self::from_opencode_str(&text)?,
_ => Self::from_claude_code_str_with_fidelity(&text, fidelity)?,
};
let observed_messages = candidate
.imported_message_count
.unwrap_or(candidate.messages.len())
.max(candidate.messages.len());
let human_turns = candidate
.messages
.iter()
.filter(|message| message.role == Role::User)
.count();
let window_len = text.len();
let sufficient =
!omitted_prefix || (observed_messages > message_limit.max(1) && human_turns >= 2);
let byte_window_exhausted = window_len <= previous_window_len && read_limit >= 4096;
if sufficient || byte_window_exhausted {
if omitted_prefix {
candidate.imported_message_count =
Some(observed_messages.max(message_limit.max(1).saturating_add(1)));
}
break (candidate, omitted_prefix);
}
previous_window_len = window_len;
read_limit = read_limit.saturating_mul(2);
};
if omitted_prefix {
session.load_residue.push(
"older native records remain outside this bounded display window".to_string(),
);
}
truncate_session_messages(&mut session, message_limit);
Ok(session)
}
fn load_with_fidelity_and_subagents(
path: impl AsRef<Path>,
fidelity: Fidelity,
include_subagents: bool,
) -> Result<Session> {
let path = path.as_ref();
if path.is_dir() {
return match detect_opencode_storage_surface(path) {
Some((OpenCodeStorageSurface::Sqlite, db_path)) => {
Self::from_opencode_sqlite(&db_path, None)
}
Some((
OpenCodeStorageSurface::JsonTreeA | OpenCodeStorageSurface::JsonTreeB,
_,
)) => Err(crate::Error::Other(format!(
"{} is an OpenCode data root using a legacy JSON storage tree, which \
supercode does not load directly — point `inspect`/`convert`/`resume` \
at the store's `opencode*.db` SQLite file if this install has one, or \
use `audit --format opencode {}` instead",
path.display(),
path.display()
))),
None => Err(crate::Error::Other(format!(
"{} is a directory, but no session file or OpenCode store was found in it \
(expected an `opencode*.db` SQLite file, or an OpenCode legacy JSON storage \
tree)",
path.display()
))),
};
}
if looks_like_sqlite(path) {
if let Ok(conn) = Connection::open_with_flags(
path,
rusqlite::OpenFlags::SQLITE_OPEN_READ_ONLY
| rusqlite::OpenFlags::SQLITE_OPEN_NO_MUTEX,
) {
if hermes_sqlite_fingerprint(&conn) {
drop(conn);
return Self::from_hermes_sqlite(path, None);
}
}
return Self::from_opencode_sqlite(path, None);
}
let text = read_utf8_or_diagnose(path)?;
match detect_source(&text) {
Some(SessionSource::Codex) => Self::from_codex_str(&text),
Some(SessionSource::Pi) => Self::from_pi_str(&text),
Some(SessionSource::OpenClaw) => {
let mut session = Self::from_openclaw_str(&text)?;
if session.meta.profile.is_none() {
session.meta.profile = openclaw_agent_id_from_path(path);
}
Ok(session)
}
Some(SessionSource::Grok) => {
let mut session = Self::from_grok_str(&text)?;
session.capture_grok_path_metadata(path);
Ok(session)
}
Some(SessionSource::Gemini) => Self::from_gemini_str(&text),
Some(SessionSource::Goose) => Self::from_goose_str(&text),
Some(SessionSource::OpenCode) => Self::from_opencode_str(&text),
_ => {
let mut session = Self::from_claude_code_str_with_fidelity(&text, fidelity)?;
if include_subagents {
session.attach_claude_subagents(path, &text, fidelity)?;
}
Ok(session)
}
}
}
pub fn reconstruct_tree(sessions: Vec<Session>) -> Vec<Session> {
use std::collections::HashMap;
let mut idx: HashMap<String, usize> = HashMap::new();
for (i, s) in sessions.iter().enumerate() {
if let Some(id) = &s.meta.session_id {
idx.insert(id.clone(), i);
}
}
let parent_of: Vec<Option<usize>> = sessions
.iter()
.map(|s| {
s.meta
.lineage
.get("parent_thread_id")
.and_then(|p| idx.get(p).copied())
})
.collect();
let mut slots: Vec<Option<Session>> = sessions.into_iter().map(Some).collect();
let mut order: Vec<usize> = (0..slots.len()).collect();
order.sort_by_key(|&i| std::cmp::Reverse(depth_of(i, &parent_of)));
for i in order {
if let Some(p) = parent_of[i] {
if p != i {
if let Some(child) = slots[i].take() {
if let Some(parent) = slots[p].as_mut() {
parent.subagents.push(child);
} else {
slots[i] = Some(child); }
}
}
}
}
slots.into_iter().flatten().collect()
}
pub fn load_str(jsonl: &str, format: SessionFormat) -> Result<Session> {
match format {
SessionFormat::ClaudeCode => Self::from_claude_code_str(jsonl),
SessionFormat::Codex => Self::from_codex_str(jsonl),
SessionFormat::Pi => Self::from_pi_str(jsonl),
SessionFormat::OpenCode => Self::from_opencode_str(jsonl),
SessionFormat::Grok => Self::from_grok_str(jsonl),
SessionFormat::Gemini => Self::from_gemini_str(jsonl),
SessionFormat::Goose => Self::from_goose_str(jsonl),
}
}
pub fn to_jsonl(&self, format: SessionFormat) -> Result<String> {
match format {
SessionFormat::ClaudeCode => Ok(self.to_claude_code_jsonl()),
SessionFormat::Codex => Ok(self.to_codex_jsonl()),
SessionFormat::Pi => Ok(self.to_pi_jsonl()),
SessionFormat::OpenCode => self.to_opencode_jsonl(),
SessionFormat::Grok => Ok(self.to_grok_jsonl()),
SessionFormat::Gemini => Ok(self.to_gemini_jsonl()),
SessionFormat::Goose => Ok(self.to_goose_json()),
}
}
pub fn to_jsonl_spliced(
&self,
format: SessionFormat,
session_id: Option<&str>,
) -> Result<String> {
if self.parse_error_lines > 0
|| self
.subagents
.iter()
.any(|subagent| subagent.parse_error_lines > 0)
{
return Err(Error::InvalidSession(
"refusing spliced export because the loaded session contains parse loss"
.to_string(),
));
}
if self.raw.is_empty() || format.source() != self.meta.source {
if let Some(session_id) = session_id {
let mut rewritten = self.clone();
rewritten.meta.session_id = Some(session_id.to_string());
return rewritten.to_jsonl(format);
}
return self.to_jsonl(format);
}
match format {
SessionFormat::ClaudeCode => Ok(self.to_claude_code_jsonl_spliced(session_id)),
SessionFormat::Codex => Ok(self.to_codex_jsonl_spliced(session_id)),
SessionFormat::Pi => self.to_pi_jsonl_spliced(session_id),
SessionFormat::OpenCode => self.to_opencode_jsonl_spliced(session_id),
SessionFormat::Grok => Ok(self.to_grok_jsonl_spliced()),
SessionFormat::Gemini => Ok(self.to_gemini_jsonl_spliced(session_id)),
SessionFormat::Goose => Ok(self.to_goose_json_spliced(session_id)),
}
}
pub fn save(&self, path: impl AsRef<Path>, format: SessionFormat) -> Result<()> {
std::fs::write(path.as_ref(), self.to_jsonl(format)?)?;
Ok(())
}
pub fn raw_verbatim(&self) -> String {
join_lines_verbatim(&self.raw, self.raw_trailing_newline)
}
pub fn to_session_tree(&self, created_at_ms: i64) -> crate::session_tree::SessionTree {
crate::session_tree::SessionTree::from_linear(&self.messages, created_at_ms)
}
pub fn apply_session_tree(&mut self, tree: &crate::session_tree::SessionTree) -> Result<()> {
self.messages = tree.linear_projection()?;
Ok(())
}
}
pub(super) fn read_utf8_or_diagnose(path: &Path) -> Result<String> {
let bytes = std::fs::read(path)?;
String::from_utf8(bytes).map_err(|_| {
crate::Error::Other(format!(
"{} is not valid UTF-8 text, and is not a recognized OpenCode SQLite store \
(no `SQLite format 3` header) — supercode reads Claude Code / Codex / Pi / \
OpenCode session logs as UTF-8 JSONL, or an OpenCode `opencode*.db` SQLite file",
path.display()
))
})
}
pub(super) fn read_display_jsonl(
path: &Path,
message_limit: usize,
) -> Result<(Option<SessionSource>, String, bool)> {
const MIN_TAIL_BYTES: u64 = 4 * 1024 * 1024;
const MAX_TAIL_BYTES: u64 = 64 * 1024 * 1024;
const BYTES_PER_MESSAGE: u64 = 16 * 1024;
let mut first = String::new();
BufReader::new(std::fs::File::open(path)?).read_line(&mut first)?;
let source = detect_source(&first);
if !matches!(
source,
Some(SessionSource::ClaudeCode | SessionSource::Codex | SessionSource::Gemini)
) {
let text = read_utf8_or_diagnose(path)?;
return Ok((detect_source(&text), text, false));
}
let mut file = std::fs::File::open(path)?;
let file_len = file.metadata()?.len();
let requested = (message_limit.max(1) as u64)
.saturating_mul(BYTES_PER_MESSAGE)
.clamp(MIN_TAIL_BYTES, MAX_TAIL_BYTES);
if file_len <= requested {
let text = read_utf8_or_diagnose(path)?;
return Ok((source, text, false));
}
let start = file_len - requested;
file.seek(SeekFrom::Start(start))?;
let mut bytes = Vec::with_capacity(requested as usize);
file.read_to_end(&mut bytes)?;
if let Some(newline) = bytes.iter().position(|byte| *byte == b'\n') {
bytes.drain(..=newline);
}
let mut tail = String::from_utf8(bytes).map_err(|_| {
crate::Error::Other(format!(
"{} contains non-UTF-8 data in its display window",
path.display()
))
})?;
if start > 0 {
let max_search_bytes = start.min(MAX_TAIL_BYTES);
let mut search_bytes = requested.min(max_search_bytes);
let anchors = loop {
let search_start = start - search_bytes;
file.seek(SeekFrom::Start(search_start))?;
let mut search = Vec::with_capacity(search_bytes as usize);
(&mut file).take(search_bytes).read_to_end(&mut search)?;
if search_start > 0 {
if let Some(newline) = search.iter().position(|byte| *byte == b'\n') {
search.drain(..=newline);
} else {
search.clear();
}
}
if let Some(newline) = search.iter().rposition(|byte| *byte == b'\n') {
search.truncate(newline + 1);
} else {
search.clear();
}
let anchors = std::str::from_utf8(&search)
.ok()
.map(|search| {
let mut found = search
.lines()
.rev()
.filter(|line| native_display_human_line(line, source))
.take(2)
.map(str::to_string)
.collect::<Vec<_>>();
found.reverse();
found
})
.unwrap_or_default();
if anchors.len() >= 2 || search_start == 0 || search_bytes == max_search_bytes {
break anchors;
}
search_bytes = search_bytes.saturating_mul(2).min(max_search_bytes);
};
if !anchors.is_empty() {
tail = format!("{}\n{tail}", anchors.join("\n"));
}
}
let text = if matches!(source, Some(SessionSource::Codex | SessionSource::Gemini)) {
format!("{first}{tail}")
} else {
tail
};
Ok((source, text, true))
}
#[cfg(test)]
mod orchestration_noun_tests {
use super::*;
#[test]
fn hermes_key_parses_profile_and_surface() {
let (s, p) = parse_hermes_session_key("agent:coder:telegram:group:-100777:55:u9").unwrap();
assert_eq!(p.as_deref(), Some("coder"));
assert_eq!(s.platform.as_deref(), Some("telegram"));
assert_eq!(s.kind.as_deref(), Some("group"));
assert_eq!(s.chat_id.as_deref(), Some("-100777"));
assert_eq!(s.thread_id.as_deref(), Some("55"));
assert_eq!(s.participant_id.as_deref(), Some("u9"));
let (_, p) = parse_hermes_session_key("agent:main:telegram:dm:1").unwrap();
assert!(p.is_none());
assert!(parse_hermes_session_key("cron:abc").is_none());
}
#[test]
fn openclaw_keys_parse_every_documented_shape() {
let (a, s, t, r) =
parse_openclaw_session_key("agent:design:slack:channel:C1:thread:T2").unwrap();
assert_eq!(a.as_deref(), Some("design"));
assert_eq!(s.platform.as_deref(), Some("slack"));
assert_eq!(s.chat_id.as_deref(), Some("C1"));
assert_eq!(s.thread_id.as_deref(), Some("T2"));
assert_eq!(t, Trigger::Channel);
assert!(r.is_none());
let (a, s, t, _) = parse_openclaw_session_key("agent:main:main").unwrap();
assert_eq!(a.as_deref(), Some("main"));
assert_eq!(s.kind.as_deref(), Some("main"));
assert_eq!(t, Trigger::Unknown);
let (_, _, t, r) = parse_openclaw_session_key("cron:job-7").unwrap();
assert_eq!(t, Trigger::Cron);
assert_eq!(r.unwrap().job_id, "job-7");
assert_eq!(
parse_openclaw_session_key("hook:gmail:m1").unwrap().2,
Trigger::Webhook
);
assert_eq!(
parse_openclaw_session_key("acp-bridge:u").unwrap().2,
Trigger::Api
);
assert!(parse_openclaw_session_key("garbage").is_none());
}
#[test]
fn hermes_source_and_cron_ids_classify() {
assert_eq!(hermes_trigger_for_source("telegram"), Trigger::Channel);
assert_eq!(hermes_trigger_for_source("cli"), Trigger::Human);
assert_eq!(hermes_trigger_for_source("acp"), Trigger::Human);
assert_eq!(hermes_trigger_for_source("api_server"), Trigger::Api);
assert_eq!(hermes_trigger_for_source("cron"), Trigger::Cron);
assert_eq!(hermes_trigger_for_source(""), Trigger::Unknown);
assert_eq!(
hermes_cron_job_id("cron_job42_20260902_120000").as_deref(),
Some("job42")
);
assert_eq!(
hermes_cron_job_id("cron_a_b_20260902_120000").as_deref(),
Some("a_b")
);
assert!(hermes_cron_job_id("cron_job42_2026_1200").is_none());
assert!(hermes_cron_job_id("adf8a015").is_none());
}
#[test]
fn workspace_precedence_repo_over_channel_over_none() {
let mut meta = SessionMeta::new(SessionSource::Hermes);
assert_eq!(meta.workspace().0, WorkspaceKind::None);
meta.surface = Some(SurfaceKey {
platform: Some("telegram".into()),
chat_id: Some("1".into()),
..Default::default()
});
assert_eq!(
meta.workspace(),
(WorkspaceKind::Channel, Some("telegram:1".into()))
);
meta.cwd = Some(PathBuf::from("/w"));
assert_eq!(meta.workspace().0, WorkspaceKind::Repo);
assert_eq!(meta.trigger_or_default(), Trigger::Human);
meta.agent_id = Some("a".into());
assert_eq!(meta.trigger_or_default(), Trigger::Parent);
}
#[test]
fn openclaw_agent_id_comes_from_the_agents_directory() {
let p = std::path::Path::new("/home/u/.openclaw/agents/design/sessions/x.jsonl");
assert_eq!(openclaw_agent_id_from_path(p).as_deref(), Some("design"));
assert!(openclaw_agent_id_from_path(std::path::Path::new("/tmp/x.jsonl")).is_none());
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_new_user_message_slides_the_display_tail_without_becoming_its_only_anchor() {
let mut messages = vec![
ChatMessage::user("original prompt"),
ChatMessage::assistant("one"),
ChatMessage::assistant("two"),
ChatMessage::assistant("three"),
ChatMessage::assistant("four"),
ChatMessage::assistant("five"),
ChatMessage::user("new prompt"),
];
truncate_messages_with_anchor(&mut messages, 4, Vec::new());
assert_eq!(messages.len(), 4);
assert_eq!(messages[0].content.as_deref(), Some("original prompt"));
assert_eq!(messages[3].content.as_deref(), Some("new prompt"));
}
#[test]
fn a_tool_heavy_current_turn_keeps_the_previous_and_current_user_anchors() {
let mut messages = vec![
ChatMessage::user("previous prompt"),
ChatMessage::assistant("previous answer"),
ChatMessage::user("current prompt"),
ChatMessage::assistant("tool one"),
ChatMessage::assistant("tool two"),
ChatMessage::assistant("tool three"),
ChatMessage::assistant("tool four"),
ChatMessage::assistant("tool five"),
];
truncate_messages_with_anchor(&mut messages, 4, Vec::new());
assert_eq!(messages.len(), 4);
assert_eq!(messages[0].content.as_deref(), Some("previous prompt"));
assert_eq!(messages[1].content.as_deref(), Some("current prompt"));
assert_eq!(messages[3].content.as_deref(), Some("tool five"));
}
#[test]
fn a_preceding_user_anchor_is_restored_when_dedup_shrinks_below_the_limit() {
let mut messages = vec![
ChatMessage::user("current prompt"),
ChatMessage::assistant("tool one"),
ChatMessage::assistant("tool two"),
];
truncate_messages_with_anchor(&mut messages, 4, vec![ChatMessage::user("previous prompt")]);
assert_eq!(messages.len(), 4);
assert_eq!(messages[0].content.as_deref(), Some("previous prompt"));
assert_eq!(messages[1].content.as_deref(), Some("current prompt"));
}
#[test]
fn a_tool_heavy_turn_restores_two_users_that_both_left_the_retention_buffer() {
let mut messages = vec![
ChatMessage::assistant("tool one"),
ChatMessage::assistant("tool two"),
ChatMessage::assistant("tool three"),
ChatMessage::assistant("tool four"),
];
truncate_messages_with_anchor(
&mut messages,
4,
vec![
ChatMessage::user("previous prompt"),
ChatMessage::user("current prompt"),
],
);
assert_eq!(messages.len(), 4);
assert_eq!(messages[0].content.as_deref(), Some("previous prompt"));
assert_eq!(messages[1].content.as_deref(), Some("current prompt"));
assert_eq!(messages[3].content.as_deref(), Some("tool four"));
}
#[test]
fn a_loaded_boundary_anchor_survives_several_newer_user_turns() {
let mut messages = vec![
ChatMessage::assistant("older tool one"),
ChatMessage::assistant("older tool two"),
ChatMessage::user("recent prompt one"),
ChatMessage::assistant("recent answer one"),
ChatMessage::user("recent prompt two"),
ChatMessage::assistant("recent answer two"),
ChatMessage::user("current prompt"),
ChatMessage::assistant("current tool"),
];
truncate_messages_with_anchor(
&mut messages,
6,
vec![ChatMessage::user("loaded earlier boundary")],
);
assert_eq!(messages.len(), 6);
assert_eq!(
messages[0].content.as_deref(),
Some("loaded earlier boundary"),
"newer user prompts must not replace the prompt that owns the retained activity",
);
assert_eq!(messages[4].content.as_deref(), Some("current prompt"));
assert_eq!(messages[5].content.as_deref(), Some("current tool"));
}
#[test]
fn a_bounded_byte_window_recovers_preceding_users_even_when_its_tail_has_users() {
let nonce = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap()
.as_nanos();
let path = std::env::temp_dir().join(format!(
"supercode-display-boundary-{}-{nonce}.jsonl",
std::process::id()
));
let user = |text: &str| {
format!(
r#"{{"type":"response_item","payload":{{"type":"message","role":"user","content":[{{"type":"input_text","text":"{text}"}}]}}}}"#,
)
};
let lines = [
r#"{"type":"session_meta","payload":{"id":"session-1"}}"#.to_string(),
user("preceding boundary"),
format!(
r#"{{"type":"event_msg","payload":{{"type":"token_count","noise":"{}"}}}}"#,
"x".repeat(5 * 1024 * 1024)
),
user("newer prompt one"),
user("newer prompt two"),
];
std::fs::write(&path, format!("{}\n", lines.join("\n"))).unwrap();
let (_, text, omitted_prefix) = read_display_jsonl(&path, 120).unwrap();
std::fs::remove_file(&path).unwrap();
assert!(omitted_prefix);
assert!(text.contains("preceding boundary"));
assert!(text.contains("newer prompt one"));
assert!(text.contains("newer prompt two"));
}
}