Files
stack/packages/agent/src/hermes-runtime-provider.test.ts
jason.woltje cca6aaf947
Some checks are pending
ci/woodpecker/push/ci Pipeline is pending
ci/woodpecker/push/publish Pipeline is pending
feat(agent): add Hermes transitional capability matrix (#738)
2026-07-13 13:14:43 +00:00

78 lines
3.0 KiB
TypeScript

import { describe, expect, it, vi } from 'vitest';
import type { RuntimeScope } from '@mosaicstack/types';
import { HermesRuntimeProvider, type HermesRuntimeTransport } from './hermes-runtime-provider.js';
const scope: RuntimeScope = { actorId: 'a', tenantId: 't', channelId: 'c', correlationId: 'r' };
const transport = (capabilities = ['session.list', 'session.tree']): HermesRuntimeTransport => ({
capabilities: vi.fn(async () => capabilities),
health: vi.fn(async () => ({ status: 'healthy' })),
sessions: vi.fn(async () => [
{
conversation_id: 'child',
agent_id: 'hermes-a',
parent_conversation_id: 'parent',
status: 'running',
created_at: '2026-01-01T00:00:00Z',
updated_at: '2026-01-01T00:00:00Z',
},
{
conversation_id: 'parent',
agent_id: 'hermes-a',
status: 'unknown',
created_at: '2026-01-01T00:00:00Z',
updated_at: '2026-01-01T00:00:00Z',
},
]),
stream: async function* () {},
send: vi.fn(),
attach: vi.fn(),
detach: vi.fn(),
terminate: vi.fn(),
});
describe('HermesRuntimeProvider normalization boundary', () => {
it('returns an exhaustive fail-closed transitional capability matrix', async () => {
const provider = new HermesRuntimeProvider(transport());
await expect(provider.transitionalCapabilityMatrix(scope)).resolves.toEqual([
{ capability: 'kanban', status: 'unsupported' },
{ capability: 'skills', status: 'unsupported' },
{ capability: 'memory', status: 'unsupported' },
{ capability: 'tools', status: 'unsupported' },
{ capability: 'cron', status: 'unsupported' },
]);
});
it('denies unsupported transitional capabilities without calling Hermes', async () => {
const hermes = transport();
const provider = new HermesRuntimeProvider(hermes);
await expect(provider.assertTransitionalCapability('memory', scope)).rejects.toMatchObject({
code: 'capability_unsupported',
});
expect(hermes.capabilities).not.toHaveBeenCalled();
});
it('normalizes legacy sessions without exposing legacy fields', async () => {
const provider = new HermesRuntimeProvider(transport());
await expect(provider.listSessions(scope)).resolves.toEqual(
expect.arrayContaining([
expect.objectContaining({ id: 'child', runtimeId: 'hermes-a', state: 'active' }),
]),
);
const result = await provider.listSessions(scope);
expect(result[0]).not.toHaveProperty('conversation_id');
});
it('forms normalized hierarchy and fails closed for unbridged operations', async () => {
const provider = new HermesRuntimeProvider(transport());
await expect(provider.getSessionTree(scope)).resolves.toEqual([
expect.objectContaining({
session: expect.objectContaining({ id: 'parent', state: 'failed' }),
children: [expect.objectContaining({ session: expect.objectContaining({ id: 'child' }) })],
}),
]);
await expect(
provider.sendMessage('parent', { content: 'x', idempotencyKey: 'i' }, scope),
).rejects.toMatchObject({ code: 'capability_unsupported' });
});
});