fix: unwrap API envelope in memory tauriCommands and fix file path scope (#172)

After the controller registry migration (#138), memory RPC methods
return ApiEnvelope responses but the frontend expected flat data.
This caused "map is not a function" errors on the Intelligence page.

- Unwrap envelope in memoryListDocuments, memoryListNamespaces,
  aiListMemoryFiles, and aiReadMemoryFile in tauriCommands.ts
- Fix resolve_existing_memory_path and resolve_writable_memory_path
  to scope against workspace root instead of memory/ subdirectory —
  workspace files (MEMORY.md, SOUL.md) live at the root

Closes #170
This commit is contained in:
sanil-23
2026-04-01 11:01:00 -07:00
committed by GitHub
parent 2383d51ea6
commit f5b66de2f1
2 changed files with 43 additions and 18 deletions
+29 -4
View File
@@ -217,17 +217,30 @@ export async function memoryListDocuments(namespace?: string): Promise<unknown>
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await callCoreRpc<unknown>({
const resp = await callCoreRpc<unknown>({
method: 'openhuman.memory_list_documents',
params: { namespace },
});
// Unwrap envelope: registry returns { data: { documents: [...] }, meta: {...} }
if (resp && typeof resp === 'object' && !Array.isArray(resp) && 'data' in resp) {
return (resp as Record<string, unknown>).data;
}
return resp;
}
export async function memoryListNamespaces(): Promise<string[]> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await callCoreRpc<string[]>({ method: 'openhuman.memory_list_namespaces' });
const resp = await callCoreRpc<{ data?: { namespaces?: string[] }; namespaces?: string[] }>({
method: 'openhuman.memory_list_namespaces',
});
if (resp && typeof resp === 'object') {
if (Array.isArray(resp)) return resp;
const ns = resp.data?.namespaces ?? resp.namespaces;
if (Array.isArray(ns)) return ns;
}
return [];
}
export async function memoryDeleteDocument(
@@ -325,20 +338,32 @@ export async function aiListMemoryFiles(relativeDir = 'memory'): Promise<string[
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await callCoreRpc<string[]>({
const resp = await callCoreRpc<{ data?: { files?: string[] }; files?: string[] }>({
method: 'openhuman.memory_list_files',
params: { relative_dir: relativeDir },
});
// Unwrap envelope: registry returns { data: { files: [...] } }
if (resp && typeof resp === 'object') {
if (Array.isArray(resp)) return resp;
const files = resp.data?.files ?? resp.files;
if (Array.isArray(files)) return files;
}
return [];
}
export async function aiReadMemoryFile(relativePath: string): Promise<string> {
if (!isTauri()) {
throw new Error('Not running in Tauri');
}
return await callCoreRpc<string>({
const resp = await callCoreRpc<{ data?: { content?: string }; content?: string } | string>({
method: 'openhuman.memory_read_file',
params: { relative_path: relativePath },
});
if (typeof resp === 'string') return resp;
if (resp && typeof resp === 'object') {
return resp.data?.content ?? resp.content ?? '';
}
return '';
}
export async function aiWriteMemoryFile(relativePath: string, content: string): Promise<void> {
+14 -14
View File
@@ -335,27 +335,27 @@ async fn resolve_memory_root() -> Result<PathBuf, String> {
async fn resolve_existing_memory_path(relative_path: &str) -> Result<PathBuf, String> {
validate_memory_relative_path(relative_path)?;
let memory_root = resolve_memory_root().await?;
let workspace_root = memory_root
.parent()
.ok_or_else(|| "memory root is missing a parent workspace".to_string())?;
let full_path = workspace_root.join(relative_path);
let workspace_dir = current_workspace_dir().await?;
let canonical_workspace = workspace_dir
.canonicalize()
.map_err(|e| format!("resolve workspace dir {}: {e}", workspace_dir.display()))?;
let full_path = workspace_dir.join(relative_path);
let resolved = full_path
.canonicalize()
.map_err(|e| format!("resolve memory path {}: {e}", full_path.display()))?;
if !resolved.starts_with(&memory_root) {
return Err("memory path escapes the memory directory".to_string());
if !resolved.starts_with(&canonical_workspace) {
return Err("memory path escapes the workspace directory".to_string());
}
Ok(resolved)
}
async fn resolve_writable_memory_path(relative_path: &str) -> Result<PathBuf, String> {
validate_memory_relative_path(relative_path)?;
let memory_root = resolve_memory_root().await?;
let workspace_root = memory_root
.parent()
.ok_or_else(|| "memory root is missing a parent workspace".to_string())?;
let full_path = workspace_root.join(relative_path);
let workspace_dir = current_workspace_dir().await?;
let canonical_workspace = workspace_dir
.canonicalize()
.map_err(|e| format!("resolve workspace dir {}: {e}", workspace_dir.display()))?;
let full_path = workspace_dir.join(relative_path);
let parent = full_path
.parent()
.ok_or_else(|| "memory path must include a file name".to_string())?;
@@ -364,8 +364,8 @@ async fn resolve_writable_memory_path(relative_path: &str) -> Result<PathBuf, St
let resolved_parent = parent
.canonicalize()
.map_err(|e| format!("resolve memory parent {}: {e}", parent.display()))?;
if !resolved_parent.starts_with(&memory_root) {
return Err("memory path escapes the memory directory".to_string());
if !resolved_parent.starts_with(&canonical_workspace) {
return Err("memory path escapes the workspace directory".to_string());
}
let file_name = full_path
.file_name()