agent-ecosystem/test/main/services/team/OpenCodePromptDeliveryRepairPolicy.test.ts

136 lines
5.1 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
decideOpenCodePromptDeliveryRepair,
type OpenCodePromptDeliveryRepairInput,
} from '@main/services/team/opencode/delivery/OpenCodePromptDeliveryRepairPolicy';
function base(overrides: Partial<OpenCodePromptDeliveryRepairInput> = {}) {
return {
teamName: 'team-a',
memberName: 'alice',
inboxMessageId: 'msg-1',
replyRecipient: 'user',
messageKind: 'default',
actionMode: 'ask',
taskRefs: [],
status: 'responded',
responseState: 'empty_assistant_turn',
attempts: 1,
maxAttempts: 3,
pendingReason: 'empty_assistant_turn',
readAllowed: false,
inboxReadCommitted: false,
visibleReplyFound: false,
hasKnownProgressProof: false,
toolCallNames: [],
acceptanceUnknown: false,
hardFailureKind: 'none',
...overrides,
} satisfies OpenCodePromptDeliveryRepairInput;
}
describe('OpenCodePromptDeliveryRepairPolicy', () => {
it('adds no-assistant response repair without treating it as success', () => {
const decision = decideOpenCodePromptDeliveryRepair(base());
expect(decision.kind).toBe('no_assistant_response');
expect(decision.retryable).toBe(true);
expect(decision.controlText).toContain('You must not end this turn empty.');
expect(decision.controlText).toContain('relayOfMessageId="msg-1"');
});
it('requires member work sync status and report for work-sync nudges', () => {
const decision = decideOpenCodePromptDeliveryRepair(
base({
messageKind: 'member_work_sync_nudge',
actionMode: 'do',
taskRefs: [{ taskId: 'task-1', displayId: '#1', teamName: 'team-a' }],
responseState: 'responded_plain_text',
pendingReason: 'plain_text_ack_only_still_requires_answer',
controlUrl: 'http://127.0.0.1:43123',
})
);
expect(decision.kind).toBe('work_sync_report_required');
expect(decision.controlText).toContain('member_work_sync_status');
expect(decision.controlText).toContain('member_work_sync_report');
expect(decision.controlText).toContain('controlUrl="http://127.0.0.1:43123"');
expect(decision.controlText).toContain('"task-1"');
expect(decision.controlText).not.toContain('reportToken=');
});
it('uses review pickup repair wording for review pickup work-sync nudges', () => {
const decision = decideOpenCodePromptDeliveryRepair(
base({
messageKind: 'member_work_sync_nudge',
workSyncIntent: 'review_pickup',
actionMode: 'do',
taskRefs: [{ taskId: 'task-1', displayId: '#1', teamName: 'team-a' }],
responseState: 'responded_plain_text',
pendingReason: 'plain_text_ack_only_still_requires_answer',
})
);
expect(decision.kind).toBe('work_sync_report_required');
expect(decision.controlText).toContain('review pickup control message');
expect(decision.controlText).toContain('start or continue the review');
expect(decision.controlText).toContain('"task-1"');
expect(decision.controlText).not.toContain('Then call agent-teams_member_work_sync_report');
});
it('repairs visible replies that missed required taskRefs with exact metadata', () => {
const taskRef = { taskId: 'task-refs-1', displayId: 'refs-1', teamName: 'team-a' };
const decision = decideOpenCodePromptDeliveryRepair(
base({
taskRefs: [taskRef],
responseState: 'responded_visible_message',
pendingReason: 'visible_reply_missing_task_refs',
visibleReplyFound: true,
})
);
expect(decision.kind).toBe('missing_visible_reply_correlation');
expect(decision.retryable).toBe(true);
expect(decision.controlText).toContain('relayOfMessageId="msg-1"');
expect(decision.controlText).toContain(
`Include taskRefs exactly as this JSON array: ${JSON.stringify([taskRef])}.`
);
});
it('does not repair terminal, permission, or session failures', () => {
expect(
decideOpenCodePromptDeliveryRepair(
base({ status: 'failed_terminal', responseState: 'empty_assistant_turn' })
)
).toMatchObject({ kind: 'none', retryable: false });
expect(
decideOpenCodePromptDeliveryRepair(
base({ responseState: 'permission_blocked', hardFailureKind: 'permission' })
)
).toMatchObject({ kind: 'none', retryable: false });
expect(
decideOpenCodePromptDeliveryRepair(
base({ responseState: 'session_error', hardFailureKind: 'session' })
)
).toMatchObject({ kind: 'none', retryable: false });
});
it('does not ask to repeat side-effect tools after tool_error', () => {
const decision = decideOpenCodePromptDeliveryRepair(
base({
responseState: 'tool_error',
pendingReason: 'tool_error_without_required_delivery_proof',
toolCallNames: ['bash'],
actionMode: 'do',
taskRefs: [{ taskId: 'task-2', displayId: '#2', teamName: 'team-a' }],
})
);
expect(decision.kind).toBe('progress_proof_required');
expect(decision.controlText).toContain('Do not repeat side-effectful commands');
expect(decision.controlText).toContain('"task-2"');
});
});