AUDIT / src /lib /agent /ProactiveAgent.ts
Arypulka98's picture
feat(audit): deploy full backend cluster node (part 2)
cc11e77 verified
Raw
History Blame Contribute Delete
7.25 kB
/**
* ProactiveAgent.ts — Context-aware proactive suggestions (FASE 6)
*
* Monitors conversation + time + memory to proactively suggest actions.
* No LLM required — pure pattern matching + temporal analysis.
*
* Triggers:
* - Time-based: "Good morning" greeting, "notizie del giorno" suggestion at 8am
* - Staleness: detects when a prior tool result might be outdated
* - Continuity: detects when the user might want to continue a prior task
* - Gaps: detects missing context that the agent could fill in advance
*/
import { agentMemory } from "../agentMemory";
import { sessionMemory } from "./MemoryUpdater";
export interface ProactiveSuggestion {
id: string;
text: string;
reason: string;
priority: "high" | "medium" | "low";
tool?: string;
args?: Record<string, unknown>;
expires?: number; // unix ms
}
// ── Time helpers ──────────────────────────────────────────────────────────────
function hourNow(): number { return new Date().getHours(); }
function isWeekday(): boolean { return new Date().getDay() >= 1 && new Date().getDay() <= 5; }
// ── Pattern matchers ───────────────────────────────────────�����──────────────────
export const NEWS_STALE_MS = 4 * 60 * 60 * 1000; // 4h
export const WEATHER_STALE_MS = 6 * 60 * 60 * 1000; // 6h
// ── Evaluators ────────────────────────────────────────────────────────────────
function checkTimeBased(): ProactiveSuggestion[] {
const h = hourNow();
const suggestions: ProactiveSuggestion[] = [];
if (h >= 7 && h <= 9) {
suggestions.push({
id: "morning-news",
text: "Vuoi che ti mostri le notizie del mattino?",
reason: "È mattina — potrebbe essere utile un riepilogo",
priority: "medium",
tool: "get_news",
args: { query: "" },
expires: Date.now() + 2 * 60 * 60 * 1000,
});
}
if (h >= 9 && h <= 10 && isWeekday()) {
suggestions.push({
id: "morning-currency",
text: "Controlla i tassi di cambio di apertura mercati?",
reason: "Apertura mercati finanziari",
priority: "low",
tool: "get_currency",
args: { from: "EUR", to: "USD", amount: 1 },
expires: Date.now() + 3 * 60 * 60 * 1000,
});
}
if (h >= 18 && h <= 20) {
suggestions.push({
id: "evening-summary",
text: "Vuoi un riepilogo delle notizie della giornata?",
reason: "Orario serale — riepilogo giornaliero",
priority: "low",
tool: "get_news",
args: { query: "oggi" },
expires: Date.now() + 2 * 60 * 60 * 1000,
});
}
return suggestions;
}
function checkStaleData(): ProactiveSuggestion[] {
const suggestions: ProactiveSuggestion[] = [];
const now = Date.now();
// S91: usa agentMemory.list() (Dexie-backed, in-memory cache) invece di localStorage raw.
// Evita deserializzazione JSON ad ogni check + accede al layer cache in-memory già caricato.
const allMemory = agentMemory.list();
// Check for stale weather
const weatherEntry = allMemory.find(e =>
e.key.startsWith("meteo:") || e.key.startsWith("weather:") ||
e.category === "weather" || e.category === "meteo"
);
if (weatherEntry && now - weatherEntry.updatedAt > WEATHER_STALE_MS) {
const val = weatherEntry.value;
const cityMatch = val.match(/a\s+(\w+)/i);
suggestions.push({
id: "stale-weather",
text: `Aggiorna meteo${cityMatch ? ` per ${cityMatch[1]}` : ""}?`,
reason: "Dati meteo aggiornati più di 6 ore fa",
priority: "low",
tool: "get_weather",
args: cityMatch ? { city: cityMatch[1] } : {},
expires: now + 60 * 60 * 1000,
});
}
// Check for stale news
const newsEntry = allMemory.find(e =>
e.key.startsWith("news:") || e.key.startsWith("notizie:") ||
e.category === "news" || e.category === "notizie"
);
if (newsEntry && now - newsEntry.updatedAt > NEWS_STALE_MS) {
suggestions.push({
id: "stale-news",
text: "Le ultime notizie salvate sono vecchie — aggiorno?",
reason: "Notizie aggiornate più di 4 ore fa",
priority: "medium",
tool: "get_news",
args: { query: "" },
expires: now + 30 * 60 * 1000,
});
}
return suggestions;
}
function checkContinuity(): ProactiveSuggestion[] {
const suggestions: ProactiveSuggestion[] = [];
// Look at session memory for interrupted tasks
const summary = sessionMemory.summarize();
if (!summary) return suggestions;
const hasCode = /run_code|python|javascript/i.test(summary);
const hasFile = /write_file|read_file/i.test(summary);
const hasResearch = /web_search|wikipedia/i.test(summary);
if (hasCode) {
suggestions.push({
id: "continue-code",
text: "Continuare con il codice della sessione precedente?",
reason: "Rilevata sessione di coding in corso",
priority: "medium",
expires: Date.now() + 10 * 60 * 1000,
});
}
if (hasFile && !hasCode) {
suggestions.push({
id: "continue-file",
text: "Mostra i file modificati di recente?",
reason: "Rilevata operazione su file",
priority: "low",
tool: "read_file",
expires: Date.now() + 10 * 60 * 1000,
});
}
if (hasResearch) {
suggestions.push({
id: "continue-research",
text: "Approfondire la ricerca con Wikipedia?",
reason: "Ricerca in corso nella sessione",
priority: "low",
tool: "search_wikipedia",
expires: Date.now() + 15 * 60 * 1000,
});
}
return suggestions;
}
// ── Public API ────────────────────────────────────────────────────────────────
let _lastGenerated = 0;
const COOLDOWN_MS = 5 * 60 * 1000; // don't spam — max once every 5min
/**
* Generate proactive suggestions based on current context.
* Returns an empty array if called too frequently.
*/
export function getProactiveSuggestions(maxResults = 3): ProactiveSuggestion[] {
const now = Date.now();
if (now - _lastGenerated < COOLDOWN_MS) return [];
_lastGenerated = now;
try {
const all = [
...checkTimeBased(),
...checkStaleData(),
...checkContinuity(),
];
// Sort by priority, filter expired
const valid = all
.filter(s => !s.expires || s.expires > now)
.sort((a, b) => {
const P = { high: 0, medium: 1, low: 2 };
return P[a.priority] - P[b.priority];
});
return valid.slice(0, maxResults);
} catch {
return [];
}
}
/**
* Lightweight version that just checks time (safe to call on every app load).
*/
export function getTimeBasedGreeting(): string {
const h = hourNow();
if (h >= 5 && h < 12) return "Buongiorno";
if (h >= 12 && h < 18) return "Buon pomeriggio";
if (h >= 18 && h < 22) return "Buona sera";
return "Buonanotte";
}