feat(tess): wire durable interaction surfaces (#732)
This commit was merged in pull request #732.
This commit is contained in:
@@ -1,9 +1,27 @@
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||
import { RuntimeApprovalDeniedError } from './runtime-provider-registry.service.js';
|
||||
import { RuntimeApprovalDeniedFilter } from './runtime-approval-denied.filter.js';
|
||||
import { InteractionController } from './interaction.controller.js';
|
||||
|
||||
describe('InteractionController', (): void => {
|
||||
afterEach(() => vi.restoreAllMocks());
|
||||
|
||||
it('maps a denied runtime approval to Fastify HTTP 403', () => {
|
||||
const send = vi.fn();
|
||||
const status = vi.fn().mockReturnValue({ send });
|
||||
const response = { status };
|
||||
const host = { switchToHttp: () => ({ getResponse: () => response }) };
|
||||
|
||||
new RuntimeApprovalDeniedFilter().catch(new RuntimeApprovalDeniedError(), host as never);
|
||||
|
||||
expect(status).toHaveBeenCalledWith(403);
|
||||
expect(send).toHaveBeenCalledWith({
|
||||
statusCode: 403,
|
||||
message: 'Runtime termination approval denied',
|
||||
});
|
||||
});
|
||||
|
||||
it('honors a differently named configured instance without a code change', async () => {
|
||||
const prior = process.env['MOSAIC_AGENT_NAME'];
|
||||
process.env['MOSAIC_AGENT_NAME'] = 'Nova';
|
||||
@@ -30,6 +48,36 @@ describe('InteractionController', (): void => {
|
||||
);
|
||||
});
|
||||
|
||||
it('enrolls a visible runtime session under the cross-surface conversation handle', async () => {
|
||||
process.env['MOSAIC_AGENT_NAME'] = 'Nova';
|
||||
const runtime = {
|
||||
listSessions: vi.fn().mockResolvedValue([{ id: 'runtime-1' }]),
|
||||
};
|
||||
const durable = { enroll: vi.fn().mockResolvedValue(undefined) };
|
||||
const controller = new InteractionController(runtime as never, durable as never);
|
||||
|
||||
await expect(
|
||||
controller.enroll(
|
||||
'Nova',
|
||||
'conversation-1',
|
||||
{ providerId: 'fleet', runtimeSessionId: 'runtime-1' },
|
||||
{ id: 'owner', tenantId: 'team' },
|
||||
'corr-1',
|
||||
),
|
||||
).resolves.toEqual({ status: 'enrolled', sessionId: 'conversation-1' });
|
||||
expect(durable.enroll).toHaveBeenCalledWith(
|
||||
{
|
||||
agentName: 'Nova',
|
||||
sessionId: 'conversation-1',
|
||||
tenantId: 'team',
|
||||
ownerId: 'owner',
|
||||
providerId: 'fleet',
|
||||
runtimeSessionId: 'runtime-1',
|
||||
},
|
||||
expect.objectContaining({ correlationId: 'corr-1' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('rejects an invalid attach mode before invoking a provider', async () => {
|
||||
process.env['MOSAIC_AGENT_NAME'] = 'Nova';
|
||||
const controller = new InteractionController({ attach: vi.fn() } as never, {} as never);
|
||||
@@ -39,6 +87,121 @@ describe('InteractionController', (): void => {
|
||||
).rejects.toThrow('Interaction attach mode is invalid');
|
||||
});
|
||||
|
||||
it('resumes a durable session by attaching and streaming its runtime events', async () => {
|
||||
process.env['MOSAIC_AGENT_NAME'] = 'Nova';
|
||||
const runtimeEvent = {
|
||||
type: 'message.delta' as const,
|
||||
sessionId: 'runtime-1',
|
||||
cursor: 'cursor-1',
|
||||
occurredAt: '2026-07-13T00:00:00.000Z',
|
||||
content: 'resumed',
|
||||
};
|
||||
const runtime = {
|
||||
attach: vi.fn().mockResolvedValue({ attachmentId: 'attach-1', sessionId: 'runtime-1' }),
|
||||
streamSession: vi.fn(async function* () {
|
||||
yield runtimeEvent;
|
||||
}),
|
||||
};
|
||||
const durable = {
|
||||
getSnapshot: vi.fn().mockResolvedValue({
|
||||
identity: { agentName: 'Nova', providerId: 'fleet', runtimeSessionId: 'runtime-1' },
|
||||
}),
|
||||
};
|
||||
const controller = new InteractionController(runtime as never, durable as never);
|
||||
|
||||
await controller.attach('Nova', 'conversation-1', { mode: 'read' }, { id: 'owner' }, 'corr-1');
|
||||
await expect(
|
||||
firstValueFrom(
|
||||
controller.stream('Nova', 'conversation-1', undefined, { id: 'owner' }, 'corr-1'),
|
||||
),
|
||||
).resolves.toEqual({ data: runtimeEvent });
|
||||
|
||||
expect(runtime.attach).toHaveBeenCalledWith(
|
||||
'fleet',
|
||||
'runtime-1',
|
||||
'read',
|
||||
expect.objectContaining({ correlationId: 'corr-1' }),
|
||||
);
|
||||
expect(runtime.streamSession).toHaveBeenCalledWith(
|
||||
'fleet',
|
||||
'runtime-1',
|
||||
undefined,
|
||||
expect.objectContaining({ correlationId: 'corr-1' }),
|
||||
);
|
||||
});
|
||||
|
||||
it('does not create a runtime stream after the SSE subscriber disconnects during snapshot lookup', async () => {
|
||||
process.env['MOSAIC_AGENT_NAME'] = 'Nova';
|
||||
let resolveSnapshot!: (value: { identity: Record<string, string> }) => void;
|
||||
const snapshot = new Promise<{ identity: Record<string, string> }>((resolve) => {
|
||||
resolveSnapshot = resolve;
|
||||
});
|
||||
const runtime = { streamSession: vi.fn() };
|
||||
const durable = { getSnapshot: vi.fn().mockReturnValue(snapshot) };
|
||||
const controller = new InteractionController(runtime as never, durable as never);
|
||||
|
||||
const subscription = controller
|
||||
.stream('Nova', 'conversation-1', undefined, { id: 'owner' }, 'corr-1')
|
||||
.subscribe();
|
||||
subscription.unsubscribe();
|
||||
resolveSnapshot({
|
||||
identity: { agentName: 'Nova', providerId: 'fleet', runtimeSessionId: 'runtime-1' },
|
||||
});
|
||||
await new Promise((resolve) => setTimeout(resolve, 0));
|
||||
|
||||
expect(runtime.streamSession).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it.each([
|
||||
['wrong actor', { getSnapshot: vi.fn().mockRejectedValue(new Error('scope mismatch')) }],
|
||||
[
|
||||
'session-agent mismatch',
|
||||
{
|
||||
getSnapshot: vi.fn().mockResolvedValue({
|
||||
identity: { agentName: 'Other', providerId: 'fleet', runtimeSessionId: 'runtime-1' },
|
||||
}),
|
||||
},
|
||||
],
|
||||
])('denies a CLI stop for %s', async (_reason, durable) => {
|
||||
process.env['MOSAIC_AGENT_NAME'] = 'Nova';
|
||||
const runtime = { terminate: vi.fn().mockResolvedValue(undefined) };
|
||||
const controller = new InteractionController(runtime as never, durable as never);
|
||||
|
||||
await expect(
|
||||
controller.stop(
|
||||
'Nova',
|
||||
'durable-1',
|
||||
{ approvalRef: 'approval-1' },
|
||||
{ id: 'owner' },
|
||||
'corr-1',
|
||||
),
|
||||
).rejects.toBeDefined();
|
||||
expect(runtime.terminate).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('surfaces a denied runtime approval to the CLI interaction surface', async () => {
|
||||
process.env['MOSAIC_AGENT_NAME'] = 'Nova';
|
||||
const runtime = {
|
||||
terminate: vi.fn().mockRejectedValue(new Error('Runtime termination approval denied')),
|
||||
};
|
||||
const durable = {
|
||||
getSnapshot: vi.fn().mockResolvedValue({
|
||||
identity: { agentName: 'Nova', providerId: 'fleet', runtimeSessionId: 'runtime-1' },
|
||||
}),
|
||||
};
|
||||
const controller = new InteractionController(runtime as never, durable as never);
|
||||
|
||||
await expect(
|
||||
controller.stop(
|
||||
'Nova',
|
||||
'durable-1',
|
||||
{ approvalRef: 'approval-1' },
|
||||
{ id: 'owner' },
|
||||
'corr-1',
|
||||
),
|
||||
).rejects.toThrow('Runtime termination approval denied');
|
||||
});
|
||||
|
||||
it('uses the durable session identity and runtime registry for an approved stop', async () => {
|
||||
process.env['MOSAIC_AGENT_NAME'] = 'Nova';
|
||||
const runtime = { terminate: vi.fn().mockResolvedValue(undefined) };
|
||||
|
||||
Reference in New Issue
Block a user