fix: keep dave chat responsive on llm failures
This commit is contained in:
@@ -117,6 +117,41 @@ test('Telegram transport routes authorized free text as plain-text AI reply', as
|
||||
assert.equal(sent.body.reply_to_message_id, 11);
|
||||
});
|
||||
|
||||
test('Telegram AI handler failures return short DAVE fallback messages', async () => {
|
||||
const alerter = new TelegramAlerter({ botToken: 'test-token', chatId: '42' });
|
||||
const requests = [];
|
||||
alerter.onMessage(async () => {
|
||||
throw new Error('The operation was aborted due to timeout');
|
||||
});
|
||||
|
||||
const originalFetch = globalThis.fetch;
|
||||
globalThis.fetch = async (url, options = {}) => {
|
||||
requests.push({ url, body: options.body ? JSON.parse(options.body) : null });
|
||||
if (url.includes('/getUpdates')) {
|
||||
return {
|
||||
ok: true,
|
||||
json: async () => ({
|
||||
ok: true,
|
||||
result: [{ update_id: 1, message: { message_id: 10, text: 'Noch da?', chat: { id: 42 } } }],
|
||||
}),
|
||||
};
|
||||
}
|
||||
return { ok: true, json: async () => ({ ok: true, result: { message_id: requests.length } }) };
|
||||
};
|
||||
|
||||
try {
|
||||
await alerter._pollUpdates();
|
||||
} finally {
|
||||
globalThis.fetch = originalFetch;
|
||||
}
|
||||
|
||||
const sent = requests.filter(request => request.url.includes('/sendMessage'));
|
||||
assert.equal(sent.length, 2);
|
||||
assert.match(sent[0].body.text, /Dave bekommt gerade keine stabile Antwort/);
|
||||
assert.doesNotMatch(sent.map(item => item.body.text).join('\n'), /AI chat failed|Please try again/i);
|
||||
assert.equal(sent[0].body.reply_to_message_id, 10);
|
||||
});
|
||||
|
||||
test('Telegram conversation delivery strips visible markdown and splits large AI replies', async () => {
|
||||
const alerter = new TelegramAlerter({ botToken: 'test-token', chatId: '42' });
|
||||
const requests = [];
|
||||
@@ -209,8 +244,8 @@ test('Telegram tiered fallback alert is sent as conversational DAVE messages', a
|
||||
|
||||
const texts = requests.filter(request => request.url.includes('/sendMessage')).map(request => request.body.text);
|
||||
assert.ok(texts.length >= 2);
|
||||
assert.match(texts[0], /^\[DAVE \/\/ HINWEIS\]/);
|
||||
assert.doesNotMatch(texts.join('\n'), /CRUCIX ROUTINE|Confidence:|Direction:|Cross-correlation:|Werkzeuge|Tools used/);
|
||||
assert.match(texts[0], /^Kurzer Hinweis:/);
|
||||
assert.doesNotMatch(texts.join('\n'), /\[DAVE \/\/|CRUCIX ROUTINE|Confidence:|Direction:|Cross-correlation:|Werkzeuge|Tools used/);
|
||||
for (const text of texts) assert.ok(text.length <= 380, `alert chunk too long: ${text.length}`);
|
||||
});
|
||||
|
||||
|
||||
@@ -132,6 +132,52 @@ test('repeated tool calls during finalization fail closed without leaking protoc
|
||||
assert.equal(result.notify, false);
|
||||
});
|
||||
|
||||
test('empty model responses are repaired instead of shown to the operator', async () => {
|
||||
let call = 0;
|
||||
const agent = new TerminalAgent({
|
||||
provider: {
|
||||
isConfigured: true,
|
||||
async complete() {
|
||||
call++;
|
||||
return call === 1
|
||||
? { text: '' }
|
||||
: { text: JSON.stringify({ type: 'final', answer: 'Ich pruefe den letzten Sweep weiter.', confidence: 'low', evidence: [], notify: false, priority: 'routine' }) };
|
||||
},
|
||||
},
|
||||
registry: new TerminalToolRegistry([]),
|
||||
});
|
||||
|
||||
const result = await agent.run('Ist noch woanders was passiert?', { preferredLanguage: 'de' });
|
||||
assert.equal(result.answer, 'Ich pruefe den letzten Sweep weiter.');
|
||||
assert.doesNotMatch(result.answer, /no usable response/i);
|
||||
});
|
||||
|
||||
test('provider timeout falls back to the local sweep snapshot', async () => {
|
||||
const agent = new TerminalAgent({
|
||||
provider: {
|
||||
isConfigured: true,
|
||||
async complete() {
|
||||
throw new Error('The operation was aborted due to timeout');
|
||||
},
|
||||
},
|
||||
registry: new TerminalToolRegistry([]),
|
||||
});
|
||||
const context = JSON.stringify({
|
||||
news: [{ title: 'Regional escalation report' }],
|
||||
urgentOsint: ['OSINT: power outage near border'],
|
||||
degradedSources: [{ name: 'GDELT' }, { name: 'ACLED' }],
|
||||
ideas: [{ title: 'Gold hedge' }],
|
||||
});
|
||||
|
||||
const result = await agent.run('Auch auf andere Teile der Welt', { preferredLanguage: 'de', context });
|
||||
assert.match(result.answer, /keine stabile Modellantwort/);
|
||||
assert.match(result.answer, /letzten lokalen Sweep/);
|
||||
assert.match(result.answer, /OSINT: power outage near border/);
|
||||
assert.match(result.answer, /Steuern kann ich per Chat/);
|
||||
assert.equal(result.trace[0].tool, 'llm');
|
||||
assert.equal(result.trace[0].status, 'failed');
|
||||
});
|
||||
|
||||
test('agent propagates preferred German language through tool and finalization prompts', async () => {
|
||||
const prompts = [];
|
||||
let call = 0;
|
||||
|
||||
Reference in New Issue
Block a user