src / tools / memoryTools.ts
import { readFile, writeFile, readdir } from "fs/promises";
import { existsSync, readdirSync } from "fs";
import { join, dirname } from "path";
import { SESSIONS_DIR, RAW_SESSIONS_DIR, AWARENESS_FILE, ensureDirectory, resolveInsideBase } from "./constants";
function getNextSessionNumber(): number {
if (!existsSync(SESSIONS_DIR)) return 1;
const files = readdirSync(SESSIONS_DIR);
const nums: number[] = files
.filter((f: string) => f.startsWith("session") && f.endsWith(".json"))
.map((f: string) => parseInt(f.replace("session", "").replace(".json", ""), 10))
.filter((n: number) => !isNaN(n));
return nums.length ? Math.max(...nums) + 1 : 1;
}
export async function executeMemoryTool(toolName: string, args: any): Promise<any> {
switch (toolName) {
case "saveSession":
case "save_session":
case "End_session": {
await ensureDirectory(SESSIONS_DIR);
await ensureDirectory(RAW_SESSIONS_DIR);
const nextNum = getNextSessionNumber();
const sessionId = `session${String(nextNum).padStart(2, "0")}`;
const title = args?.title || args?.session_full_path || "Session";
const main_idea = args?.main_idea || "";
const summary = args?.summary || "";
const tags = Array.isArray(args?.tags) ? args.tags : (toolName === "End_session" ? ["end_session"] : []);
const raw_content = args?.raw_content || JSON.stringify(args, null, 2);
const jsonContent = {
session_id: sessionId,
title,
main_idea,
sub_ideas: args?.sub_ideas || [],
summary,
tags,
timestamp: new Date().toISOString(),
raw_file: `raw_sessions/${sessionId}.md`,
};
const jsonPath = join(SESSIONS_DIR, `${sessionId}.json`);
const rawPath = join(RAW_SESSIONS_DIR, `${sessionId}.md`);
await writeFile(jsonPath, JSON.stringify(jsonContent, null, 2), "utf-8");
await writeFile(rawPath, raw_content, "utf-8");
return {
success: true,
message: `Session ${sessionId} saved successfully.`,
session_id: sessionId,
};
}
case "getMemory":
case "get_memory":
case "support_me": {
const query = toolName === "support_me" && Array.isArray(args?.tags) ? args.tags.join(" ") : (args?.query || "");
const tag = args?.tag;
const limit = args?.limit ? Number(args.limit) : 5;
const threshold = args?.threshold ? Number(args.threshold) : 0.2;
if (!existsSync(SESSIONS_DIR)) {
return { success: true, results: [], totalFound: 0, returned: 0 };
}
const files = (await readdir(SESSIONS_DIR)).filter(f => f.endsWith(".json"));
const results: any[] = [];
for (const file of files) {
const content = await readFile(join(SESSIONS_DIR, file), "utf-8");
const session = JSON.parse(content);
let relevance = 0;
if (query) {
const q = query.toLowerCase();
if (session.title?.toLowerCase().includes(q)) relevance += 0.4;
if (session.main_idea?.toLowerCase().includes(q)) relevance += 0.3;
if (session.summary?.toLowerCase().includes(q)) relevance += 0.2;
if (session.sub_ideas?.some((s: string) => s.toLowerCase().includes(q))) relevance += 0.1;
if (session.tags?.some((t: string) => t.toLowerCase().includes(q))) relevance += 0.3;
}
if (tag && session.tags?.includes(tag)) {
relevance += 0.5;
}
relevance = Math.min(relevance, 1);
if (relevance >= threshold || (!query && !tag)) {
results.push({
session_id: session.session_id,
title: session.title,
summary: session.summary,
tags: session.tags,
timestamp: session.timestamp,
relevance,
raw_file: session.raw_file,
});
}
}
results.sort((a, b) => b.relevance - a.relevance);
const returned = results.slice(0, limit);
return { success: true, results: returned, totalFound: results.length };
}
case "readRawSession":
case "read_raw_session": {
const rawPath = resolveInsideBase(args?.raw_file || args?.session_id || "");
if (!existsSync(rawPath)) return { success: false, error: `Raw file not found: ${args?.raw_file}` };
const content = await readFile(rawPath, "utf-8");
return { success: true, path: rawPath, content };
}
case "semanticMemory": {
const query = args?.query || "";
const limit = args?.limit ? Number(args.limit) : 2;
try {
const res = await fetch(`http://127.0.0.1:5077/search`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, limit })
});
const data = (await res.json()) as any;
return {
success: true,
results: data.results,
system_note: `These are semantic memory matches. Use them to ground your response.`
};
} catch (e: any) {
return { success: false, error: `Memory server at port 5077 unreachable: ${e.message}` };
}
}
case "markAwareness": {
const insight = args?.insight || "";
const tags = Array.isArray(args?.tags) ? args.tags : [];
const context = args?.context || "";
const entry = { id: `aw_${Date.now()}`, timestamp: new Date().toISOString(), insight, tags, context, children: [] };
let db = { entries: [] as any[] };
try {
const existing = await readFile(AWARENESS_FILE, "utf-8");
db = JSON.parse(existing);
} catch {}
db.entries.push(entry);
await ensureDirectory(dirname(AWARENESS_FILE));
await writeFile(AWARENESS_FILE, JSON.stringify(db, null, 2));
return { success: true, system_note: `Awareness insight recorded successfully.` };
}
case "recallAwareness": {
const query = args?.query || "";
const tags = Array.isArray(args?.tags) ? args.tags : [];
const limit = args?.limit ? Number(args.limit) : 5;
let db = { entries: [] as any[] };
try {
const existing = await readFile(AWARENESS_FILE, "utf-8");
db = JSON.parse(existing);
} catch {
return { success: true, entries: [], system_note: `No awareness records found.` };
}
let entries = db.entries;
if (query) {
const q = query.toLowerCase();
entries = entries.filter(e => e.insight.toLowerCase().includes(q) || e.context.toLowerCase().includes(q) || e.tags.some((t: string) => t.toLowerCase().includes(q)));
}
if (tags.length) {
entries = entries.filter(e => tags.some(t => e.tags.includes(t)));
}
return { success: true, entries: entries.slice(0, limit) };
}
default:
return null;
}
}
src / tools / memoryTools.ts
import { readFile, writeFile, readdir } from "fs/promises";
import { existsSync, readdirSync } from "fs";
import { join, dirname } from "path";
import { SESSIONS_DIR, RAW_SESSIONS_DIR, AWARENESS_FILE, ensureDirectory, resolveInsideBase } from "./constants";
function getNextSessionNumber(): number {
if (!existsSync(SESSIONS_DIR)) return 1;
const files = readdirSync(SESSIONS_DIR);
const nums: number[] = files
.filter((f: string) => f.startsWith("session") && f.endsWith(".json"))
.map((f: string) => parseInt(f.replace("session", "").replace(".json", ""), 10))
.filter((n: number) => !isNaN(n));
return nums.length ? Math.max(...nums) + 1 : 1;
}
export async function executeMemoryTool(toolName: string, args: any): Promise<any> {
switch (toolName) {
case "saveSession":
case "save_session":
case "End_session": {
await ensureDirectory(SESSIONS_DIR);
await ensureDirectory(RAW_SESSIONS_DIR);
const nextNum = getNextSessionNumber();
const sessionId = `session${String(nextNum).padStart(2, "0")}`;
const title = args?.title || args?.session_full_path || "Session";
const main_idea = args?.main_idea || "";
const summary = args?.summary || "";
const tags = Array.isArray(args?.tags) ? args.tags : (toolName === "End_session" ? ["end_session"] : []);
const raw_content = args?.raw_content || JSON.stringify(args, null, 2);
const jsonContent = {
session_id: sessionId,
title,
main_idea,
sub_ideas: args?.sub_ideas || [],
summary,
tags,
timestamp: new Date().toISOString(),
raw_file: `raw_sessions/${sessionId}.md`,
};
const jsonPath = join(SESSIONS_DIR, `${sessionId}.json`);
const rawPath = join(RAW_SESSIONS_DIR, `${sessionId}.md`);
await writeFile(jsonPath, JSON.stringify(jsonContent, null, 2), "utf-8");
await writeFile(rawPath, raw_content, "utf-8");
return {
success: true,
message: `Session ${sessionId} saved successfully.`,
session_id: sessionId,
};
}
case "getMemory":
case "get_memory":
case "support_me": {
const query = toolName === "support_me" && Array.isArray(args?.tags) ? args.tags.join(" ") : (args?.query || "");
const tag = args?.tag;
const limit = args?.limit ? Number(args.limit) : 5;
const threshold = args?.threshold ? Number(args.threshold) : 0.2;
if (!existsSync(SESSIONS_DIR)) {
return { success: true, results: [], totalFound: 0, returned: 0 };
}
const files = (await readdir(SESSIONS_DIR)).filter(f => f.endsWith(".json"));
const results: any[] = [];
for (const file of files) {
const content = await readFile(join(SESSIONS_DIR, file), "utf-8");
const session = JSON.parse(content);
let relevance = 0;
if (query) {
const q = query.toLowerCase();
if (session.title?.toLowerCase().includes(q)) relevance += 0.4;
if (session.main_idea?.toLowerCase().includes(q)) relevance += 0.3;
if (session.summary?.toLowerCase().includes(q)) relevance += 0.2;
if (session.sub_ideas?.some((s: string) => s.toLowerCase().includes(q))) relevance += 0.1;
if (session.tags?.some((t: string) => t.toLowerCase().includes(q))) relevance += 0.3;
}
if (tag && session.tags?.includes(tag)) {
relevance += 0.5;
}
relevance = Math.min(relevance, 1);
if (relevance >= threshold || (!query && !tag)) {
results.push({
session_id: session.session_id,
title: session.title,
summary: session.summary,
tags: session.tags,
timestamp: session.timestamp,
relevance,
raw_file: session.raw_file,
});
}
}
results.sort((a, b) => b.relevance - a.relevance);
const returned = results.slice(0, limit);
return { success: true, results: returned, totalFound: results.length };
}
case "readRawSession":
case "read_raw_session": {
const rawPath = resolveInsideBase(args?.raw_file || args?.session_id || "");
if (!existsSync(rawPath)) return { success: false, error: `Raw file not found: ${args?.raw_file}` };
const content = await readFile(rawPath, "utf-8");
return { success: true, path: rawPath, content };
}
case "semanticMemory": {
const query = args?.query || "";
const limit = args?.limit ? Number(args.limit) : 2;
try {
const res = await fetch(`http://127.0.0.1:5077/search`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ query, limit })
});
const data = (await res.json()) as any;
return {
success: true,
results: data.results,
system_note: `These are semantic memory matches. Use them to ground your response.`
};
} catch (e: any) {
return { success: false, error: `Memory server at port 5077 unreachable: ${e.message}` };
}
}
case "markAwareness": {
const insight = args?.insight || "";
const tags = Array.isArray(args?.tags) ? args.tags : [];
const context = args?.context || "";
const entry = { id: `aw_${Date.now()}`, timestamp: new Date().toISOString(), insight, tags, context, children: [] };
let db = { entries: [] as any[] };
try {
const existing = await readFile(AWARENESS_FILE, "utf-8");
db = JSON.parse(existing);
} catch {}
db.entries.push(entry);
await ensureDirectory(dirname(AWARENESS_FILE));
await writeFile(AWARENESS_FILE, JSON.stringify(db, null, 2));
return { success: true, system_note: `Awareness insight recorded successfully.` };
}
case "recallAwareness": {
const query = args?.query || "";
const tags = Array.isArray(args?.tags) ? args.tags : [];
const limit = args?.limit ? Number(args.limit) : 5;
let db = { entries: [] as any[] };
try {
const existing = await readFile(AWARENESS_FILE, "utf-8");
db = JSON.parse(existing);
} catch {
return { success: true, entries: [], system_note: `No awareness records found.` };
}
let entries = db.entries;
if (query) {
const q = query.toLowerCase();
entries = entries.filter(e => e.insight.toLowerCase().includes(q) || e.context.toLowerCase().includes(q) || e.tags.some((t: string) => t.toLowerCase().includes(q)));
}
if (tags.length) {
entries = entries.filter(e => tags.some(t => e.tags.includes(t)));
}
return { success: true, entries: entries.slice(0, limit) };
}
default:
return null;
}
}