139 lines
5.5 KiB
TypeScript
139 lines
5.5 KiB
TypeScript
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
import { MindDB } from '@waggle/core';
|
|
import { Orchestrator, type ContextFrames } from '../src/orchestrator.js';
|
|
import { MockEmbedder } from '../../hive-mind-core/tests/mind/helpers/mock-embedder.js';
|
|
|
|
/**
|
|
* Tests the PromptAssembler wiring points added to Orchestrator:
|
|
* - `loadRecentContextFrames()`: typed counterpart to loadRecentContext
|
|
* - `recallMemory(query, limit, opts?)`: extended with profile / scoreFloor / tier
|
|
*
|
|
* Byte-identical-when-absent contract: recallMemory must behave exactly as
|
|
* the pre-extension version when opts is omitted.
|
|
*/
|
|
describe('Orchestrator PromptAssembler wiring', () => {
|
|
let db: MindDB;
|
|
let orchestrator: Orchestrator;
|
|
|
|
beforeEach(() => {
|
|
db = new MindDB(':memory:');
|
|
orchestrator = new Orchestrator({
|
|
db,
|
|
embedder: new MockEmbedder(),
|
|
});
|
|
});
|
|
|
|
afterEach(() => {
|
|
db.close();
|
|
});
|
|
|
|
describe('loadRecentContextFrames()', () => {
|
|
it('returns well-formed ContextFrames on an empty mind', () => {
|
|
const frames: ContextFrames = orchestrator.loadRecentContextFrames();
|
|
|
|
expect(frames.stateFrames).toEqual([]);
|
|
expect(frames.recentChanges).toEqual([]);
|
|
expect(frames.activeWork).toEqual([]);
|
|
expect(frames.keyEntities).toEqual([]);
|
|
expect(frames.personalPreferences).toEqual([]);
|
|
});
|
|
|
|
it('populates activeWork from AwarenessLayer items', () => {
|
|
orchestrator.getAwareness().add('task', 'Ship PromptAssembler', 10);
|
|
orchestrator.getAwareness().add('pending', 'Verify OpenRouter slugs', 5);
|
|
|
|
const frames = orchestrator.loadRecentContextFrames();
|
|
|
|
expect(frames.activeWork).toHaveLength(2);
|
|
expect(frames.activeWork[0].content).toMatch(/Ship PromptAssembler|Verify OpenRouter/);
|
|
expect(frames.activeWork[0].priority).toBeGreaterThanOrEqual(0);
|
|
expect(['task', 'pending', 'action', 'flag']).toContain(frames.activeWork[0].category);
|
|
});
|
|
|
|
it('pulls personal preferences even when no workspace is active', async () => {
|
|
await orchestrator.executeTool('save_memory', {
|
|
content: 'User preference: prefers concise replies',
|
|
importance: 'normal',
|
|
});
|
|
|
|
const frames = orchestrator.loadRecentContextFrames();
|
|
expect(frames.personalPreferences.some(p => p.includes('User preference'))).toBe(true);
|
|
});
|
|
|
|
it('splits frames by frame_type: I → stateFrames, P/B → recentChanges', () => {
|
|
const frames = orchestrator.getFrames();
|
|
const session = orchestrator.getSessions().ensureActive();
|
|
const iFrame = frames.createIFrame(session.gop_id, 'Identity snapshot', 'important');
|
|
frames.createPFrame(session.gop_id, 'Delta update', iFrame.id, 'normal');
|
|
|
|
const ctx = orchestrator.loadRecentContextFrames();
|
|
|
|
expect(ctx.stateFrames.length).toBeGreaterThanOrEqual(1);
|
|
expect(ctx.stateFrames.every(f => f.frame_type === 'I')).toBe(true);
|
|
expect(ctx.recentChanges.some(f => f.frame_type === 'P')).toBe(true);
|
|
});
|
|
|
|
it('respects the limit argument', () => {
|
|
const frames = orchestrator.getFrames();
|
|
const session = orchestrator.getSessions().ensureActive();
|
|
for (let i = 0; i < 20; i++) {
|
|
frames.createIFrame(session.gop_id, `State frame ${i}`, 'normal');
|
|
}
|
|
|
|
const ctx = orchestrator.loadRecentContextFrames(3);
|
|
const total = ctx.stateFrames.length + ctx.recentChanges.length;
|
|
expect(total).toBeLessThanOrEqual(3);
|
|
});
|
|
});
|
|
|
|
describe('recallMemory() byte-identical when opts absent', () => {
|
|
it('returns empty result structure on empty mind', async () => {
|
|
const result = await orchestrator.recallMemory('anything');
|
|
expect(result.count).toBe(0);
|
|
expect(result.text).toBe('');
|
|
expect(result.recalled).toEqual([]);
|
|
});
|
|
|
|
it('default-path shape unchanged (text starts with # Recalled Memories when hits exist)', async () => {
|
|
const frames = orchestrator.getFrames();
|
|
const session = orchestrator.getSessions().ensureActive();
|
|
frames.createIFrame(session.gop_id, 'The KVARK license boundary is non-negotiable', 'important');
|
|
|
|
// Seed the search index
|
|
await orchestrator.getSearch().indexFrame(
|
|
(await new Promise<number>(resolve => {
|
|
const raw = db.getDatabase();
|
|
const row = raw.prepare('SELECT id FROM memory_frames ORDER BY id DESC LIMIT 1').get() as { id: number };
|
|
resolve(row.id);
|
|
})),
|
|
'The KVARK license boundary is non-negotiable',
|
|
);
|
|
|
|
const result = await orchestrator.recallMemory('license boundary');
|
|
// Either hit or miss — test just checks shape stability
|
|
expect(typeof result.text).toBe('string');
|
|
expect(typeof result.count).toBe('number');
|
|
expect(Array.isArray(result.recalled)).toBe(true);
|
|
});
|
|
});
|
|
|
|
describe('recallMemory() with RecallOptions', () => {
|
|
it('accepts profile override without type errors', async () => {
|
|
const result = await orchestrator.recallMemory('test', 5, { profile: 'recent' });
|
|
expect(result).toBeDefined();
|
|
expect(typeof result.count).toBe('number');
|
|
});
|
|
|
|
it('accepts scoreFloor (filters when provided)', async () => {
|
|
const result = await orchestrator.recallMemory('test', 5, { scoreFloor: 0.99 });
|
|
// With a very high score floor and no seeded matches, count should be 0
|
|
expect(result.count).toBe(0);
|
|
});
|
|
|
|
it('accepts tier hint (recorded, not acted upon in recallMemory)', async () => {
|
|
const result = await orchestrator.recallMemory('test', 5, { tier: 'small' });
|
|
expect(result).toBeDefined();
|
|
});
|
|
});
|
|
});
|