runners: share protocol and reviewer policy

This commit is contained in:
ejclaw
2026-04-11 05:07:05 +09:00
parent 94d53e4cc3
commit f5c3393c32
17 changed files with 648 additions and 276 deletions

View File

@@ -0,0 +1,78 @@
import { describe, expect, it } from 'vitest';
import {
extractImageTagPaths,
normalizeEjclawStructuredOutput,
normalizePublicTextOutput,
writeProtocolOutput,
} from '../src/agent-protocol.js';
describe('shared agent protocol helpers', () => {
it('extracts image tags without leaking regex state', () => {
expect(extractImageTagPaths('hello [Image: /tmp/a.png]')).toEqual({
cleanText: 'hello',
imagePaths: ['/tmp/a.png'],
});
expect(extractImageTagPaths('[Image: /tmp/b.png] second')).toEqual({
cleanText: 'second',
imagePaths: ['/tmp/b.png'],
});
});
it('normalizes plain text runner output as public text', () => {
expect(normalizePublicTextOutput('DONE')).toEqual({
result: 'DONE',
output: {
visibility: 'public',
text: 'DONE',
},
});
});
it('parses silent ejclaw envelopes', () => {
expect(
normalizeEjclawStructuredOutput(
JSON.stringify({
ejclaw: { visibility: 'silent', verdict: 'silent' },
}),
),
).toEqual({
result: null,
output: {
visibility: 'silent',
verdict: 'silent',
},
});
});
it('falls back to visible raw text on invalid public verdicts', () => {
const raw = JSON.stringify({
ejclaw: {
visibility: 'public',
text: 'DONE',
verdict: 'mystery',
},
});
expect(normalizeEjclawStructuredOutput(raw)).toEqual({
result: raw,
output: {
visibility: 'public',
text: raw,
},
});
});
it('writes marker-delimited protocol output', () => {
const lines: string[] = [];
writeProtocolOutput({ status: 'success', result: 'ok' }, (line) => {
lines.push(line);
});
expect(lines).toEqual([
'---EJCLAW_OUTPUT_START---',
'{"status":"success","result":"ok"}',
'---EJCLAW_OUTPUT_END---',
]);
});
});

View File

@@ -0,0 +1,77 @@
import { describe, expect, it } from 'vitest';
import {
buildPairedReadonlyRuntimeEnvOverrides,
getReviewerRuntimeCapabilities,
} from '../src/reviewer-runtime-policy.js';
describe('shared reviewer runtime policy', () => {
it('encodes claude reviewer capabilities explicitly', () => {
expect(getReviewerRuntimeCapabilities('claude-code')).toEqual({
agentType: 'claude-code',
supportsShellPreflightHook: true,
supportsReadonlySandboxing: true,
supportsGitWriteGuard: true,
supportsHardMutationBlocking: true,
});
});
it('encodes codex reviewer limitations explicitly', () => {
expect(getReviewerRuntimeCapabilities('codex')).toEqual({
agentType: 'codex',
supportsShellPreflightHook: false,
supportsReadonlySandboxing: false,
supportsGitWriteGuard: true,
supportsHardMutationBlocking: false,
});
});
it('builds reviewer runtime env for normal isolated runs', () => {
expect(
buildPairedReadonlyRuntimeEnvOverrides({
role: 'reviewer',
agentType: 'claude-code',
unsafeHostPairedMode: false,
}),
).toEqual({
EJCLAW_REVIEWER_RUNTIME: '1',
});
});
it('builds claude host reviewer env with explicit readonly flag', () => {
expect(
buildPairedReadonlyRuntimeEnvOverrides({
role: 'reviewer',
agentType: 'claude-code',
unsafeHostPairedMode: true,
}),
).toEqual({
EJCLAW_UNSAFE_HOST_PAIRED_MODE: '1',
EJCLAW_CLAUDE_REVIEWER_READONLY: '1',
});
});
it('leaves codex host reviewer mode without fake hard-block flags', () => {
expect(
buildPairedReadonlyRuntimeEnvOverrides({
role: 'reviewer',
agentType: 'codex',
unsafeHostPairedMode: true,
}),
).toEqual({
EJCLAW_UNSAFE_HOST_PAIRED_MODE: '1',
});
});
it('keeps arbiter runtime routing unchanged', () => {
expect(
buildPairedReadonlyRuntimeEnvOverrides({
role: 'arbiter',
agentType: 'claude-code',
unsafeHostPairedMode: false,
}),
).toEqual({
EJCLAW_ARBITER_RUNTIME: '1',
});
});
});