feat(agent): add Hermes transitional capability matrix
All checks were successful
ci/woodpecker/pr/ci Pipeline was successful

This commit is contained in:
Jarvis
2026-07-13 08:01:46 -05:00
parent 2363f155b4
commit 582c6db208
3 changed files with 78 additions and 1 deletions

View File

@@ -30,6 +30,28 @@ const transport = (capabilities = ['session.list', 'session.tree']): HermesRunti
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(

View File

@@ -11,9 +11,19 @@ import type {
RuntimeSessionState,
RuntimeSessionTree,
RuntimeStreamEvent,
TransitionalCapabilityInventoryEntry,
TransitionalCapabilityInventoryProvider,
TransitionalRuntimeCapability,
} from '@mosaicstack/types';
const HERMES_PROVIDER_ID = 'runtime.hermes';
const TRANSITIONAL_CAPABILITIES: readonly TransitionalRuntimeCapability[] = [
'kanban',
'skills',
'memory',
'tools',
'cron',
];
const RUNTIME_CAPABILITIES: readonly RuntimeCapability[] = [
'session.list',
'session.tree',
@@ -65,11 +75,38 @@ export class HermesRuntimeProviderError extends Error {
* Transitional Hermes adapter. Legacy identifiers and schemas do not cross this
* boundary: callers only observe Mosaic AgentRuntimeProvider contracts.
*/
export class HermesRuntimeProvider implements AgentRuntimeProvider {
export class HermesRuntimeProvider
implements AgentRuntimeProvider, TransitionalCapabilityInventoryProvider
{
readonly id = HERMES_PROVIDER_ID;
constructor(private readonly transport: HermesRuntimeTransport) {}
/**
* Full AC-TESS-05 migration inventory. These operations are deliberately
* unsupported until their Mosaic-owned plugin contracts exist.
*/
async transitionalCapabilityMatrix(
_scope: RuntimeScope,
): Promise<TransitionalCapabilityInventoryEntry[]> {
return TRANSITIONAL_CAPABILITIES.map((capability) => ({ capability, status: 'unsupported' }));
}
async assertTransitionalCapability(
capability: TransitionalRuntimeCapability,
scope: RuntimeScope,
): Promise<void> {
const entry = (await this.transitionalCapabilityMatrix(scope)).find(
(candidate) => candidate.capability === capability,
);
if (!entry || entry.status !== 'supported') {
throw new HermesRuntimeProviderError(
'capability_unsupported',
`Hermes transitional capability is unsupported: ${capability}`,
);
}
}
async capabilities(scope: RuntimeScope): Promise<RuntimeCapabilitySet> {
const legacyCapabilities = await this.transport.capabilities(scope);
return {

View File

@@ -6,6 +6,24 @@ export type RuntimeCapability =
| 'session.attach'
| 'session.terminate';
export type RuntimeSessionState = 'starting' | 'active' | 'idle' | 'stopped' | 'failed';
/** Transitional capability inventory is normalized; provider legacy vocabularies never enter core. */
export type TransitionalRuntimeCapability = 'kanban' | 'skills' | 'memory' | 'tools' | 'cron';
export type TransitionalCapabilityStatus = 'supported' | 'unsupported';
export interface TransitionalCapabilityInventoryEntry {
capability: TransitionalRuntimeCapability;
status: TransitionalCapabilityStatus;
}
/** Optional extension for transitional adapters; not every runtime has Hermes inventory. */
export interface TransitionalCapabilityInventoryProvider {
transitionalCapabilityMatrix(
scope: RuntimeScope,
): Promise<TransitionalCapabilityInventoryEntry[]>;
assertTransitionalCapability(
capability: TransitionalRuntimeCapability,
scope: RuntimeScope,
): Promise<void>;
}
export type RuntimeAttachMode = 'read' | 'control';
/** Server-derived immutable authority context. Client identity fields are intentionally absent. */