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,61 @@
import { describe, it, expect, beforeEach } from 'vitest';
import { McpManager, type McpServerConfig } from '../../sidecar/src/mcp-manager.js';
describe('MCP Manager', () => {
let manager: McpManager;
beforeEach(() => {
manager = new McpManager();
});
it('starts with no servers', () => {
expect(manager.listServers()).toEqual([]);
});
it('adds a server config', () => {
const config: McpServerConfig = {
id: 'filesystem',
name: 'File System',
command: 'npx',
args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp'],
};
manager.addServer(config);
expect(manager.listServers()).toHaveLength(1);
expect(manager.listServers()[0].id).toBe('filesystem');
});
it('removes a server config', () => {
manager.addServer({ id: 'test', name: 'Test', command: 'echo', args: ['hello'] });
expect(manager.listServers()).toHaveLength(1);
manager.removeServer('test');
expect(manager.listServers()).toHaveLength(0);
});
it('prevents duplicate server IDs', () => {
manager.addServer({ id: 'dup', name: 'First', command: 'a', args: [] });
expect(() => {
manager.addServer({ id: 'dup', name: 'Second', command: 'b', args: [] });
}).toThrow('already exists');
});
it('gets server by ID', () => {
manager.addServer({ id: 'fs', name: 'FS', command: 'npx', args: [] });
const server = manager.getServer('fs');
expect(server?.name).toBe('FS');
});
it('returns undefined for unknown server', () => {
expect(manager.getServer('nope')).toBeUndefined();
});
it('serializes and deserializes config', () => {
manager.addServer({ id: 'a', name: 'A', command: 'x', args: ['1'] });
manager.addServer({ id: 'b', name: 'B', command: 'y', args: ['2'], env: { FOO: 'bar' } });
const json = manager.toJSON();
const restored = McpManager.fromJSON(json);
expect(restored.listServers()).toHaveLength(2);
expect(restored.getServer('b')?.env).toEqual({ FOO: 'bar' });
});
});

View File

@@ -0,0 +1,107 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { RpcHandler } from '../../sidecar/src/rpc-handler.js';
import { MindDB, IdentityLayer } from '@waggle/core';
import fs from 'fs';
import path from 'path';
import os from 'os';
describe('RPC Handler', () => {
let handler: RpcHandler;
let dbPath: string;
let db: MindDB;
beforeEach(() => {
dbPath = path.join(os.tmpdir(), `waggle-rpc-test-${Date.now()}.mind`);
db = new MindDB(dbPath);
handler = new RpcHandler(db);
});
afterEach(() => {
db.close();
try {
if (fs.existsSync(dbPath)) fs.unlinkSync(dbPath);
} catch {
// Windows file lock
}
});
it('handles ping method', async () => {
const result = await handler.handle({
jsonrpc: '2.0',
method: 'ping',
id: 1,
});
expect(result).toEqual({
jsonrpc: '2.0',
result: { status: 'ok' },
id: 1,
});
});
it('handles mind.getIdentity', async () => {
const identity = new IdentityLayer(db);
identity.create({
name: 'TestBot',
role: 'assistant',
department: '',
personality: '',
capabilities: '',
system_prompt: '',
});
const result = await handler.handle({
jsonrpc: '2.0',
method: 'mind.getIdentity',
id: 2,
});
expect(result.result).toContain('TestBot');
});
it('handles mind.getAwareness', async () => {
const result = await handler.handle({
jsonrpc: '2.0',
method: 'mind.getAwareness',
id: 3,
});
expect(result.result).toBeDefined();
});
it('returns error for unknown method', async () => {
const result = await handler.handle({
jsonrpc: '2.0',
method: 'nonexistent',
id: 4,
});
expect(result.error).toBeDefined();
expect(result.error!.code).toBe(-32601);
});
it('handles chat.send with stub response', async () => {
const result = await handler.handle({
jsonrpc: '2.0',
method: 'chat.send',
params: { message: 'hello' },
id: 5,
});
expect(result.result).toBeDefined();
});
it('handles settings.get', async () => {
const result = await handler.handle({
jsonrpc: '2.0',
method: 'settings.get',
id: 6,
});
expect(result.result).toBeDefined();
});
it('handles settings.set', async () => {
const result = await handler.handle({
jsonrpc: '2.0',
method: 'settings.set',
params: { key: 'model', value: 'claude-sonnet-4-6' },
id: 7,
});
expect(result.result).toEqual({ success: true });
});
});

View File

@@ -0,0 +1,120 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { SkillLoader, type Skill } from '../../sidecar/src/skill-loader.js';
import fs from 'fs';
import path from 'path';
import os from 'os';
describe('Skill Loader', () => {
let skillDir: string;
let loader: SkillLoader;
beforeEach(() => {
skillDir = path.join(os.tmpdir(), `waggle-skills-${Date.now()}`);
fs.mkdirSync(skillDir, { recursive: true });
loader = new SkillLoader([skillDir]);
});
afterEach(() => {
fs.rmSync(skillDir, { recursive: true, force: true });
});
it('discovers skills from SKILL.md files', () => {
const skillPath = path.join(skillDir, 'summarize');
fs.mkdirSync(skillPath, { recursive: true });
fs.writeFileSync(path.join(skillPath, 'SKILL.md'), `---
name: summarize
description: Summarize text into key points
model: haiku
---
You are a summarization expert. Given text, produce a concise summary.
`);
const skills = loader.discover();
expect(skills).toHaveLength(1);
expect(skills[0].name).toBe('summarize');
expect(skills[0].description).toBe('Summarize text into key points');
expect(skills[0].model).toBe('haiku');
expect(skills[0].prompt).toContain('summarization expert');
});
it('discovers skills from multiple directories', () => {
const dir2 = path.join(os.tmpdir(), `waggle-skills2-${Date.now()}`);
fs.mkdirSync(dir2, { recursive: true });
fs.mkdirSync(path.join(skillDir, 'skill-a'), { recursive: true });
fs.writeFileSync(path.join(skillDir, 'skill-a', 'SKILL.md'), `---
name: skill-a
description: First skill
---
Prompt A
`);
fs.mkdirSync(path.join(dir2, 'skill-b'), { recursive: true });
fs.writeFileSync(path.join(dir2, 'skill-b', 'SKILL.md'), `---
name: skill-b
description: Second skill
---
Prompt B
`);
const multiLoader = new SkillLoader([skillDir, dir2]);
const skills = multiLoader.discover();
expect(skills).toHaveLength(2);
fs.rmSync(dir2, { recursive: true, force: true });
});
it('ignores directories without SKILL.md', () => {
fs.mkdirSync(path.join(skillDir, 'empty-dir'), { recursive: true });
const skills = loader.discover();
expect(skills).toHaveLength(0);
});
it('handles invalid YAML frontmatter gracefully', () => {
fs.mkdirSync(path.join(skillDir, 'bad-skill'), { recursive: true });
fs.writeFileSync(path.join(skillDir, 'bad-skill', 'SKILL.md'), `No frontmatter here.`);
const skills = loader.discover();
expect(skills).toHaveLength(0);
});
it('parses optional tools field', () => {
fs.mkdirSync(path.join(skillDir, 'researcher'), { recursive: true });
fs.writeFileSync(path.join(skillDir, 'researcher', 'SKILL.md'), `---
name: researcher
description: Research topics
tools: WebSearch, WebFetch
model: sonnet
---
Research the given topic.
`);
const skills = loader.discover();
expect(skills[0].tools).toEqual(['WebSearch', 'WebFetch']);
});
it('deduplicates skills by name (later directory wins)', () => {
const dir2 = path.join(os.tmpdir(), `waggle-skills-dup-${Date.now()}`);
fs.mkdirSync(dir2, { recursive: true });
fs.mkdirSync(path.join(skillDir, 'dupe'), { recursive: true });
fs.writeFileSync(path.join(skillDir, 'dupe', 'SKILL.md'), `---
name: dupe
description: First version
---
First
`);
fs.mkdirSync(path.join(dir2, 'dupe'), { recursive: true });
fs.writeFileSync(path.join(dir2, 'dupe', 'SKILL.md'), `---
name: dupe
description: Second version
---
Second
`);
const multiLoader = new SkillLoader([skillDir, dir2]);
const skills = multiLoader.discover();
expect(skills).toHaveLength(1);
expect(skills[0].description).toBe('Second version');
fs.rmSync(dir2, { recursive: true, force: true });
});
});