refactor(vibe): simplify durable session flow
This commit is contained in:
@@ -1,12 +1,6 @@
|
||||
import { DbService, VibeEvent, VibePlan, VibeSession } from './db.service.js';
|
||||
import { SessionDirector } from './session-director.service.js';
|
||||
import { Candidate } from './generators.service.js';
|
||||
import {
|
||||
initialVibeState,
|
||||
normalizeVibeContext,
|
||||
normalizeVibeEventPayload,
|
||||
VibeContext,
|
||||
} from './vibe-context.service.js';
|
||||
|
||||
/**
|
||||
* This is deliberately a narrow bridge between the durable Vibe ledger and
|
||||
@@ -16,7 +10,7 @@ import {
|
||||
export const DEFAULT_VIBE_POLICY_VERSION = 'vibe-v2-initial';
|
||||
|
||||
export const VIBE_EVENT_TYPES = [
|
||||
'session_started', 'session_resumed', 'session_ended', 'context_changed',
|
||||
'session_started', 'session_resumed', 'session_ended',
|
||||
'plan_published', 'track_served', 'playback_started', 'progress', 'completed',
|
||||
'skipped', 'disliked', 'kept', 'favourite_added', 'queue_removed',
|
||||
'manual_search', 'album_opened', 'artist_opened', 'playlist_added',
|
||||
@@ -27,9 +21,6 @@ export type VibeEventType = (typeof VIBE_EVENT_TYPES)[number];
|
||||
|
||||
export interface StartVibeSessionInput {
|
||||
seedTrackId?: string;
|
||||
context?: VibeContext | Record<string, unknown>;
|
||||
intent?: string;
|
||||
policyVersion?: string;
|
||||
resumeSessionId?: string;
|
||||
}
|
||||
|
||||
@@ -79,14 +70,19 @@ export class VibeSessionCoordinator {
|
||||
|
||||
async start(userId: string, input: StartVibeSessionInput): Promise<VibeSessionResponse> {
|
||||
if (input.resumeSessionId) return this.resume(userId, input.resumeSessionId);
|
||||
const context = normalizeVibeContext({ ...(input.context ?? {}) });
|
||||
const initialState = initialVibeState(context);
|
||||
const policyVersion = input.policyVersion ?? DEFAULT_VIBE_POLICY_VERSION;
|
||||
// Vibe has no reliable device/activity/location signal. Start from neutral
|
||||
// recommendation state and let actual listening behaviour shape the plan.
|
||||
const initialState = {
|
||||
energy: 0.5,
|
||||
noveltyHunger: 0.3,
|
||||
explorationCoefficient: 0.3,
|
||||
discoveryRadius: 0.38,
|
||||
sessionGoal: { type: 'discovery' as const, target: 1, progress: 0 },
|
||||
};
|
||||
const session = await this.db.createVibeSession({
|
||||
userId,
|
||||
policyVersion,
|
||||
policyVersion: DEFAULT_VIBE_POLICY_VERSION,
|
||||
seedTrackId: input.seedTrackId ?? null,
|
||||
context: { ...context },
|
||||
profile: {
|
||||
goals: initialState.sessionGoal,
|
||||
explorationCoefficient: initialState.explorationCoefficient,
|
||||
@@ -99,14 +95,13 @@ export class VibeSessionCoordinator {
|
||||
// session while the durable tables remain the source of truth.
|
||||
await this.db.createSessionState(
|
||||
userId,
|
||||
initialState.contextLabel ?? input.intent,
|
||||
undefined,
|
||||
{
|
||||
energy: initialState.energy,
|
||||
noveltyHunger: initialState.noveltyHunger,
|
||||
explorationCoefficient: initialState.explorationCoefficient,
|
||||
discoveryRadius: initialState.discoveryRadius,
|
||||
sessionGoal: initialState.sessionGoal,
|
||||
context,
|
||||
},
|
||||
session.id,
|
||||
);
|
||||
@@ -114,7 +109,7 @@ export class VibeSessionCoordinator {
|
||||
sessionId: session.id,
|
||||
userId,
|
||||
type: 'session_started',
|
||||
payload: { policyVersion, context, intent: input.intent ?? null },
|
||||
payload: { policyVersion: DEFAULT_VIBE_POLICY_VERSION },
|
||||
});
|
||||
|
||||
const candidates = await this.director.buildPlan(userId, session.id, input.seedTrackId);
|
||||
@@ -128,8 +123,7 @@ export class VibeSessionCoordinator {
|
||||
reason: 'session_started',
|
||||
stateSnapshot: state,
|
||||
objectiveSnapshot: {
|
||||
policyVersion,
|
||||
intent: input.intent ?? null,
|
||||
policyVersion: DEFAULT_VIBE_POLICY_VERSION,
|
||||
horizonTracks: candidates.length,
|
||||
...(candidates[0]?.plan?.objective ?? {}),
|
||||
},
|
||||
@@ -203,7 +197,6 @@ export class VibeSessionCoordinator {
|
||||
// events are immutable. The DB repeats this boundary for non-HTTP
|
||||
// callers; keeping it here also makes coordinator callers see exactly
|
||||
// what will be persisted.
|
||||
const payload = normalizeVibeEventPayload(input.type, input.payload);
|
||||
const result = await this.db.recordVibeEvent({
|
||||
sessionId,
|
||||
userId,
|
||||
@@ -213,7 +206,7 @@ export class VibeSessionCoordinator {
|
||||
occurredAt: input.occurredAt,
|
||||
positionMs: input.positionMs,
|
||||
durationMs: input.durationMs,
|
||||
payload,
|
||||
payload: input.payload,
|
||||
});
|
||||
// The ledger write is authoritative; this idempotent projection updates
|
||||
// exploration only after the exact event exists. Keep the compatibility
|
||||
|
||||
Reference in New Issue
Block a user