Files
Oleg Maslov 0c3e2ead3b
Some checks failed
Installer Smoke / installer-smoke (push) Has been cancelled
moving
2026-09-02 10:10:29 +02:00

104 lines
3.5 KiB
TypeScript

import { describe, expect, it, afterEach, vi } from 'vitest';
import { runStop } from '../../src/hooks/stop.js';
import { makeHookCaptures, makeMockBridge } from './_test-helpers.js';
import type { HookFrame } from '@waggle/hive-mind-shim-core';
describe('codex stop handler', () => {
afterEach(() => {
vi.unstubAllEnvs();
});
it('reads the codex last_assistant_message and saves an important frame', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({
last_assistant_message: 'Here is the answer to your question about X.',
cwd: '/proj/foo',
session_id: 'sess-9',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).toHaveBeenCalledTimes(1);
const frame = bridge.saveMemory.mock.calls[0][0] as HookFrame;
expect(frame.source).toBe('codex');
expect(frame.scope).toBe('sess-9');
expect(['important', 'critical']).toContain(frame.importance);
expect(frame.content.length).toBeGreaterThan(0);
expect(cap.exits).toEqual([0]);
});
it('links the Stop frame to its parent prompt frame when known', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({
last_assistant_message: 'done',
parent_frame_id: 'frame-prompt-1',
session_id: 's',
}),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
const frame = bridge.saveMemory.mock.calls[0][0] as HookFrame;
expect(frame.parent).toBe('frame-prompt-1');
});
it('still honours the CC response fallback keys (response / assistant_message)', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({ response: 'classic CC key', session_id: 's' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).toHaveBeenCalledTimes(1);
const frame = bridge.saveMemory.mock.calls[0][0] as HookFrame;
expect(frame.content.length).toBeGreaterThan(0);
});
it('skips the save when no response is present', async () => {
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => '{}',
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(bridge.saveMemory).not.toHaveBeenCalled();
expect(cap.exits).toEqual([0]);
});
it('does NOT emit a discovery signal by default (WAGGLE_SIGNAL_EMIT off)', async () => {
vi.stubEnv('WAGGLE_SIGNAL_EMIT', '');
const bridge = makeMockBridge();
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({ last_assistant_message: 'hi', session_id: 's' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
// callMcpTool is how the bridge would reach the sidecar; no emit by default.
expect(bridge.callMcpTool).not.toHaveBeenCalled();
expect(cap.exits).toEqual([0]);
});
it('FAIL-OPEN: exits 0 even when saveMemory rejects', async () => {
const bridge = makeMockBridge({ saveMemoryThrows: new Error('cli unreachable') });
const cap = makeHookCaptures();
await runStop({
readStdin: async () => JSON.stringify({ last_assistant_message: 'x', session_id: 's' }),
writeStdout: cap.writeStdout,
exit: cap.exit,
bridge,
});
expect(cap.exits).toEqual([0]);
});
});