blob: 6cc1d002c3ac49303614d19b295461acb2b87a65 [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import type {
AdditionalPermissionRequestEvent,
PermissionRequestEvent,
SandboxEscalationRequestEvent,
UserQuestionRequestEvent,
} from '@maka/core';
import {
activeInteractionFor,
clearInteractions,
dequeueInteractionByRequestId,
dequeueInteractionByToolUseId,
enqueueInteraction,
type InteractionQueues,
} from '../interaction-queue.js';
function permission(requestId: string): PermissionRequestEvent {
return {
type: 'permission_request',
id: `evt_${requestId}`,
ts: 0,
requestId,
toolUseId: `call_${requestId}`,
toolName: 'browser_snapshot',
} as PermissionRequestEvent;
}
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' }] }],
};
}
function additionalPermission(requestId: string): AdditionalPermissionRequestEvent {
return {
type: 'permission_request',
kind: 'additional_permissions',
id: `evt_${requestId}`,
turnId: 'turn_1',
ts: 0,
requestId,
toolUseId: `call_${requestId}`,
toolName: 'Write',
category: 'file_write',
reason: 'additional_permissions',
args: undefined,
cwd: '/workspace',
justification: 'Write requires access to the requested path.',
intentHash: `sha256:${'1'.repeat(64)}`,
permissionsHash: `sha256:${'2'.repeat(64)}`,
additionalPermissions: {
fileSystem: { entries: [{ path: '/outside/file', access: 'write', scope: 'exact' }] },
},
risk: { outsideWorkspace: true, protectedMetadata: false, networkEnabled: false },
alsoApprovesToolExecution: true,
availableDecisions: ['allow_once', 'deny'],
rememberForTurnAllowed: false,
};
}
function sandboxEscalation(requestId: string): SandboxEscalationRequestEvent {
return {
type: 'permission_request',
kind: 'sandbox_escalation',
id: `evt_${requestId}`,
turnId: 'turn_1',
ts: 0,
requestId,
toolUseId: `call_${requestId}`,
toolName: 'Bash',
category: 'shell_unsafe',
reason: 'sandbox_escalation',
args: undefined,
command: 'printf retry-ok > /tmp/retry.txt',
cwd: '/workspace',
justification: 'The exact command must write outside the workspace.',
intentHash: `sha256:${'3'.repeat(64)}`,
commandHash: `sha256:${'4'.repeat(64)}`,
trigger: 'sandbox_denial',
risk: {
unsandboxedExecution: true,
unrestrictedFileSystem: true,
unrestrictedNetwork: true,
protectedMetadataExposed: true,
},
alsoApprovesToolExecution: true,
availableDecisions: ['allow_once', 'deny'],
rememberForTurnAllowed: false,
};
}
describe('composer interaction queue', () => {
test('permission and question requests share one FIFO per session', () => {
let queues: InteractionQueues = {};
queues = enqueueInteraction(queues, 's', permission('permission'));
queues = enqueueInteraction(queues, 's', question('question'));
assert.equal(activeInteractionFor(queues, 's')?.requestId, 'permission');
queues = dequeueInteractionByRequestId(queues, 's', 'permission');
assert.equal(activeInteractionFor(queues, 's')?.requestId, 'question');
});
test('queues one-call additional permissions as ordinary composer interactions', () => {
const queues = enqueueInteraction({}, 's', additionalPermission('additional'));
const active = activeInteractionFor(queues, 's');
assert.equal(active?.type, 'permission_request');
if (active?.type === 'permission_request') {
assert.equal(active.kind, 'additional_permissions');
assert.equal(active.rememberForTurnAllowed, false);
}
});
test('queues one-call sandbox escalation without turn memory', () => {
const queues = enqueueInteraction({}, 's', sandboxEscalation('escalation'));
const active = activeInteractionFor(queues, 's');
assert.equal(active?.type, 'permission_request');
if (active?.type === 'permission_request') {
assert.equal(active.kind, 'sandbox_escalation');
assert.equal(active.rememberForTurnAllowed, false);
}
});
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', permission('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', permission('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);
});
});