blob: 90d5ccc107b5bc1edabc128a83999b3a22295b04 [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import type {
SandboxBoundaryRequestEvent,
UserQuestionRequestEvent,
} from '@maka/core';
import {
activeInteractionFor,
clearInteractions,
dequeueInteractionByRequestId,
dequeueInteractionByToolUseId,
enqueueInteraction,
reconcileInteractions,
type InteractionQueues,
} from '../interaction-queue.js';
function boundary(requestId: string): SandboxBoundaryRequestEvent {
return {
type: 'sandbox_boundary_request',
id: `evt_${requestId}`,
turnId: 'turn_1',
ts: 0,
requestId,
toolUseId: `call_${requestId}`,
justification: 'Read an external file.',
expansion: {
filesystem: {
entries: [{ path: '/outside/file', access: 'read', scope: 'exact' }],
},
},
};
}
function question(requestId: string): UserQuestionRequestEvent {
return {
type: 'user_question_request',
id: `evt_${requestId}`,
ts: 0,
requestId,
toolUseId: `call_${requestId}`,
turnId: 'turn_1',
questions: [{ question: 'Choose', options: [{ label: 'A' }] }],
};
}
describe('composer interaction queue', () => {
test('boundary and question requests share one FIFO per session', () => {
let queues: InteractionQueues = {};
queues = enqueueInteraction(queues, 's', boundary('boundary'));
queues = enqueueInteraction(queues, 's', question('question'));
assert.equal(activeInteractionFor(queues, 's')?.requestId, 'boundary');
queues = dequeueInteractionByRequestId(queues, 's', 'boundary');
assert.equal(activeInteractionFor(queues, 's')?.requestId, 'question');
});
test('deduplicates replays and isolates sessions', () => {
let queues: InteractionQueues = {};
queues = enqueueInteraction(queues, 's1', question('a'));
queues = enqueueInteraction(queues, 's1', question('a'));
queues = enqueueInteraction(queues, 's2', boundary('b'));
assert.equal(queues.s1.length, 1);
assert.equal(activeInteractionFor(queues, 's2')?.requestId, 'b');
});
test('tool completion and terminal events drain stale interactions', () => {
let queues: InteractionQueues = {};
queues = enqueueInteraction(queues, 's', boundary('a'));
queues = enqueueInteraction(queues, 's', question('b'));
queues = dequeueInteractionByToolUseId(queues, 's', 'call_a');
assert.equal(activeInteractionFor(queues, 's')?.requestId, 'b');
assert.equal(dequeueInteractionByToolUseId(queues, 's', 'missing'), queues);
queues = clearInteractions(queues, 's');
assert.equal(activeInteractionFor(queues, 's'), undefined);
});
test('rehydration keeps the shown order and drops what the runtime settled', () => {
let queues: InteractionQueues = {};
queues = enqueueInteraction(queues, 's', boundary('stale'));
queues = enqueueInteraction(queues, 's', question('answered'));
queues = enqueueInteraction(queues, 's', boundary('live'));
const reconciled = reconcileInteractions(queues, 's', [
boundary('live'),
question('unseen'),
boundary('new'),
]);
assert.deepEqual(
reconciled.s.map((interaction) => interaction.requestId),
['live', 'unseen', 'new'],
);
});
test('rehydration adds a question the surface never saw live', () => {
const reconciled = reconcileInteractions({}, 's', [question('missed')]);
assert.equal(activeInteractionFor(reconciled, 's')?.type, 'user_question_request');
assert.equal(activeInteractionFor(reconciled, 's')?.requestId, 'missed');
});
});