moving
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled

This commit is contained in:
Oleg Maslov
2026-09-02 10:10:29 +02:00
commit 0c3e2ead3b
3841 changed files with 970576 additions and 0 deletions

View File

@@ -0,0 +1,272 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fs from 'node:fs';
import * as path from 'node:path';
import * as os from 'node:os';
import { MindDB, WorkspaceManager, AwarenessLayer } from '@waggle/core';
import {
generateMorningBriefing,
checkStaleWorkspaces,
checkPendingTasks,
suggestCapabilities,
type ProactiveContext,
} from '../src/local/proactive-handlers.js';
describe('Proactive Handlers', () => {
let tmpDir: string;
let wsManager: WorkspaceManager;
const mindDbs = new Map<string, MindDB>();
function makeContext(): ProactiveContext {
return {
dataDir: tmpDir,
workspaceManager: wsManager,
getWorkspaceMindDb: (workspaceId: string) => {
const existing = mindDbs.get(workspaceId);
if (existing) return existing;
const mindPath = path.join(tmpDir, 'workspaces', workspaceId, 'workspace.mind');
if (!fs.existsSync(mindPath)) return null;
try {
const db = new MindDB(mindPath);
mindDbs.set(workspaceId, db);
return db;
} catch {
return null;
}
},
};
}
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'waggle-proactive-'));
wsManager = new WorkspaceManager(tmpDir);
});
afterEach(() => {
for (const [, db] of mindDbs) {
try { db.close(); } catch { /* already closed */ }
}
mindDbs.clear();
fs.rmSync(tmpDir, { recursive: true, force: true });
});
// ── Morning Briefing ─────────────────────────────────────────────
describe('generateMorningBriefing', () => {
it('returns null when no workspaces exist', () => {
const ctx = makeContext();
const result = generateMorningBriefing(ctx);
expect(result).toBeNull();
});
it('returns null when no pending items and no stale workspaces', () => {
// Create a workspace with a recent session file
const ws = wsManager.create({ name: 'Active Project', group: 'Work' });
const sessionsDir = path.join(tmpDir, 'workspaces', ws.id, 'sessions');
fs.mkdirSync(sessionsDir, { recursive: true });
fs.writeFileSync(path.join(sessionsDir, '2026-03-19.jsonl'), '{"role":"user","content":"hello"}\n');
const ctx = makeContext();
const result = generateMorningBriefing(ctx);
expect(result).toBeNull();
});
it('includes workspace summaries when pending items exist', () => {
const ws = wsManager.create({ name: 'My Research', group: 'Work' });
// Initialize the mind DB (write actual data so MindDB opens it properly)
const mindPath = path.join(tmpDir, 'workspaces', ws.id, 'workspace.mind');
const db = new MindDB(mindPath);
const awareness = new AwarenessLayer(db);
awareness.add('task', 'Review paper draft', 1);
awareness.add('pending', 'Waiting for feedback', 0);
mindDbs.set(ws.id, db);
const ctx = makeContext();
const result = generateMorningBriefing(ctx);
expect(result).not.toBeNull();
expect(result!.type).toBe('morning_briefing');
expect(result!.title).toContain('morning');
expect(result!.body).toContain('2 pending');
expect(result!.body).toContain('My Research');
});
it('reports stale workspaces in briefing', () => {
const ws = wsManager.create({ name: 'Old Project', group: 'Archive' });
// Create a session file with old modification time
const sessionsDir = path.join(tmpDir, 'workspaces', ws.id, 'sessions');
fs.mkdirSync(sessionsDir, { recursive: true });
const sessionFile = path.join(sessionsDir, '2026-02-01.jsonl');
fs.writeFileSync(sessionFile, '{"role":"user","content":"test"}\n');
// Set mtime to 30 days ago
const oldTime = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000);
fs.utimesSync(sessionFile, oldTime, oldTime);
const ctx = makeContext();
const result = generateMorningBriefing(ctx);
expect(result).not.toBeNull();
expect(result!.body).toContain('not visited in 14+ days');
});
});
// ── Stale Workspace Check ────────────────────────────────────────
describe('checkStaleWorkspaces', () => {
it('returns empty array when no workspaces', () => {
const ctx = makeContext();
const result = checkStaleWorkspaces(ctx);
expect(result).toEqual([]);
});
it('detects stale workspaces based on session activity', () => {
const ws = wsManager.create({ name: 'Forgotten Project', group: 'Old' });
// Create a session file with old modification time
const sessionsDir = path.join(tmpDir, 'workspaces', ws.id, 'sessions');
fs.mkdirSync(sessionsDir, { recursive: true });
const sessionFile = path.join(sessionsDir, '2026-01-15.jsonl');
fs.writeFileSync(sessionFile, '{"role":"user","content":"test"}\n');
const oldTime = new Date(Date.now() - 20 * 24 * 60 * 60 * 1000);
fs.utimesSync(sessionFile, oldTime, oldTime);
const ctx = makeContext();
const result = checkStaleWorkspaces(ctx);
expect(result.length).toBe(1);
expect(result[0].type).toBe('stale_workspace');
expect(result[0].title).toContain('Forgotten Project');
expect(result[0].workspaceId).toBe(ws.id);
});
it('skips recently active workspaces', () => {
const ws = wsManager.create({ name: 'Active Project', group: 'Work' });
const sessionsDir = path.join(tmpDir, 'workspaces', ws.id, 'sessions');
fs.mkdirSync(sessionsDir, { recursive: true });
fs.writeFileSync(path.join(sessionsDir, '2026-03-19.jsonl'), '{"role":"user","content":"hello"}\n');
const ctx = makeContext();
const result = checkStaleWorkspaces(ctx);
expect(result).toEqual([]);
});
it('uses creation date when no sessions exist', () => {
// Create workspace with backdated creation
const ws = wsManager.create({ name: 'Empty Old Workspace', group: 'Test' });
// Backdate the workspace config
const configPath = path.join(tmpDir, 'workspaces', ws.id, 'workspace.json');
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
config.created = new Date(Date.now() - 30 * 24 * 60 * 60 * 1000).toISOString();
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
const ctx = makeContext();
const result = checkStaleWorkspaces(ctx);
expect(result.length).toBe(1);
expect(result[0].title).toContain('Empty Old Workspace');
});
});
// ── Task Reminder ────────────────────────────────────────────────
describe('checkPendingTasks', () => {
it('returns empty array when no workspaces', () => {
const ctx = makeContext();
const result = checkPendingTasks(ctx);
expect(result).toEqual([]);
});
it('aggregates pending tasks across workspaces', () => {
const ws1 = wsManager.create({ name: 'Project A', group: 'Work' });
const ws2 = wsManager.create({ name: 'Project B', group: 'Work' });
// Add awareness items to ws1
const mindPath1 = path.join(tmpDir, 'workspaces', ws1.id, 'workspace.mind');
const db1 = new MindDB(mindPath1);
const awareness1 = new AwarenessLayer(db1);
awareness1.add('task', 'Fix bug #123', 1);
awareness1.add('task', 'Write tests', 0);
mindDbs.set(ws1.id, db1);
// Add awareness items to ws2
const mindPath2 = path.join(tmpDir, 'workspaces', ws2.id, 'workspace.mind');
const db2 = new MindDB(mindPath2);
const awareness2 = new AwarenessLayer(db2);
awareness2.add('pending', 'Waiting for review', 0);
mindDbs.set(ws2.id, db2);
const ctx = makeContext();
const result = checkPendingTasks(ctx);
expect(result.length).toBe(2);
const projectA = result.find(r => r.workspaceId === ws1.id);
const projectB = result.find(r => r.workspaceId === ws2.id);
expect(projectA).toBeDefined();
expect(projectA!.title).toContain('2 pending');
expect(projectB).toBeDefined();
expect(projectB!.title).toContain('1 pending');
});
it('skips workspaces with no pending items', () => {
wsManager.create({ name: 'Clean Workspace', group: 'Work' });
const ctx = makeContext();
const result = checkPendingTasks(ctx);
expect(result).toEqual([]);
});
it('sets high priority when many pending items', () => {
const ws = wsManager.create({ name: 'Busy Project', group: 'Work' });
const mindPath = path.join(tmpDir, 'workspaces', ws.id, 'workspace.mind');
const db = new MindDB(mindPath);
const awareness = new AwarenessLayer(db);
for (let i = 0; i < 5; i++) {
awareness.add('task', `Task ${i}`, 1);
}
mindDbs.set(ws.id, db);
const ctx = makeContext();
const result = checkPendingTasks(ctx);
expect(result.length).toBe(1);
expect(result[0].priority).toBe('high');
});
});
// ── Capability Suggestion ────────────────────────────────────────
describe('suggestCapabilities', () => {
it('returns null when no workspaces and capabilities installed', () => {
// Create a fake skills directory to simulate installed capabilities
fs.mkdirSync(path.join(tmpDir, 'skills', 'some-skill'), { recursive: true });
const ctx = makeContext();
const result = suggestCapabilities(ctx);
expect(result).toBeNull();
});
it('suggests capability packs when none are installed', () => {
const ctx = makeContext();
const result = suggestCapabilities(ctx);
expect(result).not.toBeNull();
expect(result!.type).toBe('capability_suggestion');
expect(result!.title).toContain('capability packs');
expect(result!.actionUrl).toBe('/skills');
});
it('returns null when capabilities installed and low memory count', () => {
// Create skills dir (simulates installed capabilities)
fs.mkdirSync(path.join(tmpDir, 'skills', 'research-skill'), { recursive: true });
// Create a workspace with few frames
const ws = wsManager.create({ name: 'Small Project', group: 'Test' });
const mindPath = path.join(tmpDir, 'workspaces', ws.id, 'workspace.mind');
const db = new MindDB(mindPath);
mindDbs.set(ws.id, db);
const ctx = makeContext();
const result = suggestCapabilities(ctx);
expect(result).toBeNull();
});
});
});