import { describe, it, expect } from 'vitest'; import { shouldUseWarmBackend, isTurnOkForStopReason } from '../warm-acp-routing.js'; /** * Phase 2 routing predicate: which goose/qwen tasks go to the warm pool backend * vs the existing one-shot ACP path. * * The warm backend is keyed (chat_id, agent) — the persistent context unit (same * as opencode-server). A task only routes warm when it carries BOTH a session_id * and a chat_id, i.e. it originates from a real chat tab (the coder message route * stamps both). Session-less creators (arena, MCP-created, generic /api/tasks, * new_task) lack chat_id/session_id and keep the one-shot worktree-per-task path, * which never spawns a warm process. */ describe('shouldUseWarmBackend (Phase 2 routing)', () => { it('routes a chat-tab task (session_id + chat_id) to the warm backend', () => { expect(shouldUseWarmBackend({ agent: 'qwen', session_id: 's1', chat_id: 'c1' })).toBe(true); expect(shouldUseWarmBackend({ agent: 'goose', session_id: 's1', chat_id: 'c1' })).toBe(true); }); it('keeps a session-less arena/MCP task on the one-shot path', () => { expect(shouldUseWarmBackend({ agent: 'qwen', session_id: null, chat_id: null })).toBe(false); }); it('keeps a task with a session but no chat on the one-shot path', () => { // chat_id is the warm-key half; without it ensureSession would get a degenerate // (null, agent) key, so fall back to one-shot rather than synthesize a chat. expect(shouldUseWarmBackend({ agent: 'goose', session_id: 's1', chat_id: null })).toBe(false); }); it('keeps a task with a chat but no session on the one-shot path', () => { expect(shouldUseWarmBackend({ agent: 'qwen', session_id: null, chat_id: 'c1' })).toBe(false); }); it('only applies to warm-capable agents (goose, qwen); others never warm here', () => { // opencode has its own dedicated warm path; native/claude/etc. are not ACP-warm. expect(shouldUseWarmBackend({ agent: 'opencode', session_id: 's1', chat_id: 'c1' })).toBe(false); expect(shouldUseWarmBackend({ agent: 'claude', session_id: 's1', chat_id: 'c1' })).toBe(false); expect(shouldUseWarmBackend({ agent: null, session_id: 's1', chat_id: 'c1' })).toBe(false); }); }); describe('isTurnOkForStopReason (ACP stop-reason → ok/fail)', () => { it('treats normal completions as ok', () => { expect(isTurnOkForStopReason('end_turn')).toBe(true); expect(isTurnOkForStopReason('max_tokens')).toBe(true); expect(isTurnOkForStopReason('max_turn_requests')).toBe(true); }); it('treats refusal and cancelled as failures', () => { expect(isTurnOkForStopReason('refusal')).toBe(false); expect(isTurnOkForStopReason('cancelled')).toBe(false); }); it('defaults an absent stop reason to a successful end_turn', () => { expect(isTurnOkForStopReason(undefined)).toBe(true); expect(isTurnOkForStopReason(null)).toBe(true); }); });