Merge remote-tracking branch 'origin/codex/production-intelligence-terminal' into codex/issue-17-sse-heartbeat
# Conflicts: # README.md # test/fetch-utils.test.mjs
This commit is contained in:
39
server.mjs
39
server.mjs
@@ -18,6 +18,8 @@ import { TelegramAlerter } from './lib/alerts/telegram.mjs';
|
||||
import { DiscordAlerter } from './lib/alerts/discord.mjs';
|
||||
import { getFetchMetrics } from './apis/utils/fetch.mjs';
|
||||
import { IntelligenceStore } from './lib/intelligence-store.mjs';
|
||||
import { formatStaleAlert, shouldSendStaleAlert } from './lib/stale-alerts.mjs';
|
||||
import { evaluateScenarios } from './lib/scenarios.mjs';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ROOT = __dirname;
|
||||
@@ -39,6 +41,7 @@ let sweepStartedAt = null; // Timestamp when current/last sweep started
|
||||
let sweepInProgress = false;
|
||||
const startTime = Date.now();
|
||||
const sseClients = new Set();
|
||||
const staleAlertState = {};
|
||||
|
||||
// === Delta/Memory ===
|
||||
const memory = new MemoryManager(RUNS_DIR);
|
||||
@@ -425,6 +428,31 @@ function buildHealth() {
|
||||
};
|
||||
}
|
||||
|
||||
async function notifyIfDataStale(context = 'scheduled sweep') {
|
||||
const health = buildHealth();
|
||||
const decision = shouldSendStaleAlert(health, staleAlertState, {
|
||||
cooldownMs: config.staleAlertCooldownMinutes * 60 * 1000,
|
||||
});
|
||||
if (!decision.send) return false;
|
||||
|
||||
const dashboardUrl = config.dashboardUrl || `http://localhost:${config.port}`;
|
||||
const message = formatStaleAlert(health, { dashboardUrl, context });
|
||||
const sends = [];
|
||||
if (telegramAlerter.isConfigured) sends.push(telegramAlerter.sendMessage(message));
|
||||
if (discordAlerter.isConfigured) sends.push(discordAlerter.sendAlert(message));
|
||||
|
||||
if (sends.length === 0) {
|
||||
console.warn('[Crucix] Data is stale but no operator alert channel is configured');
|
||||
return false;
|
||||
}
|
||||
|
||||
const results = await Promise.allSettled(sends);
|
||||
const sent = results.some(r => r.status === 'fulfilled' && (r.value === true || r.value?.ok === true));
|
||||
if (sent) console.warn('[Crucix] Operator stale-data alert sent');
|
||||
else console.warn('[Crucix] Operator stale-data alert attempted but no channel accepted it');
|
||||
return sent;
|
||||
}
|
||||
|
||||
function buildBrief(data) {
|
||||
const verbosity = config.telegram.briefVerbosity || 'standard';
|
||||
const delta = memory.getLastDelta();
|
||||
@@ -461,6 +489,13 @@ function buildBrief(data) {
|
||||
lines.push('', '*Why This Matters*');
|
||||
for (const idea of ideas) lines.push(`- ${idea.title}: ${(idea.rationale || idea.text || '').slice(0, 140)}`);
|
||||
}
|
||||
const scenarioChanges = data.scenarios?.changed || [];
|
||||
if (scenarioChanges.length) {
|
||||
lines.push('', '*Scenario Watchlist*');
|
||||
for (const scenario of scenarioChanges.slice(0, 4)) {
|
||||
lines.push(`- ${scenario.name}: ${scenario.state.toUpperCase()} (${scenario.confidence}% confidence)`);
|
||||
}
|
||||
}
|
||||
lines.push('', '*What To Do Next*', '- Open the dashboard, verify the evidence links, and compare source health before acting.');
|
||||
return lines.join('\n');
|
||||
}
|
||||
@@ -507,6 +542,7 @@ async function runSweepCycle() {
|
||||
// 4. Delta computation + memory
|
||||
const delta = memory.addRun(synthesized);
|
||||
synthesized.delta = delta;
|
||||
synthesized.scenarios = evaluateScenarios(synthesized, delta, RUNS_DIR);
|
||||
|
||||
// 5. LLM-powered trade ideas (LLM-only feature) — isolated so failures don't kill sweep
|
||||
if (llmProvider?.isConfigured) {
|
||||
@@ -567,6 +603,9 @@ async function runSweepCycle() {
|
||||
broadcast({ type: 'sweep_error', error: err.message });
|
||||
} finally {
|
||||
sweepInProgress = false;
|
||||
await notifyIfDataStale(lastSweepError ? 'failed sweep' : 'completed sweep').catch(err => {
|
||||
console.error('[Crucix] Stale-data operator alert failed:', err.message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user