feat: make DAVE Telegram chat conversational
All checks were successful
Codex Template Compliance / template-compliance (pull_request) Successful in 8s
Build / test-and-image (pull_request) Successful in 52s

This commit is contained in:
2026-07-07 18:46:44 +02:00
parent c0fb9d1e66
commit a7f3f36d2b
10 changed files with 244 additions and 35 deletions

View File

@@ -117,6 +117,35 @@ test('Telegram transport routes authorized free text as plain-text AI reply', as
assert.equal(sent.body.reply_to_message_id, 11);
});
test('Telegram conversation delivery strips visible markdown and splits large AI replies', async () => {
const alerter = new TelegramAlerter({ botToken: 'test-token', chatId: '42' });
const requests = [];
const originalFetch = globalThis.fetch;
globalThis.fetch = async (url, options = {}) => {
requests.push({ url, body: options.body ? JSON.parse(options.body) : null });
return { ok: true, json: async () => ({ ok: true, result: { message_id: requests.length } }) };
};
try {
const text = '**Aktuelle Lage:**\n\n' + [
'Erster kurzer Absatz mit einer direkten Einschaetzung.',
'Zweiter Absatz mit mehr Kontext und genug Laenge, damit er in einer eigenen Nachricht landet. '.repeat(6),
'Dritter Absatz mit einer Rueckfrage, auf die der Nutzer direkt antworten kann. '.repeat(5),
].join('\n\n');
const result = await alerter.sendConversation(text, { maxChunkChars: 300, delayMs: 0, replyToMessageId: 7 });
assert.equal(result.ok, true);
} finally {
globalThis.fetch = originalFetch;
}
const sent = requests.filter(request => request.url.includes('/sendMessage'));
assert.ok(sent.length >= 2);
assert.doesNotMatch(sent[0].body.text, /\*\*/);
assert.equal('parse_mode' in sent[0].body, false);
assert.equal(sent[0].body.reply_to_message_id, 7);
assert.equal('reply_to_message_id' in sent[1].body, false);
});
test('Telegram OSINT extraction excludes private AI chat messages', () => {
const messages = extractBotChannelMessages([
{ update_id: 1, message: { text: 'private question', chat: { id: 42, type: 'private' } } },

View File

@@ -1,6 +1,7 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { TerminalAgent, TerminalToolRegistry } from '../lib/agent/terminal-agent.mjs';
import { createTerminalToolRegistry } from '../lib/agent/terminal-tools.mjs';
function providerWith(decisions) {
let index = 0;
@@ -214,3 +215,38 @@ test('malformed tagged protocol is repaired instead of returned to the user', as
assert.equal(result.answer, 'I could not run the malformed tool request.');
assert.doesNotMatch(result.answer, /tool_call|call:get_evidence/i);
});
test('terminal registry exposes bounded public web search as a read-only tool', async () => {
const originalFetch = globalThis.fetch;
let requestedUrl = '';
globalThis.fetch = async (url) => {
requestedUrl = String(url);
return {
ok: true,
status: 200,
headers: { get: () => 'application/json' },
text: async () => JSON.stringify({
articles: [{
title: 'Verified report',
sourceCommonName: 'Example News',
url: 'https://example.test/report',
seendate: '20260707100000',
language: 'English',
}],
}),
};
};
try {
const registry = createTerminalToolRegistry();
const tool = registry.get('web_search');
assert.equal(tool.mutating, false);
const result = await registry.execute('web_search', { query: 'Russia attack claim', limit: 3, hours: 24 });
assert.match(requestedUrl, /api\.gdeltproject\.org\/api\/v2\/doc\/doc/);
assert.equal(result.query, 'Russia attack claim');
assert.equal(result.results.length, 1);
assert.equal(result.results[0].url, 'https://example.test/report');
} finally {
globalThis.fetch = originalFetch;
}
});