Files
CodingInCarhartts 6ad6748e6e feat: add session memory tools with semantic search and logging
Adds comprehensive session memory management with the following tools:
- start_logging_session: Begin logging a session with optional name
- log_message: Log user/agent messages to active sessions
- search_session: Semantic search within a specific session
- search_all_sessions: Semantic search across all logged sessions
- list_sessions: List all sessions with optional active filter
- get_session: Get session details and messages
- end_session: End session with optional summary
Database schema includes:
- sessions table with name, timestamps, summary, and active status
- session_messages table with role, content, and creation time
- Proper indexes for performance
- Foreign key relationships
All tools support semantic search using the existing embedding model.
2025-12-14 22:19:18 -08:00

158 lines
5.5 KiB
TypeScript

/**
* Session Service Tests
*
* Uses isolated test database via OPENCODE_PK_DATA_DIR environment variable.
* Uses dynamic imports to ensure env var is set BEFORE module loads.
*/
import { describe, test, expect, beforeAll, afterAll } from "bun:test";
import { mkdtempSync, rmSync } from "fs";
import { join } from "path";
import { tmpdir } from "os";
// Set up isolated test database BEFORE any imports
const testDir = mkdtempSync(join(tmpdir(), "opencode-pk-test-"));
process.env.OPENCODE_PK_DATA_DIR = testDir;
// Types for the dynamically imported module
type DatabaseModule = typeof import("../../src/database/index");
let db: DatabaseModule;
describe("Session Database Functions", () => {
beforeAll(async () => {
// Dynamic import AFTER env var is set
db = await import("../../src/database/index");
db.initDatabase();
});
afterAll(() => {
// Clean up
db.closeDatabase();
rmSync(testDir, { recursive: true, force: true });
});
describe("createSession", () => {
test("creates a session with name", () => {
const id = db.createSession("Test Session");
expect(id).toBeGreaterThan(0);
const session = db.getSession(id);
expect(session).not.toBeNull();
expect(session!.name).toBe("Test Session");
expect(session!.is_active).toBe(true);
expect(session!.ended_at).toBeNull();
});
test("creates a session without name", () => {
const id = db.createSession();
const session = db.getSession(id);
expect(session).not.toBeNull();
expect(session!.name).toBeNull();
expect(session!.is_active).toBe(true);
});
});
describe("getActiveSession", () => {
test("returns most recent active session", () => {
db.createSession("First");
db.createSession("Second");
const active = db.getActiveSession();
expect(active).not.toBeNull();
// Check it's the most recent by name (ID ordering can vary)
expect(active!.name).toBe("Second");
});
});
describe("endSession", () => {
test("ends a session with summary", () => {
const id = db.createSession("To End");
const success = db.endSession(id, "Completed successfully");
expect(success).toBe(true);
const session = db.getSession(id);
expect(session).not.toBeNull();
expect(session!.is_active).toBe(false);
expect(session!.ended_at).not.toBeNull();
expect(session!.summary).toBe("Completed successfully");
});
test("returns false for non-existent session", () => {
const success = db.endSession(99999);
expect(success).toBe(false);
});
});
describe("saveSessionMessage", () => {
test("saves user message", () => {
const sessionId = db.createSession("Message Test");
const msgId = db.saveSessionMessage(sessionId, "user", "Hello, world!");
expect(msgId).toBeGreaterThan(0);
const messages = db.getSessionMessages(sessionId);
expect(messages.length).toBe(1);
expect(messages[0].role).toBe("user");
expect(messages[0].content).toBe("Hello, world!");
});
test("saves agent message", () => {
const sessionId = db.createSession("Agent Message Test");
db.saveSessionMessage(sessionId, "user", "Question");
const msgId = db.saveSessionMessage(sessionId, "agent", "Answer");
const messages = db.getSessionMessages(sessionId);
expect(messages.length).toBe(2);
expect(messages[1].role).toBe("agent");
expect(messages[1].content).toBe("Answer");
});
});
describe("getSessionMessageCount", () => {
test("returns correct count", () => {
const sessionId = db.createSession("Count Test");
db.saveSessionMessage(sessionId, "user", "Message 1");
db.saveSessionMessage(sessionId, "agent", "Message 2");
db.saveSessionMessage(sessionId, "user", "Message 3");
const count = db.getSessionMessageCount(sessionId);
expect(count).toBe(3);
});
test("returns 0 for empty session", () => {
const sessionId = db.createSession("Empty");
const count = db.getSessionMessageCount(sessionId);
expect(count).toBe(0);
});
});
describe("listSessions", () => {
test("lists all sessions", () => {
// Create a few sessions for testing
db.createSession("List Test 1");
db.createSession("List Test 2");
const sessions = db.listSessions({ limit: 100 });
expect(sessions.length).toBeGreaterThanOrEqual(2);
});
test("respects limit", () => {
const sessions = db.listSessions({ limit: 2 });
expect(sessions.length).toBeLessThanOrEqual(2);
});
test("filters active only", () => {
const activeId = db.createSession("Active Only Test");
const inactiveId = db.createSession("Will End");
db.endSession(inactiveId);
const activeSessions = db.listSessions({ activeOnly: true });
const activeIds = activeSessions.map(s => s.id);
expect(activeIds).toContain(activeId);
expect(activeIds).not.toContain(inactiveId);
});
});
});