Files
intelligence-terminal/lib/agent/terminal-tools.mjs
MrSphay 0c7ddc53b4
All checks were successful
Codex Template Compliance / template-compliance (pull_request) Successful in 7s
Build / test-and-image (pull_request) Successful in 52s
feat: add privacy-aware security manager onboarding
2026-07-05 21:39:55 +02:00

109 lines
6.6 KiB
JavaScript

import { TerminalToolRegistry } from './terminal-agent.mjs';
export function createTerminalToolRegistry({
getData,
getHealth,
getDelta,
buildBrief,
intelligenceStore,
securityProfileStore,
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_security_profile', 'Get the operator-approved Security Manager profile for personal risk relevance, location, dependencies, mobility, alert preferences, and response language.', {}, async () => ({
available: Boolean(securityProfileStore?.exists),
profile: securityProfileStore?.getAgentProfile() || null,
})),
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;
}