From 51af388650441c66a4f75538cf3e71c232313e58 Mon Sep 17 00:00:00 2001 From: William Valentin Date: Tue, 17 Feb 2026 09:36:32 -0800 Subject: [PATCH] feat(backends): add native fallback and tests for external runner prompt handling --- src/daemon/routing.test.ts | 75 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/src/daemon/routing.test.ts b/src/daemon/routing.test.ts index 907cb3c..cc9debe 100644 --- a/src/daemon/routing.test.ts +++ b/src/daemon/routing.test.ts @@ -1121,6 +1121,81 @@ describe('daemon auto-escalate integration', () => { expect(setModelTierSpy).toHaveBeenCalledWith('complex'); expect(reply).toHaveBeenCalledWith(expect.objectContaining({ text: 'complex-tier response' })); }); + + it('falls back to native processing when external backend fails', async () => { + const processSpy = vi.spyOn(AgentOrchestrator.prototype, 'process') + .mockResolvedValue('native fallback response'); + const history: Array<{ role: 'user' | 'assistant'; content: string }> = []; + const session = { + id: 'telegram:external-fail', + addMessage: vi.fn((msg: { role: 'user' | 'assistant'; content: string }) => { + history.push(msg); + return msg; + }), + getHistory: vi.fn(() => [...history]), + clear: vi.fn(), + replaceHistory: vi.fn(), + getConfig: vi.fn(() => undefined), + setConfig: vi.fn(), + deleteConfig: vi.fn(), + }; + + const externalBackend = { + name: 'codex', + process: vi.fn(async () => { + throw new Error('external failed'); + }), + }; + + const router = createMessageRouter({ + sessionManager: { + getSession: vi.fn(() => session), + } as unknown as MessageRouterDeps['sessionManager'], + modelRouter: { + getAvailableTiers: () => ['fast', 'default', 'complex', 'local'], + getAllLabels: () => ({ fast: 'fast', default: 'default', complex: 'complex', local: 'local' }), + getLabel: (tier: string) => tier, + } as unknown as MessageRouterDeps['modelRouter'], + systemPrompt: 'test prompt', + toolRegistry: { + clone() { return this; }, + register: vi.fn(), + } as unknown as MessageRouterDeps['toolRegistry'], + toolExecutor: {} as unknown as MessageRouterDeps['toolExecutor'], + config: { + agents: { + primary_tier: 'default', + delegation: { + compaction: 'fast', + memory_extraction: 'fast', + classification: 'fast', + tool_summarisation: 'fast', + complex_reasoning: 'complex', + }, + max_delegation_depth: 3, + max_iterations: 10, + auto_escalate: false, + }, + compaction: { enabled: false }, + models: { default: { provider: 'anthropic', model: 'claude' } }, + } as unknown as MessageRouterDeps['config'], + externalBackends: { codex: externalBackend } as unknown as MessageRouterDeps['externalBackends'], + defaultExternalBackendName: 'codex', + }); + + const reply = vi.fn(async (_message: OutboundMessage) => {}); + await router.handler({ + id: 'm-external-fail', + channel: 'telegram', + senderId: 'external-fail', + text: 'hello fallback', + timestamp: Date.now(), + } as MessageRouterInput, reply); + + expect(externalBackend.process).toHaveBeenCalled(); + expect(processSpy).toHaveBeenCalled(); + expect(reply).toHaveBeenCalledWith(expect.objectContaining({ text: 'native fallback response' })); + }); }); describe('daemon talk mode (voice wake) integration', () => {