mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 18:41:08 +02:00
feat(brain): emit Companion chat + tool events for observability
Closes the gap where the Companion module wrote messages directly to
IndexedDB without participating in the Domain Event stream — chat
activity, tool invocations and conversation creation were invisible
to the Event Stream page, Goals, Streaks, and Memory layer.
New events (3 types):
- CompanionConversationStarted: emitted on chatStore.createConversation
- CompanionMessageSent: emitted on user/assistant messages (skips
empty tool plumbing messages)
- CompanionToolCalled: emitted in engine.runCompanionChat after every
tool execution, with tool name, source module, success/failure,
latency in ms, and error message on failure
Event Stream page updated with icons (ChatCircle, Robot) and German
labels for the three new event types so they appear inline with all
other domain activity.
Now possible (future iterations):
- Goals like "5x Companion-Chat pro Woche"
- Streaks for daily Companion usage
- Tool performance analytics ("create_task hat 3% Fehlerrate")
- Memory facts about which tools the user uses most
Total: 70 event types, 51 tools across 31 modules.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ff2cfcdac
commit
4192a4bd9b
4 changed files with 73 additions and 1 deletions
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<LocalConversation>(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;
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@
|
|||
ForkKnife,
|
||||
MapPin,
|
||||
Lightning,
|
||||
Robot,
|
||||
ChatCircle,
|
||||
} from '@mana/shared-icons';
|
||||
|
||||
let events = $state<DomainEvent[]>([]);
|
||||
|
|
@ -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, (p: Record<string, unknown>) => 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 {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue