feat(vibe): weigh local calendar context in session planning
The director had no idea what hour or season a session started in, so a 22:00 weeknight and a Sunday morning drew from the same pool. The client now sends localHour, weekday, month and an optional timeZone; the route validates and bounds all four, the coordinator threads them through, and the generators use them as scoring signals. Also exposes GET /library/stats, which the Home page counts read. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +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 { Candidate, VibeCalendarContext } from './generators.service.js';
|
||||
|
||||
/**
|
||||
* This is deliberately a narrow bridge between the durable Vibe ledger and
|
||||
@@ -22,6 +22,7 @@ export type VibeEventType = (typeof VIBE_EVENT_TYPES)[number];
|
||||
export interface StartVibeSessionInput {
|
||||
seedTrackId?: string;
|
||||
resumeSessionId?: string;
|
||||
context?: VibeCalendarContext;
|
||||
}
|
||||
|
||||
export interface AppendVibeEventInput {
|
||||
@@ -70,10 +71,15 @@ export class VibeSessionCoordinator {
|
||||
|
||||
async start(userId: string, input: StartVibeSessionInput): Promise<VibeSessionResponse> {
|
||||
if (input.resumeSessionId) return this.resume(userId, input.resumeSessionId);
|
||||
// Vibe has no reliable device/activity/location signal. Start from neutral
|
||||
// recommendation state and let actual listening behaviour shape the plan.
|
||||
// Calendar context is a weak boot prior, never a substitute for listening
|
||||
// feedback or long-term preference. It is intentionally coarse and is
|
||||
// persisted with the session so a resume remains coherent.
|
||||
const calendar = input.context;
|
||||
const contextualEnergy = calendar && calendar.localHour < 6 ? 0.32
|
||||
: calendar && calendar.localHour >= 18 && calendar.localHour < 23 ? 0.58
|
||||
: 0.5;
|
||||
const initialState = {
|
||||
energy: 0.5,
|
||||
energy: contextualEnergy,
|
||||
noveltyHunger: 0.3,
|
||||
explorationCoefficient: 0.3,
|
||||
discoveryRadius: 0.38,
|
||||
@@ -83,6 +89,7 @@ export class VibeSessionCoordinator {
|
||||
userId,
|
||||
policyVersion: DEFAULT_VIBE_POLICY_VERSION,
|
||||
seedTrackId: input.seedTrackId ?? null,
|
||||
context: calendar ? { ...calendar } : undefined,
|
||||
profile: {
|
||||
goals: initialState.sessionGoal,
|
||||
explorationCoefficient: initialState.explorationCoefficient,
|
||||
@@ -95,7 +102,7 @@ export class VibeSessionCoordinator {
|
||||
// session while the durable tables remain the source of truth.
|
||||
await this.db.createSessionState(
|
||||
userId,
|
||||
undefined,
|
||||
calendar ? JSON.stringify(calendar) : undefined,
|
||||
{
|
||||
energy: initialState.energy,
|
||||
noveltyHunger: initialState.noveltyHunger,
|
||||
@@ -109,7 +116,7 @@ export class VibeSessionCoordinator {
|
||||
sessionId: session.id,
|
||||
userId,
|
||||
type: 'session_started',
|
||||
payload: { policyVersion: DEFAULT_VIBE_POLICY_VERSION },
|
||||
payload: { policyVersion: DEFAULT_VIBE_POLICY_VERSION, ...(calendar ? { calendar } : {}) },
|
||||
});
|
||||
|
||||
const candidates = await this.director.buildPlan(userId, session.id, input.seedTrackId);
|
||||
|
||||
Reference in New Issue
Block a user