diff --git a/cortex-mem-insights/src/lib/api.ts b/cortex-mem-insights/src/lib/api.ts index 583a7c9..87b6d70 100644 --- a/cortex-mem-insights/src/lib/api.ts +++ b/cortex-mem-insights/src/lib/api.ts @@ -74,9 +74,9 @@ class ApiClient { } // Filesystem endpoints - async listDirectory(path: string): Promise { + async listDirectory(path: string, includeLayers: boolean = false): Promise { const response = await this.request<{ uri: string; total: number; entries: FileEntryResponse[] }>( - `/filesystem/list?uri=${encodeURIComponent(path)}` + `/filesystem/list?uri=${encodeURIComponent(path)}&include_layers=${includeLayers}` ); return response.entries; } diff --git a/cortex-mem-insights/src/lib/pages/Memories.svelte b/cortex-mem-insights/src/lib/pages/Memories.svelte index c7b3adc..bfe66f0 100644 --- a/cortex-mem-insights/src/lib/pages/Memories.svelte +++ b/cortex-mem-insights/src/lib/pages/Memories.svelte @@ -13,6 +13,7 @@ let isEditing = $state(false); let loading = $state(true); let error = $state(''); + let showLayers = $state(false); async function loadDirectory(path: string) { loading = true; @@ -22,7 +23,7 @@ fileContent = null; try { - entries = await apiClient.listDirectory(path); + entries = await apiClient.listDirectory(path, showLayers); } catch (e) { error = e instanceof Error ? e.message : 'Failed to load directory'; } finally { @@ -92,7 +93,7 @@ isEditing = false; } - // Reload when tenant changes + // Reload when tenant changes - navigate to user root $effect(() => { const tenant = $currentTenant; if (tenant) { @@ -100,6 +101,14 @@ } }); + // Reload when showLayers changes - reload current path + $effect(() => { + const layers = showLayers; // Track showLayers dependency + if ($currentTenant) { + loadDirectory(currentPath); + } + }); + function navigateToRoot(root: string) { loadDirectory(`cortex://${root}`); } @@ -162,6 +171,17 @@ + +
+ + {#if showLayers} + .abstract.md (L0 ~100 tokens) | .overview.md (L1 ~2000 tokens) + {/if} +
+
@@ -272,4 +292,44 @@ white-space: nowrap; flex: 1; } + + .layer-toggle { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; + padding: 0.75rem 1rem; + background: var(--surface-elevated, #1e1e2e); + border-radius: 8px; + border: 1px solid var(--border-color, #313244); + } + + .toggle-label { + display: flex; + align-items: center; + gap: 0.5rem; + cursor: pointer; + user-select: none; + } + + .toggle-label input[type="checkbox"] { + width: 18px; + height: 18px; + accent-color: var(--accent-color, #89b4fa); + cursor: pointer; + } + + .toggle-text { + font-size: 0.9rem; + color: var(--text-primary, #cdd6f4); + font-weight: 500; + } + + .layer-hint { + font-size: 0.8rem; + color: var(--text-secondary, #a6adc8); + background: var(--surface-base, #181825); + padding: 0.25rem 0.75rem; + border-radius: 4px; + } \ No newline at end of file diff --git a/cortex-mem-service/src/handlers/filesystem.rs b/cortex-mem-service/src/handlers/filesystem.rs index 788862a..7289ea4 100644 --- a/cortex-mem-service/src/handlers/filesystem.rs +++ b/cortex-mem-service/src/handlers/filesystem.rs @@ -46,11 +46,12 @@ pub async fn list_directory( let uri = params.uri.clone(); let recursive = params.recursive; let include_abstracts = params.include_abstracts; + let include_layers = params.include_layers; // Use spawn_blocking to avoid blocking the async runtime let entries = tokio::task::spawn_blocking(move || { let mut entries = Vec::new(); - list_directory_recursive(&base_path, &uri, recursive, include_abstracts, &mut entries); + list_directory_recursive(&base_path, &uri, recursive, include_abstracts, include_layers, &mut entries); entries }) .await @@ -70,6 +71,7 @@ fn list_directory_recursive( base_uri: &str, recursive: bool, include_abstracts: bool, + include_layers: bool, entries: &mut Vec, ) { if !base_path.exists() || !base_path.is_dir() { @@ -80,9 +82,15 @@ fn list_directory_recursive( for entry in dir.flatten() { let name = entry.file_name().to_string_lossy().to_string(); - // Skip hidden files (layer files are accessed via dedicated endpoints) + // Skip hidden files unless include_layers is true and it's a layer file if name.starts_with('.') { - continue; + if !include_layers { + continue; + } + // Only show .abstract.md (L0) and .overview.md (L1) when include_layers is true + if name != ".abstract.md" && name != ".overview.md" { + continue; + } } if let Ok(metadata) = entry.metadata() { @@ -115,7 +123,7 @@ fn list_directory_recursive( // Recurse into subdirectories if requested if recursive && is_dir { let sub_path = base_path.join(&name); - list_directory_recursive(&sub_path, &entry_uri, true, include_abstracts, entries); + list_directory_recursive(&sub_path, &entry_uri, true, include_abstracts, include_layers, entries); } } } diff --git a/cortex-mem-service/src/models.rs b/cortex-mem-service/src/models.rs index fa4d4cf..018ec13 100644 --- a/cortex-mem-service/src/models.rs +++ b/cortex-mem-service/src/models.rs @@ -153,6 +153,9 @@ pub struct LsRequest { /// Whether to include L0 abstracts for files #[serde(default)] pub include_abstracts: bool, + /// Whether to include layer files (.abstract.md for L0, .overview.md for L1) + #[serde(default)] + pub include_layers: bool, } fn default_uri() -> String { diff --git a/examples/@memclaw/plugin/skills/memclaw/SKILL.md b/examples/@memclaw/plugin/skills/memclaw/SKILL.md index 39da558..ee80f8a 100644 --- a/examples/@memclaw/plugin/skills/memclaw/SKILL.md +++ b/examples/@memclaw/plugin/skills/memclaw/SKILL.md @@ -11,18 +11,6 @@ description: MemClaw — High-performance memory plugin for OpenClaw. Outperform Three-layer memory: L0 (abstract ~100t) → L1 (overview ~2000t) → L2 (full content). Supports semantic search + filesystem browsing. -## ⚠️ Required: AGENTS.md Migration - -OpenClaw loads `AGENTS.md` as its working guidelines at startup. If the file still references the legacy memory system (MEMORY.md, daily logs, memory_write), **you MUST update it to use MemClaw instead**. - -### Detection -Check if `AGENTS.md` contains any of these legacy patterns: -- `MEMORY.md` -- `daily/YYYY-MM-DD.md` -- `memory_write` -- `memory_search` -- "Write things down" in context of daily logs - ## Prerequisites 1. Install via `openclaw plugins install @memclaw/memclaw` if not present (tools unavailable or memclaw missing in `openclaw.json`) 2. Configure LLM/Embedding in plugin settings (see Troubleshooting if fails)