blob: f0a5622c4a0424d03a1e3fbe9945194800b9a9a9 [file]
import assert from 'node:assert/strict';
import { describe, test } from 'node:test';
import {
decodeClientFrame,
decodeHostFrame,
HOST_OPERATION_SPECS,
RuntimeHostProtocolError,
type SessionCatalogProjection,
} from '../protocol/index.js';
describe('Session revision protocol', () => {
test('declares exact ready-only branch and revision commands', () => {
for (const operation of ['session.branch.create', 'session.revision.create'] as const) {
assert.equal(HOST_OPERATION_SPECS[operation].mode, 'command');
assert.equal(HOST_OPERATION_SPECS[operation].availability, 'ready');
assert.deepEqual(
decodeClientFrame({
requestId: 'request-1',
operation,
input: {
sourceSessionId: 'source-session',
targetSessionId: 'target-session',
sourceTurnId: 'turn-1',
expectedSourceRevision: 3,
},
}),
{
requestId: 'request-1',
operation,
input: {
sourceSessionId: 'source-session',
targetSessionId: 'target-session',
sourceTurnId: 'turn-1',
expectedSourceRevision: 3,
},
},
);
}
});
test('rejects aliasing, unknown fields, and mismatched response identities', () => {
assert.throws(
() =>
decodeClientFrame({
requestId: 'request-1',
operation: 'session.branch.create',
input: {
sourceSessionId: 'same-session',
targetSessionId: 'same-session',
sourceTurnId: 'turn-1',
expectedSourceRevision: 1,
},
}),
isInvalidFrame,
);
assert.throws(
() =>
decodeClientFrame({
requestId: 'request-1',
operation: 'session.revision.create',
input: {
sourceSessionId: 'source-session',
targetSessionId: 'target-session',
sourceTurnId: 'turn-1',
expectedSourceRevision: 1,
ignored: true,
},
}),
isInvalidFrame,
);
const input = {
sourceSessionId: 'source-session',
targetSessionId: 'target-session',
sourceTurnId: 'turn-1',
expectedSourceRevision: 1,
};
assert.throws(
() =>
HOST_OPERATION_SPECS['session.branch.create'].assertOutputForInput?.(input, {
kind: 'committed',
session: sessionProjection('wrong-target'),
}),
isInvalidFrame,
);
assert.deepEqual(
decodeHostFrame({
requestId: 'request-abandon',
operation: 'session.revision.abandon',
ok: true,
result: { kind: 'retained', sessionId: 'revision-target' },
}),
{
requestId: 'request-abandon',
operation: 'session.revision.abandon',
ok: true,
result: { kind: 'retained', sessionId: 'revision-target' },
},
);
});
test('preserves optimistic source revision conflicts on the wire', () => {
assert.deepEqual(
decodeHostFrame({
requestId: 'request-1',
operation: 'session.revision.create',
ok: true,
result: {
kind: 'source_revision_conflict',
expectedRevision: 4,
actualRevision: 5,
},
}),
{
requestId: 'request-1',
operation: 'session.revision.create',
ok: true,
result: {
kind: 'source_revision_conflict',
expectedRevision: 4,
actualRevision: 5,
},
},
);
});
test('keeps single-target Revision abandonment identity exact', () => {
const frame = decodeClientFrame({
requestId: 'request-abandon',
operation: 'session.revision.abandon',
input: { targetSessionId: 'revision-target' },
});
assert.deepEqual(frame, {
requestId: 'request-abandon',
operation: 'session.revision.abandon',
input: { targetSessionId: 'revision-target' },
});
assert.throws(
() =>
HOST_OPERATION_SPECS['session.revision.abandon'].assertOutputForInput?.(
{ targetSessionId: 'revision-target' },
{ kind: 'abandoned', sessionId: 'other-target' },
),
isInvalidFrame,
);
});
});
function sessionProjection(id: string): SessionCatalogProjection {
return {
id,
revision: 1,
cwd: '/workspace',
createdAt: 1,
lastUsedAt: 1,
name: 'Session',
isFlagged: false,
isArchived: false,
labels: [],
labelsTruncated: false,
hasUnread: false,
status: 'active',
backend: 'fake',
llmConnectionSlug: 'fake',
connectionLocked: false,
model: 'fake-model',
permissionMode: 'ask',
collaborationMode: 'agent',
orchestrationMode: 'default',
};
}
function isInvalidFrame(error: unknown): boolean {
return error instanceof RuntimeHostProtocolError && error.code === 'invalid_frame';
}