diff --git a/apps/mana/apps/web/src/lib/data/events/catalog.ts b/apps/mana/apps/web/src/lib/data/events/catalog.ts index ae42400e5..53755d47f 100644 --- a/apps/mana/apps/web/src/lib/data/events/catalog.ts +++ b/apps/mana/apps/web/src/lib/data/events/catalog.ts @@ -543,6 +543,33 @@ export interface SleepLoggedPayload { } export type SleepEventType = 'SleepLogged'; +// ── Companion (Chat + Tools) ──────────────────────── + +export interface CompanionConversationStartedPayload { + conversationId: string; + title?: string; +} + +export interface CompanionMessageSentPayload { + messageId: string; + conversationId: string; + role: 'user' | 'assistant'; + contentLength: number; +} + +export interface CompanionToolCalledPayload { + tool: string; + module: string; + success: boolean; + latencyMs: number; + errorMessage?: string; +} + +export type CompanionEventType = + | 'CompanionConversationStarted' + | 'CompanionMessageSent' + | 'CompanionToolCalled'; + // ── Body ──────────────────────────────────────────── export interface WorkoutStartedPayload { @@ -637,6 +664,7 @@ export type ManaEventType = | QuestionsEventType | MeditateEventType | SleepEventType + | CompanionEventType | SocialEventsEventType | BodyEventType | SystemEventType; @@ -742,6 +770,10 @@ export type ManaEvent = | DomainEvent<'MeditationCompleted', MeditationCompletedPayload> // Sleep | DomainEvent<'SleepLogged', SleepLoggedPayload> + // Companion + | DomainEvent<'CompanionConversationStarted', CompanionConversationStartedPayload> + | DomainEvent<'CompanionMessageSent', CompanionMessageSentPayload> + | DomainEvent<'CompanionToolCalled', CompanionToolCalledPayload> // Social Events | DomainEvent<'SocialEventCreated', SocialEventCreatedPayload> | DomainEvent<'SocialEventDeleted', SocialEventDeletedPayload> diff --git a/apps/mana/apps/web/src/lib/modules/companion/engine.ts b/apps/mana/apps/web/src/lib/modules/companion/engine.ts index 5def48188..cf19b46b2 100644 --- a/apps/mana/apps/web/src/lib/modules/companion/engine.ts +++ b/apps/mana/apps/web/src/lib/modules/companion/engine.ts @@ -11,6 +11,8 @@ import { generateContextDocument } from '$lib/data/projections/context-document' import { getToolsForLlm, executeTool } from '$lib/data/tools'; import { authStore } from '$lib/stores/auth.svelte'; import type { DaySnapshot, StreakInfo } from '$lib/data/projections/types'; +import { emitDomainEvent } from '$lib/data/events'; +import { getTool } from '$lib/data/tools/registry'; import type { LocalMessage } from './types'; import type { ToolResult } from '$lib/data/tools/types'; @@ -230,8 +232,21 @@ export async function runCompanionChat( break; } - // Execute the tool + // Execute the tool with timing + const toolStartedAt = Date.now(); const toolResult = await executeTool(toolCall.name, toolCall.params); + const toolLatencyMs = Date.now() - toolStartedAt; + + // Emit observability event for the tool call + const toolDef = getTool(toolCall.name); + emitDomainEvent('CompanionToolCalled', 'companion', 'tools', toolCall.name, { + tool: toolCall.name, + module: toolDef?.module ?? 'unknown', + success: toolResult.success, + latencyMs: toolLatencyMs, + errorMessage: toolResult.success ? undefined : toolResult.message, + }); + toolCalls.push({ name: toolCall.name, params: toolCall.params, result: toolResult }); // Build response text from before/after the tool block diff --git a/apps/mana/apps/web/src/lib/modules/companion/stores/chat.svelte.ts b/apps/mana/apps/web/src/lib/modules/companion/stores/chat.svelte.ts index 01ea0df92..a1a9f9850 100644 --- a/apps/mana/apps/web/src/lib/modules/companion/stores/chat.svelte.ts +++ b/apps/mana/apps/web/src/lib/modules/companion/stores/chat.svelte.ts @@ -7,6 +7,7 @@ */ import { db } from '$lib/data/database'; +import { emitDomainEvent } from '$lib/data/events'; import type { LocalConversation, LocalMessage } from '../types'; const CONV_TABLE = 'companionConversations'; @@ -24,6 +25,10 @@ export const chatStore = { updatedAt: now, }; await db.table(CONV_TABLE).add(conv); + emitDomainEvent('CompanionConversationStarted', 'companion', CONV_TABLE, conv.id, { + conversationId: conv.id, + title: conv.title, + }); return conv; }, @@ -68,6 +73,16 @@ export const chatStore = { updatedAt: msg.createdAt, }); + // Emit event only for actual user/assistant messages, not tool plumbing + if (role === 'user' || role === 'assistant') { + emitDomainEvent('CompanionMessageSent', 'companion', MSG_TABLE, msg.id, { + messageId: msg.id, + conversationId, + role, + contentLength: content.length, + }); + } + return msg; }, diff --git a/apps/mana/apps/web/src/lib/modules/eventstream/ListView.svelte b/apps/mana/apps/web/src/lib/modules/eventstream/ListView.svelte index 8aa6fef2c..6a181772a 100644 --- a/apps/mana/apps/web/src/lib/modules/eventstream/ListView.svelte +++ b/apps/mana/apps/web/src/lib/modules/eventstream/ListView.svelte @@ -14,6 +14,8 @@ ForkKnife, MapPin, Lightning, + Robot, + ChatCircle, } from '@mana/shared-icons'; let events = $state([]); @@ -31,6 +33,9 @@ MealFromPhotoLogged: { icon: ForkKnife, color: '#F97316' }, PlaceVisited: { icon: MapPin, color: '#A855F7' }, PlaceCreated: { icon: MapPin, color: '#10B981' }, + CompanionConversationStarted: { icon: ChatCircle, color: '#8B5CF6' }, + CompanionMessageSent: { icon: ChatCircle, color: '#A78BFA' }, + CompanionToolCalled: { icon: Robot, color: '#6366F1' }, }; const EVENT_LABELS: Record) => string> = { @@ -55,6 +60,11 @@ TrackingStarted: () => 'Tracking gestartet', TrackingStopped: () => 'Tracking gestoppt', GoalReached: (p) => `Ziel erreicht: "${p.title}"`, + CompanionConversationStarted: (p) => + `Companion-Chat gestartet${p.title ? `: "${p.title}"` : ''}`, + CompanionMessageSent: (p) => + `${p.role === 'user' ? 'Du' : 'Companion'}: ${p.contentLength} Zeichen`, + CompanionToolCalled: (p) => `Tool: ${p.tool}${p.success ? '' : ' (Fehler)'} (${p.latencyMs}ms)`, }; function formatTime(iso: string): string {