141 lines
4.5 KiB
TypeScript
141 lines
4.5 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
buildStructuredOutputPrompt,
|
|
classifySuppressTokenOutput,
|
|
parseStructuredOutputEnvelope,
|
|
} from './output-suppression.js';
|
|
|
|
describe('classifySuppressTokenOutput', () => {
|
|
it('treats the current turn suppress token as exact', () => {
|
|
expect(
|
|
classifySuppressTokenOutput(
|
|
'__EJ_SUPPRESS_deadbeefdeadbeefdeadbeef__',
|
|
'__EJ_SUPPRESS_deadbeefdeadbeefdeadbeef__',
|
|
),
|
|
).toBe('exact');
|
|
});
|
|
|
|
it('treats a leaked foreign suppress token as exact silent output', () => {
|
|
expect(
|
|
classifySuppressTokenOutput(
|
|
'__EJ_SUPPRESS_feedfacefeedfacefeedface__',
|
|
'__EJ_SUPPRESS_deadbeefdeadbeefdeadbeef__',
|
|
),
|
|
).toBe('exact');
|
|
});
|
|
|
|
it('treats a malformed foreign suppress token without the closing suffix as exact silent output', () => {
|
|
expect(
|
|
classifySuppressTokenOutput(
|
|
'__EJ_SUPPRESS_feedfacefeedfacefeedface',
|
|
'__EJ_SUPPRESS_deadbeefdeadbeefdeadbeef__',
|
|
),
|
|
).toBe('exact');
|
|
});
|
|
|
|
it('treats a suppress token embedded in visible text as mixed', () => {
|
|
expect(
|
|
classifySuppressTokenOutput(
|
|
'prefix __EJ_SUPPRESS_feedfacefeedfacefeedface__ suffix',
|
|
'__EJ_SUPPRESS_deadbeefdeadbeefdeadbeef__',
|
|
),
|
|
).toBe('mixed');
|
|
});
|
|
|
|
it('treats the exact structured silent envelope as exact silent output', () => {
|
|
expect(
|
|
classifySuppressTokenOutput(
|
|
'{"ejclaw":{"visibility":"silent"}}',
|
|
undefined,
|
|
),
|
|
).toBe('exact');
|
|
});
|
|
|
|
it('treats a truncated structured silent envelope as mixed', () => {
|
|
expect(
|
|
classifySuppressTokenOutput(
|
|
'{"ejclaw":{"visibility":"silent"',
|
|
undefined,
|
|
),
|
|
).toBe('mixed');
|
|
});
|
|
|
|
it('treats a structured silent envelope mixed with extra text as mixed', () => {
|
|
expect(
|
|
classifySuppressTokenOutput(
|
|
'{"ejclaw":{"visibility":"silent"}} extra',
|
|
undefined,
|
|
),
|
|
).toBe('mixed');
|
|
});
|
|
});
|
|
|
|
describe('parseStructuredOutputEnvelope', () => {
|
|
it('parses the exact silent envelope', () => {
|
|
expect(
|
|
parseStructuredOutputEnvelope('{"ejclaw":{"visibility":"silent"}}'),
|
|
).toEqual({ visibility: 'silent' });
|
|
});
|
|
|
|
it('parses a public envelope', () => {
|
|
expect(
|
|
parseStructuredOutputEnvelope(
|
|
'{"ejclaw":{"visibility":"public","text":"hello"}}',
|
|
),
|
|
).toEqual({ visibility: 'public', text: 'hello' });
|
|
});
|
|
|
|
it('parses a public envelope with a reviewer verdict', () => {
|
|
expect(
|
|
parseStructuredOutputEnvelope(
|
|
'{"ejclaw":{"visibility":"public","verdict":"done_with_concerns","text":"**DONE_WITH_CONCERNS**"}}',
|
|
),
|
|
).toEqual({
|
|
visibility: 'public',
|
|
verdict: 'done_with_concerns',
|
|
text: '**DONE_WITH_CONCERNS**',
|
|
});
|
|
});
|
|
});
|
|
|
|
describe('buildStructuredOutputPrompt', () => {
|
|
it('prepends the structured output control block', () => {
|
|
expect(buildStructuredOutputPrompt('hello')).toContain(
|
|
'If you have no user-visible content to send for this turn, output exactly this JSON and nothing else: {"ejclaw":{"visibility":"silent"}}',
|
|
);
|
|
expect(buildStructuredOutputPrompt('hello')).toContain(
|
|
'If you have already emitted any visible progress, status update, or partial answer earlier in this turn, do not end with the JSON object. Finish with a short visible final conclusion for the user instead.',
|
|
);
|
|
});
|
|
|
|
it('tightens the reviewer silent rule when reviewer mode is enabled', () => {
|
|
expect(
|
|
buildStructuredOutputPrompt('hello', { reviewerMode: true }),
|
|
).toContain(
|
|
'If you have not already emitted any visible progress, status update, or partial answer in this turn and you are only agreeing, mirroring, or restating without adding a concrete correction, risk, missing prerequisite, test gap, or code change, output only the JSON object.',
|
|
);
|
|
});
|
|
|
|
it('requires a visible structured verdict on reviewer gate turns', () => {
|
|
expect(
|
|
buildStructuredOutputPrompt('hello', {
|
|
reviewerMode: true,
|
|
gateTurnKind: 'implementation_start',
|
|
requiresVisibleVerdict: true,
|
|
}),
|
|
).toContain(
|
|
'This turn is a paired-room gate turn for implementation_start. Silent output is forbidden.',
|
|
);
|
|
expect(
|
|
buildStructuredOutputPrompt('hello', {
|
|
reviewerMode: true,
|
|
gateTurnKind: 'implementation_start',
|
|
requiresVisibleVerdict: true,
|
|
}),
|
|
).toContain(
|
|
'Allowed verdict values are: "done", "done_with_concerns", "blocked".',
|
|
);
|
|
});
|
|
});
|