feat: harden intelligence runtime and llm providers

This commit is contained in:
2026-05-16 21:18:34 +02:00
parent 7e85a54c32
commit 85f97bb2a6
22 changed files with 732 additions and 201 deletions

View File

@@ -12,6 +12,7 @@ import { exec } from 'child_process';
import config from '../crucix.config.mjs';
import { createLLMProvider } from '../lib/llm/index.mjs';
import { generateLLMIdeas } from '../lib/llm/ideas.mjs';
import { safeFetchText } from '../apis/utils/fetch.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ROOT = join(__dirname, '..');
@@ -146,10 +147,14 @@ function loadOpenSkyFallback(currentTimestamp) {
}
// === RSS Fetching ===
const rssHealth = {};
async function fetchRSS(url, source) {
const started = Date.now();
try {
const res = await fetch(url, { signal: AbortSignal.timeout(8000) });
const xml = await res.text();
const fetched = await safeFetchText(url, { signal: AbortSignal.timeout(8000), source: `RSS:${source}`, timeout: 12000, retries: 1 });
if (fetched.error) throw new Error(fetched.error);
const xml = fetched.text;
const items = [];
const itemRegex = /<item>([\s\S]*?)<\/item>/g;
let match;
@@ -160,9 +165,11 @@ async function fetchRSS(url, source) {
const pubDate = block.match(/<pubDate>(.*?)<\/pubDate>/)?.[1] || '';
if (title && title !== source) items.push({ title, date: pubDate, source, url: link || undefined });
}
rssHealth[source] = { status: items.length ? 'ok' : 'degraded', items: items.length, durationMs: Date.now() - started, url };
return items;
} catch (e) {
console.log(`RSS fetch failed (${source}):`, e.message);
rssHealth[source] = { status: 'failed', items: 0, durationMs: Date.now() - started, url, error: e.message };
return [];
}
}
@@ -176,6 +183,7 @@ const RSS_SOURCE_FALLBACKS = {
const REGIONAL_NEWS_SOURCES = ['MercoPress', 'Indian Express', 'The Hindu', 'SBS Australia'];
export async function fetchAllNews() {
for (const key of Object.keys(rssHealth)) delete rssHealth[key];
const feeds = [
// Global
['http://feeds.bbci.co.uk/news/world/rss.xml', 'BBC'],
@@ -256,6 +264,10 @@ export async function fetchAllNews() {
return selected.slice(0, 50);
}
export function getRSSHealth() {
return { ...rssHealth };
}
// === Leverageable Ideas from Signals ===
export function generateIdeas(V2) {
const ideas = [];
@@ -540,7 +552,12 @@ export async function synthesize(data) {
};
const health = Object.entries(data.sources).map(([name, src]) => ({
n: name, err: Boolean(src.error), stale: Boolean(src.stale)
n: name,
status: data.timing?.[name]?.status || (src.error ? 'degraded' : 'ok'),
err: Boolean(src.error || data.timing?.[name]?.status === 'error'),
stale: Boolean(src.stale),
message: src.error || src.message || data.timing?.[name]?.error || null,
ms: data.timing?.[name]?.ms || 0,
}));
// === Yahoo Finance live market data ===
@@ -595,6 +612,7 @@ export async function synthesize(data) {
// Fetch RSS
const news = await fetchAllNews();
const newsHealth = getRSSHealth();
const V2 = {
meta: data.crucix, air, thermal, tSignals, chokepoints, nuke, nukeSignals,
@@ -610,6 +628,11 @@ export async function synthesize(data) {
tg: { posts: tgData.totalPosts || 0, urgent: tgUrgent, topPosts: tgTop },
who, fred, energy, metals, bls, treasury, gscpi, defense, noaa, epa, acled, gdelt, space, health, news,
markets, // Live Yahoo Finance market data
newsMeta: {
rssHealth: newsHealth,
rssOk: Object.values(newsHealth).filter(s => s.status === 'ok').length,
rssFailed: Object.values(newsHealth).filter(s => s.status === 'failed').length,
},
ideas: [], ideasSource: 'disabled',
// newsFeed for ticker (merged RSS + GDELT + Telegram)
newsFeed: buildNewsFeed(news, gdeltData, tgUrgent, tgTop),