feat(events): Phase 3 — AI tools, Event-Scout template, feedback loop

- Add discover_events (auto) and suggest_event (propose) to shared-ai
  tool catalog. discover_events reads the discovery feed, suggest_event
  creates a proposal to save a discovered event to the user's calendar.
- Add Event-Scout agent template with daily "Events der Woche" mission.
  Policy: discover_events=auto, suggest_event=propose, all else denied.
- Add frontend tool implementations in events/tools.ts — discover_events
  calls the feed API, suggest_event delegates to discoveryStore.saveEvent.
- Add feedback.ts — computes implicit user profile from save/dismiss
  history (category affinity + source quality as 0–2x weight multipliers).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Till JS 2026-04-18 15:37:28 +02:00
parent 2f226a93aa
commit 2c0d866287
5 changed files with 406 additions and 0 deletions

View file

@ -1,5 +1,7 @@
import type { ModuleTool } from '$lib/data/tools/types';
import { eventsStore } from './stores/events.svelte';
import { discoveryStore } from './discovery/store.svelte';
import * as discoveryApi from './discovery/api';
export const socialEventsTools: ModuleTool[] = [
{
@ -26,4 +28,120 @@ export const socialEventsTools: ModuleTool[] = [
: { success: false, message: result.error ?? 'Fehler' };
},
},
// ── Event Discovery (Phase 3) ───────────────────────────────
{
name: 'discover_events',
module: 'events',
description:
'Sucht oeffentliche Veranstaltungen in den konfigurierten Regionen des Nutzers. Gibt Events mit Titel, Datum, Ort, Kategorie und Quelle zurueck.',
parameters: [
{
name: 'query',
type: 'string',
description: 'Optionaler Suchtext (z.B. "Jazz Konzerte")',
required: false,
},
{
name: 'category',
type: 'string',
description: 'Kategorie-Filter',
required: false,
},
{
name: 'days_ahead',
type: 'number',
description: 'Wie viele Tage voraus suchen (Standard: 14)',
required: false,
},
],
async execute(params) {
const daysAhead = (params.days_ahead as number) ?? 14;
const to = new Date(Date.now() + daysAhead * 86_400_000).toISOString();
const feedParams: discoveryApi.FeedParams = {
to,
hideDismissed: true,
limit: 20,
};
if (params.category) feedParams.category = params.category as string;
const result = await discoveryApi.getFeed(feedParams);
const events = result.events.map((e) => ({
id: e.id,
title: e.title,
date: e.startAt,
location: e.location,
category: e.category,
source: e.sourceName,
sourceUrl: e.sourceUrl,
priceInfo: e.priceInfo,
}));
if (events.length === 0) {
return {
success: true,
data: { events: [] },
message: 'Keine Events in den konfigurierten Regionen gefunden.',
};
}
const summary = events
.slice(0, 10)
.map(
(e) =>
`- ${e.title} (${new Date(e.date).toLocaleDateString('de-DE')}${e.location ? `, ${e.location}` : ''})`
)
.join('\n');
return {
success: true,
data: { events, total: result.total },
message: `${events.length} Events gefunden:\n${summary}`,
};
},
},
{
name: 'suggest_event',
module: 'events',
description:
'Schlaegt dem Nutzer ein entdecktes Event vor. Erstellt ein Proposal das der Nutzer bestaetigen muss, um das Event in seinen Kalender zu uebernehmen.',
parameters: [
{
name: 'discovered_event_id',
type: 'string',
description: 'ID des entdeckten Events',
required: true,
},
{
name: 'reason',
type: 'string',
description: 'Begruendung warum dieses Event relevant ist',
required: false,
},
],
async execute(params) {
const eventId = params.discovered_event_id as string;
const reason = params.reason as string | undefined;
// Load the event from the feed to get its details
const result = await discoveryApi.getFeed({ limit: 100 });
const event = result.events.find((e) => e.id === eventId);
if (!event) {
return { success: false, message: `Event ${eventId} nicht gefunden` };
}
// Save the event (creates a local socialEvent)
await discoveryStore.saveEvent(eventId);
const msg = reason
? `Event "${event.title}" vorgeschlagen: ${reason}`
: `Event "${event.title}" vorgeschlagen`;
return {
success: true,
data: { eventId, title: event.title, date: event.startAt },
message: msg,
};
},
},
];