mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
* fix: patch whisper-rs-sys for Windows MSVC static CRT (/MT) The upstream whisper-rs-sys builds whisper.cpp via CMake which defaults to /MD (dynamic CRT), but Rust and all other C deps use /MT (static CRT). This causes LNK2038/LNK1169 linker errors on Windows. Patch whisper-rs-sys from tinyhumansai/whisper-rs-sys fork which adds config.static_crt(true) and overrides all per-config CMake flags (Debug/Release/MinSizeRel/RelWithDebInfo) from /MD to /MT. Closes #273 * feat: add bulk namespace clear operation for memory Add memory_clear_namespace RPC that atomically deletes all documents, vector chunks, KV entries, and graph relations in a namespace. Includes Tauri command wrapper and three tests verifying cleanup, no-op on empty namespaces, and cross-namespace safety. Closes #205 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * feat(memory): add Clear Namespace UI to MemoryDebugPanel (#205) Adds namespace selector, confirmation dialog, success/error feedback, and auto-refresh after clearing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * style: fix prettier and cargo fmt formatting Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: unwrap RpcOutcome envelope in memoryClearNamespace callCoreRpc returns { result: T }, but memoryClearNamespace was returning the raw envelope instead of unwrapping .result like other callers do. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: sanil jain <jainsanil18@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
sanil jain
Claude Opus 4.6
parent
bd8d3ed15a
commit
4027f50c9e
@@ -1,6 +1,7 @@
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import {
|
||||
memoryClearNamespace,
|
||||
type MemoryDebugDocument,
|
||||
memoryDeleteDocument,
|
||||
memoryListDocuments,
|
||||
@@ -36,6 +37,11 @@ const MemoryDebugPanel = () => {
|
||||
const [queryLoading, setQueryLoading] = useState(false);
|
||||
const [recallLoading, setRecallLoading] = useState(false);
|
||||
|
||||
const [clearNamespaceInput, setClearNamespaceInput] = useState('');
|
||||
const [clearLoading, setClearLoading] = useState(false);
|
||||
const [clearSuccess, setClearSuccess] = useState<string | null>(null);
|
||||
const [clearError, setClearError] = useState<string | null>(null);
|
||||
|
||||
const maxChunks = useMemo(() => {
|
||||
const parsed = Number(maxChunksInput);
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) return 10;
|
||||
@@ -136,6 +142,33 @@ const MemoryDebugPanel = () => {
|
||||
}
|
||||
}, [maxChunks, namespaceInput]);
|
||||
|
||||
const handleClearNamespace = useCallback(async () => {
|
||||
const ns = clearNamespaceInput.trim();
|
||||
if (!ns) return;
|
||||
|
||||
const confirmed = window.confirm(
|
||||
`This will permanently delete ALL documents in namespace "${ns}". This action cannot be undone.\n\nContinue?`
|
||||
);
|
||||
if (!confirmed) return;
|
||||
|
||||
setClearLoading(true);
|
||||
setClearError(null);
|
||||
setClearSuccess(null);
|
||||
try {
|
||||
const result = await memoryClearNamespace(ns);
|
||||
if (result.cleared) {
|
||||
setClearSuccess(`Namespace "${result.namespace}" cleared successfully.`);
|
||||
} else {
|
||||
setClearSuccess(`Clear request completed for "${result.namespace}" (nothing to clear).`);
|
||||
}
|
||||
await refreshAll();
|
||||
} catch (error) {
|
||||
setClearError(error instanceof Error ? error.message : String(error));
|
||||
} finally {
|
||||
setClearLoading(false);
|
||||
}
|
||||
}, [clearNamespaceInput, refreshAll]);
|
||||
|
||||
return (
|
||||
<div className="overflow-hidden h-full flex flex-col">
|
||||
<SettingsHeader title="Memory Debug" showBackButton={true} onBack={navigateBack} />
|
||||
@@ -240,6 +273,71 @@ const MemoryDebugPanel = () => {
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard
|
||||
title="Clear Namespace"
|
||||
priority="tools"
|
||||
icon={
|
||||
<svg className="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||
<path
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
strokeWidth={2}
|
||||
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||
/>
|
||||
</svg>
|
||||
}>
|
||||
<div className="space-y-3">
|
||||
<p className="text-xs text-stone-400">
|
||||
Delete all documents within a namespace. This is a destructive operation and cannot be
|
||||
undone.
|
||||
</p>
|
||||
|
||||
<label className="block text-xs text-stone-300">
|
||||
Namespace
|
||||
{namespaces.length > 0 ? (
|
||||
<select
|
||||
value={clearNamespaceInput}
|
||||
onChange={e => setClearNamespaceInput(e.target.value)}
|
||||
className="mt-1 w-full rounded border border-stone-600 bg-black/30 px-3 py-2 text-sm text-white">
|
||||
<option value="">-- select a namespace --</option>
|
||||
{namespaces.map(ns => (
|
||||
<option key={ns} value={ns}>
|
||||
{ns}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
) : (
|
||||
<input
|
||||
value={clearNamespaceInput}
|
||||
onChange={e => setClearNamespaceInput(e.target.value)}
|
||||
className="mt-1 w-full rounded border border-stone-600 bg-black/30 px-3 py-2 text-sm text-white"
|
||||
placeholder="e.g. skill:gmail:user@example.com"
|
||||
/>
|
||||
)}
|
||||
</label>
|
||||
|
||||
<PrimaryButton
|
||||
variant="outline"
|
||||
onClick={() => void handleClearNamespace()}
|
||||
loading={clearLoading}
|
||||
disabled={!clearNamespaceInput.trim()}
|
||||
className="border-coral-500/50 text-coral-300 hover:bg-coral-500/10">
|
||||
Clear Namespace
|
||||
</PrimaryButton>
|
||||
|
||||
{clearSuccess && (
|
||||
<div className="text-xs text-sage-300 border border-sage-500/30 bg-sage-500/10 rounded p-2">
|
||||
{clearSuccess}
|
||||
</div>
|
||||
)}
|
||||
{clearError && (
|
||||
<div className="text-xs text-coral-300 border border-coral-500/30 bg-coral-500/10 rounded p-2">
|
||||
{clearError}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</SectionCard>
|
||||
|
||||
<SectionCard
|
||||
title="Query & Recall"
|
||||
priority="tools"
|
||||
|
||||
@@ -282,6 +282,19 @@ export async function memoryDeleteDocument(
|
||||
});
|
||||
}
|
||||
|
||||
export async function memoryClearNamespace(
|
||||
namespace: string
|
||||
): Promise<{ cleared: boolean; namespace: string }> {
|
||||
if (!isTauri()) {
|
||||
throw new Error('Not running in Tauri');
|
||||
}
|
||||
const response = await callCoreRpc<{ result: { cleared: boolean; namespace: string } }>({
|
||||
method: 'openhuman.memory_clear_namespace',
|
||||
params: { namespace },
|
||||
});
|
||||
return response.result;
|
||||
}
|
||||
|
||||
export async function memoryQueryNamespace(
|
||||
namespace: string,
|
||||
query: string,
|
||||
|
||||
@@ -503,6 +503,17 @@ pub struct NamespaceOnlyParams {
|
||||
pub namespace: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct ClearNamespaceParams {
|
||||
pub namespace: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub struct ClearNamespaceResult {
|
||||
pub cleared: bool,
|
||||
pub namespace: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct DeleteDocParams {
|
||||
pub namespace: String,
|
||||
@@ -656,6 +667,25 @@ pub async fn doc_delete(params: DeleteDocParams) -> Result<RpcOutcome<serde_json
|
||||
Ok(RpcOutcome::single_log(result, "memory document deleted"))
|
||||
}
|
||||
|
||||
pub async fn clear_namespace(
|
||||
params: ClearNamespaceParams,
|
||||
) -> Result<RpcOutcome<ClearNamespaceResult>, String> {
|
||||
let client = active_memory_client().await?;
|
||||
log::debug!(
|
||||
"[memory] clear_namespace RPC: namespace={}",
|
||||
params.namespace
|
||||
);
|
||||
client.clear_namespace(¶ms.namespace).await?;
|
||||
let msg = format!("memory namespace '{}' cleared", params.namespace);
|
||||
Ok(RpcOutcome::single_log(
|
||||
ClearNamespaceResult {
|
||||
cleared: true,
|
||||
namespace: params.namespace,
|
||||
},
|
||||
&msg,
|
||||
))
|
||||
}
|
||||
|
||||
pub async fn context_query(params: QueryNamespaceParams) -> Result<RpcOutcome<String>, String> {
|
||||
let client = active_memory_client().await?;
|
||||
let result = client
|
||||
|
||||
@@ -4,8 +4,9 @@ use serde_json::{Map, Value};
|
||||
use crate::core::all::{ControllerFuture, RegisteredController};
|
||||
use crate::core::{ControllerSchema, FieldSchema, TypeSchema};
|
||||
use crate::openhuman::memory::rpc::{
|
||||
self, DeleteDocParams, GraphQueryParams, GraphUpsertParams, IngestDocParams, KvGetDeleteParams,
|
||||
KvSetParams, NamespaceOnlyParams, PutDocParams, QueryNamespaceParams, RecallNamespaceParams,
|
||||
self, ClearNamespaceParams, DeleteDocParams, GraphQueryParams, GraphUpsertParams,
|
||||
IngestDocParams, KvGetDeleteParams, KvSetParams, NamespaceOnlyParams, PutDocParams,
|
||||
QueryNamespaceParams, RecallNamespaceParams,
|
||||
};
|
||||
use crate::openhuman::memory::{
|
||||
DeleteDocumentRequest, EmptyRequest, ListDocumentsRequest, ListMemoryFilesRequest,
|
||||
@@ -43,6 +44,7 @@ pub fn all_controller_schemas() -> Vec<ControllerSchema> {
|
||||
schemas("kv_list_namespace"),
|
||||
schemas("graph_upsert"),
|
||||
schemas("graph_query"),
|
||||
schemas("clear_namespace"),
|
||||
]
|
||||
}
|
||||
|
||||
@@ -140,6 +142,10 @@ pub fn all_registered_controllers() -> Vec<RegisteredController> {
|
||||
schema: schemas("graph_query"),
|
||||
handler: handle_graph_query,
|
||||
},
|
||||
RegisteredController {
|
||||
schema: schemas("clear_namespace"),
|
||||
handler: handle_clear_namespace,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
@@ -872,6 +878,34 @@ pub fn schemas(function: &str) -> ControllerSchema {
|
||||
}],
|
||||
},
|
||||
|
||||
// ----- bulk operations -----
|
||||
"clear_namespace" => ControllerSchema {
|
||||
namespace: "memory",
|
||||
function: "clear_namespace",
|
||||
description:
|
||||
"Delete all documents, vector chunks, KV entries, and graph relations for a namespace.",
|
||||
inputs: vec![FieldSchema {
|
||||
name: "namespace",
|
||||
ty: TypeSchema::String,
|
||||
comment: "Namespace to clear completely.",
|
||||
required: true,
|
||||
}],
|
||||
outputs: vec![
|
||||
FieldSchema {
|
||||
name: "cleared",
|
||||
ty: TypeSchema::Bool,
|
||||
comment: "True when the namespace was cleared.",
|
||||
required: true,
|
||||
},
|
||||
FieldSchema {
|
||||
name: "namespace",
|
||||
ty: TypeSchema::String,
|
||||
comment: "The namespace that was cleared.",
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
|
||||
// ----- fallback -----
|
||||
_other => ControllerSchema {
|
||||
namespace: "memory",
|
||||
@@ -1063,6 +1097,13 @@ fn handle_graph_query(params: Map<String, Value>) -> ControllerFuture {
|
||||
})
|
||||
}
|
||||
|
||||
fn handle_clear_namespace(params: Map<String, Value>) -> ControllerFuture {
|
||||
Box::pin(async move {
|
||||
let payload = parse_params::<ClearNamespaceParams>(params)?;
|
||||
to_json(rpc::clear_namespace(payload).await?)
|
||||
})
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Helpers
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -105,6 +105,10 @@ impl MemoryClient {
|
||||
self.inner.delete_document(namespace, document_id).await
|
||||
}
|
||||
|
||||
pub async fn clear_namespace(&self, namespace: &str) -> Result<(), String> {
|
||||
self.inner.clear_namespace(namespace).await
|
||||
}
|
||||
|
||||
pub async fn clear_skill_memory(
|
||||
&self,
|
||||
skill_id: &str,
|
||||
|
||||
@@ -272,6 +272,76 @@ impl UnifiedMemory {
|
||||
Ok(out.into_iter().collect())
|
||||
}
|
||||
|
||||
/// Delete all documents, vector chunks, KV entries, and graph relations
|
||||
/// for the given namespace in a single transaction. Also removes the
|
||||
/// on-disk markdown directory (`namespaces/{ns}/docs/`).
|
||||
pub async fn clear_namespace(&self, namespace: &str) -> Result<(), String> {
|
||||
let ns = Self::sanitize_namespace(namespace);
|
||||
log::debug!("[memory] clear_namespace: starting for namespace={ns}");
|
||||
|
||||
{
|
||||
let conn = self.conn.lock();
|
||||
let tx = conn
|
||||
.unchecked_transaction()
|
||||
.map_err(|e| format!("clear_namespace begin tx: {e}"))?;
|
||||
|
||||
let doc_count = tx
|
||||
.execute(
|
||||
"DELETE FROM memory_docs WHERE namespace = ?1",
|
||||
rusqlite::params![ns],
|
||||
)
|
||||
.map_err(|e| format!("clear_namespace delete memory_docs: {e}"))?;
|
||||
log::debug!("[memory] clear_namespace: deleted {doc_count} rows from memory_docs");
|
||||
|
||||
let chunk_count = tx
|
||||
.execute(
|
||||
"DELETE FROM vector_chunks WHERE namespace = ?1",
|
||||
rusqlite::params![ns],
|
||||
)
|
||||
.map_err(|e| format!("clear_namespace delete vector_chunks: {e}"))?;
|
||||
log::debug!("[memory] clear_namespace: deleted {chunk_count} rows from vector_chunks");
|
||||
|
||||
let kv_count = tx
|
||||
.execute(
|
||||
"DELETE FROM kv_namespace WHERE namespace = ?1",
|
||||
rusqlite::params![ns],
|
||||
)
|
||||
.map_err(|e| format!("clear_namespace delete kv_namespace: {e}"))?;
|
||||
log::debug!("[memory] clear_namespace: deleted {kv_count} rows from kv_namespace");
|
||||
|
||||
let graph_count = tx
|
||||
.execute(
|
||||
"DELETE FROM graph_namespace WHERE namespace = ?1",
|
||||
rusqlite::params![ns],
|
||||
)
|
||||
.map_err(|e| format!("clear_namespace delete graph_namespace: {e}"))?;
|
||||
log::debug!(
|
||||
"[memory] clear_namespace: deleted {graph_count} rows from graph_namespace"
|
||||
);
|
||||
|
||||
tx.commit()
|
||||
.map_err(|e| format!("clear_namespace commit tx: {e}"))?;
|
||||
}
|
||||
|
||||
// Remove on-disk markdown files for this namespace.
|
||||
let docs_dir = self.namespace_dir(&ns).join("docs");
|
||||
if docs_dir.exists() {
|
||||
std::fs::remove_dir_all(&docs_dir).map_err(|e| {
|
||||
format!(
|
||||
"clear_namespace remove docs dir {}: {e}",
|
||||
docs_dir.display()
|
||||
)
|
||||
})?;
|
||||
log::debug!(
|
||||
"[memory] clear_namespace: removed docs directory {}",
|
||||
docs_dir.display()
|
||||
);
|
||||
}
|
||||
|
||||
log::debug!("[memory] clear_namespace: completed for namespace={ns}");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_document(
|
||||
&self,
|
||||
namespace: &str,
|
||||
@@ -316,3 +386,264 @@ impl UnifiedMemory {
|
||||
Ok(json!({"deleted": deleted, "namespace": ns, "documentId": document_id }))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::sync::Arc;
|
||||
|
||||
use serde_json::json;
|
||||
use tempfile::TempDir;
|
||||
|
||||
use crate::openhuman::memory::{
|
||||
embeddings::NoopEmbedding, NamespaceDocumentInput, UnifiedMemory,
|
||||
};
|
||||
|
||||
fn make_doc_input(
|
||||
namespace: &str,
|
||||
key: &str,
|
||||
title: &str,
|
||||
content: &str,
|
||||
) -> NamespaceDocumentInput {
|
||||
NamespaceDocumentInput {
|
||||
namespace: namespace.to_string(),
|
||||
key: key.to_string(),
|
||||
title: title.to_string(),
|
||||
content: content.to_string(),
|
||||
source_type: "doc".to_string(),
|
||||
priority: "medium".to_string(),
|
||||
tags: vec![],
|
||||
metadata: json!({}),
|
||||
category: "core".to_string(),
|
||||
session_id: None,
|
||||
document_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn clear_namespace_removes_all_data_and_preserves_other_namespaces() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let memory = UnifiedMemory::new(tmp.path(), Arc::new(NoopEmbedding), None).unwrap();
|
||||
|
||||
// --- Populate "test:cleanup" namespace ---
|
||||
|
||||
// 3 documents
|
||||
memory
|
||||
.upsert_document(make_doc_input(
|
||||
"test:cleanup",
|
||||
"doc-a",
|
||||
"Document A",
|
||||
"Content of document A for cleanup.",
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
memory
|
||||
.upsert_document(make_doc_input(
|
||||
"test:cleanup",
|
||||
"doc-b",
|
||||
"Document B",
|
||||
"Content of document B for cleanup.",
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
memory
|
||||
.upsert_document(make_doc_input(
|
||||
"test:cleanup",
|
||||
"doc-c",
|
||||
"Document C",
|
||||
"Content of document C for cleanup.",
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 2 KV entries
|
||||
memory
|
||||
.kv_set_namespace("test:cleanup", "pref-1", &json!({"theme": "dark"}))
|
||||
.await
|
||||
.unwrap();
|
||||
memory
|
||||
.kv_set_namespace("test:cleanup", "pref-2", &json!({"lang": "en"}))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// 2 graph relations
|
||||
memory
|
||||
.graph_upsert_namespace(
|
||||
"test:cleanup",
|
||||
"Alice",
|
||||
"knows",
|
||||
"Bob",
|
||||
&json!({"source": "test"}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
memory
|
||||
.graph_upsert_namespace(
|
||||
"test:cleanup",
|
||||
"Bob",
|
||||
"works_at",
|
||||
"Acme",
|
||||
&json!({"source": "test"}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// --- Populate "test:other" namespace (control) ---
|
||||
|
||||
memory
|
||||
.upsert_document(make_doc_input(
|
||||
"test:other",
|
||||
"other-doc",
|
||||
"Other Document",
|
||||
"Content of document in the other namespace.",
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
memory
|
||||
.kv_set_namespace("test:other", "other-key", &json!({"value": true}))
|
||||
.await
|
||||
.unwrap();
|
||||
memory
|
||||
.graph_upsert_namespace(
|
||||
"test:other",
|
||||
"X",
|
||||
"relates_to",
|
||||
"Y",
|
||||
&json!({"source": "other"}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// --- Verify pre-conditions ---
|
||||
|
||||
let cleanup_docs = memory.list_documents(Some("test:cleanup")).await.unwrap();
|
||||
assert_eq!(
|
||||
cleanup_docs["count"].as_u64().unwrap(),
|
||||
3,
|
||||
"test:cleanup should have 3 documents before clear"
|
||||
);
|
||||
|
||||
let cleanup_kv = memory.kv_list_namespace("test:cleanup").await.unwrap();
|
||||
assert_eq!(
|
||||
cleanup_kv.len(),
|
||||
2,
|
||||
"test:cleanup should have 2 KV entries before clear"
|
||||
);
|
||||
|
||||
let cleanup_graph = memory
|
||||
.graph_relations_namespace("test:cleanup", None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
cleanup_graph.len(),
|
||||
2,
|
||||
"test:cleanup should have 2 graph relations before clear"
|
||||
);
|
||||
|
||||
let other_docs = memory.list_documents(Some("test:other")).await.unwrap();
|
||||
assert_eq!(
|
||||
other_docs["count"].as_u64().unwrap(),
|
||||
1,
|
||||
"test:other should have 1 document before clear"
|
||||
);
|
||||
|
||||
// --- Execute clear_namespace ---
|
||||
|
||||
memory.clear_namespace("test:cleanup").await.unwrap();
|
||||
|
||||
// --- Assert: "test:cleanup" is empty ---
|
||||
|
||||
let cleanup_docs_after = memory.list_documents(Some("test:cleanup")).await.unwrap();
|
||||
assert_eq!(
|
||||
cleanup_docs_after["count"].as_u64().unwrap(),
|
||||
0,
|
||||
"test:cleanup documents should be empty after clear"
|
||||
);
|
||||
|
||||
let cleanup_kv_after = memory.kv_list_namespace("test:cleanup").await.unwrap();
|
||||
assert!(
|
||||
cleanup_kv_after.is_empty(),
|
||||
"test:cleanup KV entries should be empty after clear"
|
||||
);
|
||||
|
||||
let cleanup_graph_after = memory
|
||||
.graph_relations_namespace("test:cleanup", None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
cleanup_graph_after.is_empty(),
|
||||
"test:cleanup graph relations should be empty after clear"
|
||||
);
|
||||
|
||||
// --- Assert: "test:other" is untouched (critical) ---
|
||||
|
||||
let other_docs_after = memory.list_documents(Some("test:other")).await.unwrap();
|
||||
assert_eq!(
|
||||
other_docs_after["count"].as_u64().unwrap(),
|
||||
1,
|
||||
"test:other document must still exist after clearing test:cleanup"
|
||||
);
|
||||
|
||||
let other_kv_after = memory.kv_list_namespace("test:other").await.unwrap();
|
||||
assert_eq!(
|
||||
other_kv_after.len(),
|
||||
1,
|
||||
"test:other KV entry must still exist after clearing test:cleanup"
|
||||
);
|
||||
|
||||
let other_graph_after = memory
|
||||
.graph_relations_namespace("test:other", None, None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
other_graph_after.len(),
|
||||
1,
|
||||
"test:other graph relation must still exist after clearing test:cleanup"
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn clear_namespace_on_empty_namespace_is_noop() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let memory = UnifiedMemory::new(tmp.path(), Arc::new(NoopEmbedding), None).unwrap();
|
||||
|
||||
// Clearing a namespace that has never been used should succeed without error.
|
||||
memory.clear_namespace("nonexistent").await.unwrap();
|
||||
|
||||
let docs = memory.list_documents(Some("nonexistent")).await.unwrap();
|
||||
assert_eq!(docs["count"].as_u64().unwrap(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn clear_namespace_removes_on_disk_markdown_files() {
|
||||
let tmp = TempDir::new().unwrap();
|
||||
let memory = UnifiedMemory::new(tmp.path(), Arc::new(NoopEmbedding), None).unwrap();
|
||||
|
||||
memory
|
||||
.upsert_document(make_doc_input(
|
||||
"test:diskcheck",
|
||||
"disk-doc",
|
||||
"Disk Doc",
|
||||
"This doc has a markdown file on disk.",
|
||||
))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let docs_dir = tmp
|
||||
.path()
|
||||
.join("memory")
|
||||
.join("namespaces")
|
||||
.join("test_diskcheck")
|
||||
.join("docs");
|
||||
assert!(
|
||||
docs_dir.exists(),
|
||||
"docs directory should exist after upsert"
|
||||
);
|
||||
|
||||
memory.clear_namespace("test:diskcheck").await.unwrap();
|
||||
|
||||
assert!(
|
||||
!docs_dir.exists(),
|
||||
"docs directory should be removed after clear_namespace"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user