import { TerminalToolRegistry } from './terminal-agent.mjs'; export function createTerminalToolRegistry({ getData, getHealth, getDelta, buildBrief, intelligenceStore, triggerSweep, isSweepInProgress, telegramAlerter, } = {}) { const dataFor = runtime => runtime?.data || getData?.() || null; const deltaFor = runtime => runtime?.delta || getDelta?.() || null; return new TerminalToolRegistry([ tool('get_system_status', 'Get server, sweep, LLM, Telegram, source, and memory health.', {}, async () => getHealth?.() || {}), tool('get_latest_brief', 'Build the latest operator intelligence brief.', {}, async (_args, runtime) => { const data = dataFor(runtime); return { brief: data && buildBrief ? buildBrief(data) : 'No completed sweep.' }; }), tool('get_sweep_delta', 'Inspect changes, escalations, de-escalations, and direction from the latest sweep.', {}, async (_args, runtime) => compactDelta(deltaFor(runtime))), tool('get_market_snapshot', 'Get key rates, volatility, energy, metals, and current generated ideas.', {}, async (_args, runtime) => compactMarkets(dataFor(runtime))), tool('get_source_health', 'Inspect healthy, degraded, or failed sources. Optional arguments: status, name, limit.', { status: 'string', name: 'string', limit: 'number' }, async (args, runtime) => { const data = dataFor(runtime); const status = clean(args.status, 30).toLowerCase(); const name = clean(args.name, 80).toLowerCase(); const limit = bounded(args.limit, 1, 25, 12); return (data?.sourceHealth || data?.health || []) .filter(item => !status || String(item.status || '').toLowerCase() === status) .filter(item => !name || String(item.name || item.n || '').toLowerCase().includes(name)) .slice(0, limit) .map(item => ({ name: item.name || item.n, status: item.status, ms: item.ms, error: clean(item.error || item.message, 240) || null })); }), tool('get_evidence', 'Search recent news, feed items, and urgent OSINT. Arguments: query, limit.', { query: 'string', limit: 'number' }, async (args, runtime) => { const data = dataFor(runtime); const query = clean(args.query, 120).toLowerCase(); const limit = bounded(args.limit, 1, 20, 8); const rows = [ ...(data?.news || []), ...(data?.newsFeed || []), ...(data?.tg?.urgent || []).map(item => ({ ...item, title: item.text, source: item.source || 'Telegram OSINT' })), ]; return rows.filter(item => !query || `${item.headline || item.title || item.text || ''} ${item.source || ''}`.toLowerCase().includes(query)) .slice(0, limit) .map(item => ({ title: clean(item.headline || item.title || item.text, 400), source: clean(item.source, 100), url: clean(item.url, 500) || null, timestamp: item.timestamp || item.date || null })); }), tool('search_memory', 'Search persisted cross-sweep events. Arguments: query, limit.', { query: 'string', limit: 'number' }, async args => intelligenceStore?.queryMemory({ q: clean(args.query, 120), limit: bounded(args.limit, 1, 25, 8) }) || { available: false }), tool('list_predictions', 'List persisted predictions and their current outcome states. Arguments: state, limit.', { state: 'string', limit: 'number' }, async args => intelligenceStore?.listPredictions({ state: clean(args.state, 30) || null, limit: bounded(args.limit, 1, 25, 8) }) || { available: false }), tool('get_scenarios', 'Inspect current scenario watchlist states and confidence.', {}, async (_args, runtime) => { const scenarios = dataFor(runtime)?.scenarios || {}; return { summary: scenarios.summary || null, items: (scenarios.items || scenarios.scenarios || []).slice(0, 20), changed: (scenarios.changed || []).slice(0, 10) }; }), tool('get_trade_ideas', 'Inspect current LLM-generated ideas. Optional argument: ticker.', { ticker: 'string' }, async (args, runtime) => { const ticker = clean(args.ticker, 30).toLowerCase(); return (dataFor(runtime)?.ideas || []).filter(item => !ticker || String(item.ticker || '').toLowerCase().includes(ticker)).slice(0, 10); }), tool('trigger_sweep', 'Start a new full intelligence sweep.', {}, async (_args, runtime) => { if (!runtime.confirmed) throw new Error('Operator confirmation required'); if (isSweepInProgress?.()) return { accepted: false, status: 'already_running' }; triggerSweep?.(); return { accepted: true, status: 'started' }; }, true), tool('mute_alerts', 'Mute proactive Telegram alerts for a bounded number of hours.', { hours: 'number' }, async (args, runtime) => { if (!runtime.confirmed) throw new Error('Operator confirmation required'); const hours = Math.max(0.25, Math.min(24, Number(args.hours) || 1)); telegramAlerter?.muteAlerts(hours); return { muted: true, hours }; }, true), tool('unmute_alerts', 'Resume proactive Telegram alerts.', {}, async (_args, runtime) => { if (!runtime.confirmed) throw new Error('Operator confirmation required'); telegramAlerter?.unmuteAlerts(); return { muted: false }; }, true), ]); } function tool(name, description, parameters, handler, mutating = false) { return { name, description, parameters, handler, mutating }; } function compactDelta(delta) { return { summary: delta?.summary || null, new: (delta?.signals?.new || []).slice(0, 15), escalated: (delta?.signals?.escalated || []).slice(0, 15), deescalated: (delta?.signals?.deescalated || []).slice(0, 15), }; } function compactMarkets(data) { if (!data) return { available: false }; const fred = Object.fromEntries((data.fred || []).filter(item => ['VIXCLS', 'DFF', 'DGS10', 'DGS2', 'T10Y2Y', 'BAMLH0A0HYM2'].includes(item.id)).map(item => [item.id, item.value])); return { available: true, generatedAt: data.meta?.generatedAt || data.meta?.timestamp, fred, energy: data.energy || null, metals: data.metals || null, ideasSource: data.ideasSource, ideas: (data.ideas || []).slice(0, 8) }; } function clean(value, maxLength) { return String(value || '').replace(/[\u0000-\u001f]/g, ' ').trim().slice(0, maxLength); } function bounded(value, min, max, fallback) { const parsed = Number.parseInt(value, 10); return Number.isFinite(parsed) ? Math.max(min, Math.min(max, parsed)) : fallback; }