mirror of
https://github.com/openai/codex.git
synced 2026-05-24 13:04:29 +00:00
Keep extracting memories out of core and moving the write trigger in the app-server This is temporary and it should move at the client level as a follow-up This makes core fully independant from `codex-memories-write` --------- Co-authored-by: Codex <noreply@openai.com>
150 lines
5.2 KiB
Rust
150 lines
5.2 KiB
Rust
use super::rollout_summary_file_stem;
|
|
use crate::ensure_layout;
|
|
use crate::raw_memories_file;
|
|
use crate::rebuild_raw_memories_file_from_memories;
|
|
use crate::rollout_summaries_dir;
|
|
use crate::sync_rollout_summaries_from_memories;
|
|
use chrono::TimeZone;
|
|
use chrono::Utc;
|
|
use codex_config::types::DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION;
|
|
use codex_protocol::ThreadId;
|
|
use codex_state::Stage1Output;
|
|
use pretty_assertions::assert_eq;
|
|
use std::path::PathBuf;
|
|
use tempfile::tempdir;
|
|
|
|
const FIXED_PREFIX: &str = "2025-02-11T15-35-19-jqmb";
|
|
|
|
fn stage1_output_with_slug(thread_id: ThreadId, rollout_slug: Option<&str>) -> Stage1Output {
|
|
Stage1Output {
|
|
thread_id,
|
|
source_updated_at: Utc.timestamp_opt(123, 0).single().expect("timestamp"),
|
|
raw_memory: "raw memory".to_string(),
|
|
rollout_summary: "summary".to_string(),
|
|
rollout_slug: rollout_slug.map(ToString::to_string),
|
|
rollout_path: PathBuf::from("/tmp/rollout.jsonl"),
|
|
cwd: PathBuf::from("/tmp/workspace"),
|
|
git_branch: None,
|
|
generated_at: Utc.timestamp_opt(124, 0).single().expect("timestamp"),
|
|
}
|
|
}
|
|
|
|
fn fixed_thread_id() -> ThreadId {
|
|
ThreadId::try_from("0194f5a6-89ab-7cde-8123-456789abcdef").expect("valid thread id")
|
|
}
|
|
|
|
#[test]
|
|
fn rollout_summary_file_stem_uses_uuid_timestamp_and_hash_when_slug_missing() {
|
|
let thread_id = fixed_thread_id();
|
|
let memory = stage1_output_with_slug(thread_id, /*rollout_slug*/ None);
|
|
|
|
assert_eq!(rollout_summary_file_stem(&memory), FIXED_PREFIX);
|
|
}
|
|
|
|
#[test]
|
|
fn rollout_summary_file_stem_sanitizes_and_truncates_slug() {
|
|
let thread_id = fixed_thread_id();
|
|
let memory = stage1_output_with_slug(
|
|
thread_id,
|
|
Some("Unsafe Slug/With Spaces & Symbols + EXTRA_LONG_12345_67890_ABCDE_fghij_klmno"),
|
|
);
|
|
|
|
let stem = rollout_summary_file_stem(&memory);
|
|
let slug = stem
|
|
.strip_prefix(&format!("{FIXED_PREFIX}-"))
|
|
.expect("slug suffix should be present");
|
|
assert_eq!(slug.len(), 60);
|
|
assert_eq!(
|
|
slug,
|
|
"unsafe_slug_with_spaces___symbols___extra_long_12345_67890_a"
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn rollout_summary_file_stem_uses_uuid_timestamp_and_hash_when_slug_is_empty() {
|
|
let thread_id = fixed_thread_id();
|
|
let memory = stage1_output_with_slug(thread_id, Some(""));
|
|
|
|
assert_eq!(rollout_summary_file_stem(&memory), FIXED_PREFIX);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn sync_rollout_summaries_and_raw_memories_file_keeps_latest_memories_only() {
|
|
let dir = tempdir().expect("tempdir");
|
|
let root = dir.path().join("memory");
|
|
ensure_layout(&root).await.expect("ensure layout");
|
|
|
|
let keep_id = ThreadId::default().to_string();
|
|
let drop_id = ThreadId::default().to_string();
|
|
let keep_path = rollout_summaries_dir(&root).join(format!("{keep_id}.md"));
|
|
let drop_path = rollout_summaries_dir(&root).join(format!("{drop_id}.md"));
|
|
tokio::fs::write(&keep_path, "keep")
|
|
.await
|
|
.expect("write keep");
|
|
tokio::fs::write(&drop_path, "drop")
|
|
.await
|
|
.expect("write drop");
|
|
|
|
let memories = vec![Stage1Output {
|
|
thread_id: ThreadId::try_from(keep_id.clone()).expect("thread id"),
|
|
source_updated_at: Utc.timestamp_opt(100, 0).single().expect("timestamp"),
|
|
raw_memory: "raw memory".to_string(),
|
|
rollout_summary: "short summary".to_string(),
|
|
rollout_slug: None,
|
|
rollout_path: PathBuf::from("/tmp/rollout-100.jsonl"),
|
|
cwd: PathBuf::from("/tmp/workspace"),
|
|
git_branch: None,
|
|
generated_at: Utc.timestamp_opt(101, 0).single().expect("timestamp"),
|
|
}];
|
|
|
|
sync_rollout_summaries_from_memories(
|
|
&root,
|
|
&memories,
|
|
DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION,
|
|
)
|
|
.await
|
|
.expect("sync rollout summaries");
|
|
rebuild_raw_memories_file_from_memories(
|
|
&root,
|
|
&memories,
|
|
DEFAULT_MEMORIES_MAX_RAW_MEMORIES_FOR_CONSOLIDATION,
|
|
)
|
|
.await
|
|
.expect("rebuild raw memories");
|
|
|
|
assert!(
|
|
!tokio::fs::try_exists(&keep_path)
|
|
.await
|
|
.expect("check stale keep path"),
|
|
"sync should prune stale filename that used thread id only"
|
|
);
|
|
assert!(
|
|
!tokio::fs::try_exists(&drop_path)
|
|
.await
|
|
.expect("check stale drop path"),
|
|
"sync should prune stale filename for dropped thread"
|
|
);
|
|
|
|
let mut dir = tokio::fs::read_dir(rollout_summaries_dir(&root))
|
|
.await
|
|
.expect("open rollout summaries dir");
|
|
let mut files = Vec::new();
|
|
while let Some(entry) = dir.next_entry().await.expect("read dir entry") {
|
|
files.push(entry.file_name().to_string_lossy().to_string());
|
|
}
|
|
files.sort_unstable();
|
|
assert_eq!(files.len(), 1);
|
|
let canonical_rollout_summary_file = &files[0];
|
|
|
|
let raw_memories = tokio::fs::read_to_string(raw_memories_file(&root))
|
|
.await
|
|
.expect("read raw memories");
|
|
assert!(raw_memories.contains("raw memory"));
|
|
assert!(raw_memories.contains(&keep_id));
|
|
assert!(raw_memories.contains("cwd: /tmp/workspace"));
|
|
assert!(raw_memories.contains("rollout_path: /tmp/rollout-100.jsonl"));
|
|
assert!(raw_memories.contains(&format!(
|
|
"rollout_summary_file: {canonical_rollout_summary_file}"
|
|
)));
|
|
}
|