import { describe, expect, it } from 'vitest'; import { decideOpenCodePromptDeliveryRepair, type OpenCodePromptDeliveryRepairInput, } from '@main/services/team/opencode/delivery/OpenCodePromptDeliveryRepairPolicy'; function base(overrides: Partial = {}) { 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', }) ); 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('"task-1"'); expect(decision.controlText).not.toContain('reportToken='); }); 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"'); }); });