83 lines
3.4 KiB
TypeScript
83 lines
3.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { existsSync } from 'node:fs';
|
|
import { fileURLToPath } from 'node:url';
|
|
import { dirname, join } from 'node:path';
|
|
import {
|
|
HERMES_EVENT_NAME,
|
|
HERMES_SESSION_START_OBSERVE_EVENT,
|
|
hermesAdapter,
|
|
} from '../src/adapter.js';
|
|
|
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
const SRC = join(HERE, '..', 'src');
|
|
|
|
describe('HERMES_EVENT_NAME (lifecycle → native event key)', () => {
|
|
it('maps the three live lifecycles to snake_case Hermes events; pre-compact is undefined', () => {
|
|
expect(HERMES_EVENT_NAME['session-start']).toBe('pre_llm_call');
|
|
expect(HERMES_EVENT_NAME['user-prompt-submit']).toBe('pre_llm_call');
|
|
expect(HERMES_EVENT_NAME['stop']).toBe('post_llm_call');
|
|
// CONFIRMED ABSENT in source — Hermes ships no compaction hook.
|
|
expect(HERMES_EVENT_NAME['pre-compact']).toBeUndefined();
|
|
});
|
|
|
|
it('exposes the observer-only SessionStart key registered alongside pre_llm_call', () => {
|
|
expect(HERMES_SESSION_START_OBSERVE_EVENT).toBe('on_session_start');
|
|
});
|
|
});
|
|
|
|
describe('hermesAdapter field extraction (reads from the `extra` envelope)', () => {
|
|
const payload = {
|
|
extra: {
|
|
cwd: '/work/dir',
|
|
session_id: 'sess-42',
|
|
user_message: 'the user prompt',
|
|
assistant_response: 'the assistant reply',
|
|
parent_frame_id: 'frame-99',
|
|
},
|
|
};
|
|
|
|
it('source is hermes', () => {
|
|
expect(hermesAdapter.source).toBe('hermes');
|
|
});
|
|
|
|
it('extractCwd / extractSessionId / extractPrompt read from extra', () => {
|
|
expect(hermesAdapter.extractCwd(payload)).toBe('/work/dir');
|
|
expect(hermesAdapter.extractSessionId(payload)).toBe('sess-42');
|
|
expect(hermesAdapter.extractPrompt(payload)).toBe('the user prompt');
|
|
});
|
|
|
|
it('extractResponse / extractParent read from extra', async () => {
|
|
expect(await hermesAdapter.extractResponse(payload, {})).toBe('the assistant reply');
|
|
expect(hermesAdapter.extractParent(payload)).toBe('frame-99');
|
|
});
|
|
|
|
it('falls back to top-level keys when the extra envelope is absent (forward-compat)', () => {
|
|
const flat = { cwd: '/c', session_id: 's', user_message: 'p', parent_frame_id: 'pp' };
|
|
expect(hermesAdapter.extractCwd(flat)).toBe('/c');
|
|
expect(hermesAdapter.extractSessionId(flat)).toBe('s');
|
|
expect(hermesAdapter.extractPrompt(flat)).toBe('p');
|
|
expect(hermesAdapter.extractParent(flat)).toBe('pp');
|
|
});
|
|
|
|
it('formatInject produces the { context } shape (appended to the user message)', () => {
|
|
expect(hermesAdapter.formatInject?.('recalled frames here')).toEqual({ context: 'recalled frames here' });
|
|
});
|
|
|
|
it('returns undefined for missing fields rather than throwing', () => {
|
|
expect(hermesAdapter.extractCwd({})).toBeUndefined();
|
|
expect(hermesAdapter.extractSessionId({})).toBeUndefined();
|
|
expect(hermesAdapter.extractPrompt({})).toBeUndefined();
|
|
expect(hermesAdapter.extractParent({})).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('NO pre-compact entrypoint exists (structural invariant)', () => {
|
|
it('ships exactly the three hook entrypoints — session-start / user-prompt-submit / stop', () => {
|
|
expect(existsSync(join(SRC, 'hooks', 'session-start.ts'))).toBe(true);
|
|
expect(existsSync(join(SRC, 'hooks', 'user-prompt-submit.ts'))).toBe(true);
|
|
expect(existsSync(join(SRC, 'hooks', 'stop.ts'))).toBe(true);
|
|
// There is genuinely nothing to hook for compaction — no entrypoint.
|
|
expect(existsSync(join(SRC, 'hooks', 'pre-compact.ts'))).toBe(false);
|
|
});
|
|
});
|