mirror of
https://github.com/openai/codex.git
synced 2026-11-08 00:36:31 +00:00
## What changed - Add `memories.version` with `v1` as the default and `v2` as an opt-in selection. - Route memory generation, summaries, and retrieval tools through the selected version. Store `v2` artifacts in `memories_v2` and lazily create a separate SQLite database, keeping jobs and outputs isolated while sharing the thread catalog. - Preserve the initial memory version across extension config updates so summaries and retrieval tools use the same namespace. - Clear both versions on memory reset and remove thread memory from both stores on thread deletion. Recognize both artifact roots in shell usage telemetry. ## Testing Add regression coverage for version defaults and validation, independent job claims and outputs, deletion and reset across versions, and shell usage classification for both memory roots. GitOrigin-RevId: aa799bf87ab6ec10c8f5668213931e57547d080b
124 lines
3.7 KiB
Rust
124 lines
3.7 KiB
Rust
use std::path::Component;
|
|
use std::path::Path;
|
|
use std::path::PathBuf;
|
|
|
|
use crate::backend::AddAdHocMemoryNoteRequest;
|
|
use crate::backend::AddAdHocMemoryNoteResponse;
|
|
use crate::backend::ListMemoriesRequest;
|
|
use crate::backend::ListMemoriesResponse;
|
|
use crate::backend::MemoriesBackend;
|
|
use crate::backend::MemoriesBackendError;
|
|
use crate::backend::ReadMemoryRequest;
|
|
use crate::backend::ReadMemoryResponse;
|
|
use crate::backend::SearchMemoriesRequest;
|
|
use crate::backend::SearchMemoriesResponse;
|
|
|
|
mod ad_hoc_note;
|
|
mod list;
|
|
mod path;
|
|
mod read;
|
|
mod search;
|
|
|
|
#[derive(Debug, Clone)]
|
|
pub(crate) struct LocalMemoriesBackend {
|
|
root: PathBuf,
|
|
}
|
|
|
|
impl LocalMemoriesBackend {
|
|
pub(crate) fn from_memory_root(root: impl Into<PathBuf>) -> Self {
|
|
Self { root: root.into() }
|
|
}
|
|
|
|
async fn resolve_scoped_path(
|
|
&self,
|
|
relative_path: Option<&str>,
|
|
) -> Result<PathBuf, MemoriesBackendError> {
|
|
let Some(relative_path) = relative_path else {
|
|
return Ok(self.root.clone());
|
|
};
|
|
let relative = Path::new(relative_path);
|
|
if relative.components().any(|component| {
|
|
matches!(
|
|
component,
|
|
Component::ParentDir | Component::RootDir | Component::Prefix(_)
|
|
)
|
|
}) {
|
|
return Err(MemoriesBackendError::invalid_path(
|
|
relative_path,
|
|
"must stay within the memories root",
|
|
));
|
|
}
|
|
if relative.components().any(path::is_hidden_component) {
|
|
return Err(MemoriesBackendError::NotFound {
|
|
path: relative_path.to_string(),
|
|
});
|
|
}
|
|
|
|
let components = relative.components().collect::<Vec<_>>();
|
|
let mut scoped_path = self.root.clone();
|
|
for (idx, component) in components.iter().enumerate() {
|
|
scoped_path.push(component.as_os_str());
|
|
|
|
let Some(metadata) = Self::metadata_or_none(&scoped_path).await? else {
|
|
for remaining_component in components.iter().skip(idx + 1) {
|
|
scoped_path.push(remaining_component.as_os_str());
|
|
}
|
|
return Ok(scoped_path);
|
|
};
|
|
|
|
path::reject_symlink(
|
|
&path::display_relative_path(&self.root, &scoped_path),
|
|
&metadata,
|
|
)?;
|
|
if idx + 1 < components.len() && !metadata.is_dir() {
|
|
return Err(MemoriesBackendError::invalid_path(
|
|
relative_path,
|
|
"traverses through a non-directory path component",
|
|
));
|
|
}
|
|
}
|
|
|
|
Ok(scoped_path)
|
|
}
|
|
|
|
async fn metadata_or_none(
|
|
path: &Path,
|
|
) -> Result<Option<std::fs::Metadata>, MemoriesBackendError> {
|
|
match tokio::fs::symlink_metadata(path).await {
|
|
Ok(metadata) => Ok(Some(metadata)),
|
|
Err(err) if err.kind() == std::io::ErrorKind::NotFound => Ok(None),
|
|
Err(err) => Err(err.into()),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl MemoriesBackend for LocalMemoriesBackend {
|
|
async fn add_ad_hoc_note(
|
|
&self,
|
|
request: AddAdHocMemoryNoteRequest,
|
|
) -> Result<AddAdHocMemoryNoteResponse, MemoriesBackendError> {
|
|
ad_hoc_note::add_ad_hoc_note(self, request).await
|
|
}
|
|
|
|
async fn list(
|
|
&self,
|
|
request: ListMemoriesRequest,
|
|
) -> Result<ListMemoriesResponse, MemoriesBackendError> {
|
|
list::list(self, request).await
|
|
}
|
|
|
|
async fn read(
|
|
&self,
|
|
request: ReadMemoryRequest,
|
|
) -> Result<ReadMemoryResponse, MemoriesBackendError> {
|
|
read::read(self, request).await
|
|
}
|
|
|
|
async fn search(
|
|
&self,
|
|
request: SearchMemoriesRequest,
|
|
) -> Result<SearchMemoriesResponse, MemoriesBackendError> {
|
|
search::search(self, request).await
|
|
}
|
|
}
|