Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
117 changes: 117 additions & 0 deletions electron/main/ipc-handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,9 @@ export function registerIpcHandlers(

// File staging handlers (upload/send separation)
registerFileHandlers();

// Memory handlers
registerMemoryHandlers();
}

/**
Expand Down Expand Up @@ -2255,3 +2258,117 @@ function registerSessionHandlers(): void {
});
}

/**
* Memory IPC handlers
* Read memory files from ~/.openclaw/workspace/
*/
function registerMemoryHandlers(): void {
const MEMORY_DIR = join(homedir(), '.openclaw', 'workspace');
const MEMORY_FILE = join(MEMORY_DIR, 'MEMORY.md');
const DAILY_LOGS_DIR = join(MEMORY_DIR, 'memory');

// List all memory files with stats
ipcMain.handle('memory:list', async () => {
try {
const fsP = await import('fs/promises');
const files: Array<{
path: string;
name: string;
size: number;
lastModified: string;
type: 'long-term' | 'daily';
}> = [];

let longTermSize = 0;
let dailyLogCount = 0;
let totalSize = 0;

// Check MEMORY.md (long-term memory)
try {
const stat = await fsP.stat(MEMORY_FILE);
if (stat.isFile()) {
files.push({
path: MEMORY_FILE,
name: 'MEMORY.md',
size: stat.size,
lastModified: stat.mtime.toISOString(),
type: 'long-term',
});
longTermSize = stat.size;
totalSize += stat.size;
}
} catch {
// MEMORY.md doesn't exist, that's fine
}

// Check daily logs directory
try {
const entries = await fsP.readdir(DAILY_LOGS_DIR, { withFileTypes: true });
for (const entry of entries) {
if (entry.isFile() && entry.name.endsWith('.md')) {
const filePath = join(DAILY_LOGS_DIR, entry.name);
const stat = await fsP.stat(filePath);
files.push({
path: filePath,
name: entry.name,
size: stat.size,
lastModified: stat.mtime.toISOString(),
type: 'daily',
});
totalSize += stat.size;
dailyLogCount++;
}
}
} catch {
// memory/ directory doesn't exist, that's fine
}

// Sort daily logs by name (date) descending
files.sort((a, b) => {
if (a.type === 'long-term') return -1;
if (b.type === 'long-term') return 1;
return b.name.localeCompare(a.name);
});

// Get stats
const stats = {
totalFiles: files.length,
totalSize,
longTermSize,
dailyLogCount,
};

// Read MEMORY.md content for preview
let longTermMemory = '';
try {
longTermMemory = await fsP.readFile(MEMORY_FILE, 'utf-8');
} catch {
// File doesn't exist
}

return { success: true, files, stats, longTermMemory };
} catch (error) {
return { success: false, error: String(error), files: [], stats: null };
}
});

// Get specific memory file content
ipcMain.handle('memory:get', async (_, filePath: string) => {
try {
const fsP = await import('fs/promises');
// Security: only allow reading from memory directory
const normalizedPath = filePath.replace(/\.\./g, '');
const allowedDirs = [MEMORY_DIR, DAILY_LOGS_DIR];
const isAllowed = allowedDirs.some(dir => normalizedPath.startsWith(dir));

if (!isAllowed) {
return { success: false, error: 'Access denied' };
}

const content = await fsP.readFile(normalizedPath, 'utf-8');
return { success: true, content };
} catch (error) {
return { success: false, error: String(error) };
}
});
}
2 changes: 2 additions & 0 deletions src/components/layout/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
Terminal,
ExternalLink,
Trash2,
Brain,
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { useSettingsStore } from '@/stores/settings';
Expand Down Expand Up @@ -109,6 +110,7 @@ export function Sidebar() {
const [sessionToDelete, setSessionToDelete] = useState<{ key: string; label: string } | null>(null);

const navItems = [
{ to: '/memory', icon: <Brain className="h-5 w-5" />, label: t('sidebar.memory') },
{ to: '/cron', icon: <Clock className="h-5 w-5" />, label: t('sidebar.cronTasks') },
{ to: '/skills', icon: <Puzzle className="h-5 w-5" />, label: t('sidebar.skills') },
{ to: '/channels', icon: <Radio className="h-5 w-5" />, label: t('sidebar.channels') },
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import enChannels from './locales/en/channels.json';
import enSkills from './locales/en/skills.json';
import enCron from './locales/en/cron.json';
import enSetup from './locales/en/setup.json';
import enMemory from './locales/en/memory.json';

// ZH
import zhCommon from './locales/zh/common.json';
Expand All @@ -20,6 +21,7 @@ import zhChannels from './locales/zh/channels.json';
import zhSkills from './locales/zh/skills.json';
import zhCron from './locales/zh/cron.json';
import zhSetup from './locales/zh/setup.json';
import zhMemory from './locales/zh/memory.json';

// JA
import jaCommon from './locales/ja/common.json';
Expand All @@ -30,6 +32,7 @@ import jaChannels from './locales/ja/channels.json';
import jaSkills from './locales/ja/skills.json';
import jaCron from './locales/ja/cron.json';
import jaSetup from './locales/ja/setup.json';
import jaMemory from './locales/ja/memory.json';

export const SUPPORTED_LANGUAGES = [
{ code: 'en', label: 'English' },
Expand All @@ -49,6 +52,7 @@ const resources = {
skills: enSkills,
cron: enCron,
setup: enSetup,
memory: enMemory,
},
zh: {
common: zhCommon,
Expand All @@ -59,6 +63,7 @@ const resources = {
skills: zhSkills,
cron: zhCron,
setup: zhSetup,
memory: zhMemory,
},
ja: {
common: jaCommon,
Expand All @@ -69,6 +74,7 @@ const resources = {
skills: jaSkills,
cron: jaCron,
setup: jaSetup,
memory: jaMemory,
},
};

Expand All @@ -79,7 +85,7 @@ i18n
lng: 'en', // will be overridden by settings store
fallbackLng: 'en',
defaultNS: 'common',
ns: ['common', 'settings', 'dashboard', 'chat', 'channels', 'skills', 'cron', 'setup'],
ns: ['common', 'settings', 'dashboard', 'chat', 'channels', 'skills', 'cron', 'setup', 'memory'],
interpolation: {
escapeValue: false, // React already escapes
},
Expand Down
1 change: 1 addition & 0 deletions src/i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"sidebar": {
"chat": "Chat",
"newChat": "New Chat",
"memory": "Memory",
"cronTasks": "Cron Tasks",
"skills": "Skills",
"channels": "Channels",
Expand Down
22 changes: 22 additions & 0 deletions src/i18n/locales/en/memory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"title": "Memory",
"overview": "Memory Overview",
"overviewDescription": "View and manage AI assistant memory files",
"searchPlaceholder": "Search memory files...",
"files": "files",
"longTermMemory": "Long-term Memory",
"dailyLogs": "Daily Logs",
"noMemoryFiles": "No memory files",
"refresh": "Refresh",
"emptyFile": "File is empty",
"totalFiles": "Total Files",
"totalSize": "Total Size",
"longTermSize": "Long-term Memory Size",
"dailyLogCount": "Daily Log Count",
"aboutMemory": "About Memory System",
"aboutMemoryDesc1": "The memory system helps the AI assistant remember important information across sessions.",
"aboutMemoryDesc2": "There are two types of memory:",
"longTermInfo": "Refined, persistent memory storing important events and decisions.",
"dailyLogsInfo": "Raw daily work logs for review and distillation.",
"longTermDescription": "Refined long-term memory containing important events and decisions."
}
1 change: 1 addition & 0 deletions src/i18n/locales/ja/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"sidebar": {
"chat": "チャット",
"newChat": "新しいチャット",
"memory": "メモリ",
"cronTasks": "定期タスク",
"skills": "スキル",
"channels": "チャンネル",
Expand Down
22 changes: 22 additions & 0 deletions src/i18n/locales/ja/memory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"title": "メモリ",
"overview": "メモリ概要",
"overviewDescription": "AIアシスタントのメモリファイルを表示・管理",
"searchPlaceholder": "メモリファイルを検索...",
"files": "ファイル",
"longTermMemory": "長期メモリ",
"dailyLogs": "日次ログ",
"noMemoryFiles": "メモリファイルがありません",
"refresh": "更新",
"emptyFile": "ファイルは空です",
"totalFiles": "総ファイル数",
"totalSize": "合計サイズ",
"longTermSize": "長期メモリサイズ",
"dailyLogCount": "日次ログ数",
"aboutMemory": "メモリシステムについて",
"aboutMemoryDesc1": "メモリシステムはAIアシスタントがセッションをまたいで重要な情報を記憶するのに役立ちます。",
"aboutMemoryDesc2": "メモリには2種類あります:",
"longTermInfo": "重要なイベントと決定を保存する、洗練された永続的なメモリ。",
"dailyLogsInfo": "レビューと抽出のための生の日次作業ログ。",
"longTermDescription": "重要なイベントと決定を含む洗練された長期メモリ。"
}
1 change: 1 addition & 0 deletions src/i18n/locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"sidebar": {
"chat": "聊天",
"newChat": "新对话",
"memory": "记忆",
"cronTasks": "定时任务",
"skills": "技能",
"channels": "频道",
Expand Down
22 changes: 22 additions & 0 deletions src/i18n/locales/zh/memory.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"title": "记忆",
"overview": "记忆概览",
"overviewDescription": "查看和管理 AI 助手的记忆文件",
"searchPlaceholder": "搜索记忆文件...",
"files": "个文件",
"longTermMemory": "长期记忆",
"dailyLogs": "每日日志",
"noMemoryFiles": "暂无记忆文件",
"refresh": "刷新",
"emptyFile": "文件内容为空",
"totalFiles": "总文件数",
"totalSize": "总大小",
"longTermSize": "长期记忆大小",
"dailyLogCount": "每日日志数",
"aboutMemory": "关于记忆系统",
"aboutMemoryDesc1": "记忆系统帮助 AI 助手记住重要信息,实现跨会话的连续性。",
"aboutMemoryDesc2": "记忆分为两种类型:",
"longTermInfo": "经过提炼的、持久的记忆,存储重要事件和决策。",
"dailyLogsInfo": "每日工作的原始记录,用于回顾和提炼。",
"longTermDescription": "经过提炼的长期记忆,包含重要事件和决策。"
}
Loading