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,147 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import type { AgentPersona } from '@waggle/agent';
import type { DetectedTool } from '@waggle/shared';
import { ExecutorRegistry } from '../../src/local/executor-registry.js';
function persona(id: string, overrides: Partial<AgentPersona> = {}): AgentPersona {
return {
id,
name: id,
description: '',
icon: '',
systemPrompt: '',
modelPreference: '',
tools: [],
workspaceAffinity: [],
suggestedCommands: [],
defaultWorkflow: null,
...overrides,
};
}
function detectedTool(id: string, installed = true): DetectedTool {
return {
id,
displayName: id,
installed,
installedPath: installed ? `/tools/${id}` : null,
version: installed ? '1.0.0' : null,
hooksInstalled: false,
hookPointerPath: null,
};
}
const PERSONAS = [
persona('writer'),
persona('coordinator'),
persona('analyst'),
persona('general-purpose'),
persona('researcher'),
persona('coder'),
persona('planner', { isReadOnly: true }),
];
const DETECTED_TOOLS = [
detectedTool('claude-code'),
detectedTool('codex', false),
detectedTool('hermes'),
detectedTool('openclaw'),
detectedTool('cursor'),
];
afterEach(() => {
vi.restoreAllMocks();
});
describe('ExecutorRegistry', () => {
it('composes the five v1 personas and four headless external executors', async () => {
const registry = new ExecutorRegistry({
detectTools: vi.fn(async () => DETECTED_TOOLS),
personas: () => PERSONAS,
});
const candidates = await registry.snapshot(1_000);
expect(candidates.map((candidate) => candidate.id)).toEqual([
'persona:general-purpose',
'persona:coder',
'persona:writer',
'persona:researcher',
'persona:analyst',
'external:claude-code',
'external:codex',
'external:hermes',
'external:openclaw',
]);
expect(candidates.find((candidate) => candidate.id === 'persona:coder')).toMatchObject({
kind: 'persona',
authClass: 'api-key',
installed: true,
healthy: true,
rateLimit: { state: 'unknown' },
supportsHeadless: false,
egressDestination: 'configured model provider',
taskFit: { coding: 0.85, writing: 0.3 },
});
expect(candidates.find((candidate) => candidate.id === 'external:claude-code')).toMatchObject({
kind: 'external',
displayName: 'Claude Code',
authClass: 'subscription-cli',
installed: true,
healthy: true,
rateLimit: { state: 'unknown' },
supportsHeadless: true,
egressDestination: 'Anthropic',
});
expect(candidates.find((candidate) => candidate.id === 'external:codex')).toMatchObject({
installed: false,
healthy: false,
egressDestination: 'OpenAI',
});
expect(candidates.find((candidate) => candidate.id === 'external:hermes')?.egressDestination).toBe('Nous');
expect(candidates.find((candidate) => candidate.id === 'external:openclaw')?.egressDestination)
.toBe('configured provider');
});
it('normalizes tool ids and expires rate-limit observations', async () => {
vi.spyOn(Date, 'now').mockReturnValue(10_000);
const registry = new ExecutorRegistry({
detectTools: vi.fn(async () => DETECTED_TOOLS),
personas: () => PERSONAS,
});
registry.noteRateLimit('codex', null);
const exhausted = (await registry.snapshot(10_000))
.find((candidate) => candidate.id === 'external:codex');
expect(exhausted?.rateLimit).toEqual({ state: 'observed_exhausted' });
const recovered = (await registry.snapshot(10_000 + 15 * 60_000))
.find((candidate) => candidate.id === 'external:codex');
expect(recovered?.rateLimit).toEqual({ state: 'available' });
registry.noteRateLimit('external:codex', 2_000_000);
const withReset = (await registry.snapshot(1_999_999))
.find((candidate) => candidate.id === 'external:codex');
expect(withReset?.rateLimit).toEqual({
state: 'observed_exhausted',
resumeAtMs: 2_000_000,
});
registry.noteHealthy('codex');
const healthy = (await registry.snapshot(1_999_999))
.find((candidate) => candidate.id === 'external:codex');
expect(healthy?.rateLimit).toEqual({ state: 'available' });
});
it('caches tool detection for thirty seconds', async () => {
const detectTools = vi.fn(async () => DETECTED_TOOLS);
const registry = new ExecutorRegistry({ detectTools, personas: () => PERSONAS });
await registry.snapshot(1_000);
await registry.snapshot(30_999);
expect(detectTools).toHaveBeenCalledTimes(1);
await registry.snapshot(31_000);
expect(detectTools).toHaveBeenCalledTimes(2);
});
});