fix(vibe): keep playback advancing with full plans
Typecheck / typecheck (backend) (push) Has been cancelled
Typecheck / typecheck (workers) (push) Has been cancelled

This commit is contained in:
kami
2026-08-03 14:12:22 +04:00
parent 6b40cf7a9c
commit 1f44af5893
4 changed files with 35 additions and 5 deletions
+1
View File
@@ -55,6 +55,7 @@ describe('DbService v2 methods', () => {
expect(clientQuery.mock.calls[4][0]).toContain('INSERT INTO evidence');
expect(clientQuery.mock.calls[8][0]).toContain('exploration_coefficient');
expect(clientQuery.mock.calls[8][0]).toContain('ELSE goals END');
expect(clientQuery.mock.calls[8][0]).toContain('ELSE $3::real END');
expect(clientQuery.mock.calls[9][1][4]).toBe(JSON.stringify({ type: 'familiar', target: 1, progress: 1 }));
expect(clientQuery.mock.calls.filter(([sql]) => String(sql).includes('INSERT INTO evidence'))).toHaveLength(1);
});
+3 -3
View File
@@ -1664,12 +1664,12 @@ export class DbService {
const profile = await client.query(
`UPDATE vibe_session_profiles
SET exploration_coefficient = GREATEST(0, LEAST(1, exploration_coefficient + CASE WHEN $4 THEN 0 ELSE $3 END)),
discovery_radius = GREATEST(0.15, LEAST(0.9, 0.2 + (GREATEST(0, LEAST(1, exploration_coefficient + CASE WHEN $4 THEN 0 ELSE $3 END)) * 0.65))),
SET exploration_coefficient = GREATEST(0, LEAST(1, exploration_coefficient + CASE WHEN $4 THEN 0::real ELSE $3::real END)),
discovery_radius = GREATEST(0.15, LEAST(0.9, 0.2 + (GREATEST(0, LEAST(1, exploration_coefficient + CASE WHEN $4 THEN 0::real ELSE $3::real END)) * 0.65))),
goals = CASE
WHEN goals->>'type' = 'familiar' AND $4 AND $5
THEN jsonb_set(goals, '{progress}', to_jsonb(LEAST(COALESCE((goals->>'progress')::int, 0) + 1, COALESCE((goals->>'target')::int, 1))))
WHEN goals->>'type' IN ('discovery', 'surprise', 'artist_introduction') AND NOT $4 AND $3 > 0
WHEN goals->>'type' IN ('discovery', 'surprise', 'artist_introduction') AND NOT $4 AND $3::real > 0
THEN jsonb_set(goals, '{progress}', to_jsonb(LEAST(COALESCE((goals->>'progress')::int, 0) + 1, COALESCE((goals->>'target')::int, 1))))
ELSE goals END,
updated_at = NOW()
@@ -60,6 +60,7 @@ export interface GeneratorContext {
export type Generator = (db: DbService, ctx: GeneratorContext) => Promise<Candidate[]>;
const OBJECTIVE_USER = '00000000-0000-0000-0000-000000000000';
const FALLBACK_CANDIDATE_LIMIT = 60;
// ---------------------------------------------------------------------------
// 1. COMFORT — Top artists by longterm affinity > 0.5
@@ -536,6 +537,33 @@ async function contextualGenerator(db: DbService, ctx: GeneratorContext): Promis
});
}
// ---------------------------------------------------------------------------
// 9. LIBRARY FALLBACK — keeps a Vibe usable when the metadata graph is sparse
// ---------------------------------------------------------------------------
async function libraryFallbackGenerator(db: DbService, ctx: GeneratorContext): Promise<Candidate[]> {
const res = await db.pgClient.query(
`SELECT t.id
FROM tracks t
WHERE t.state = 'LIBRARY'
AND NOT (t.id = ANY($1::uuid[]))
ORDER BY RANDOM()
LIMIT $2`,
[ctx.recentExclusions, FALLBACK_CANDIDATE_LIMIT],
);
return (res.rows as { id: string }[]).map(row => ({
trackId: row.id,
generatorId: 'library-fallback',
relevance: 0.05,
explanation: [{
subjectType: 'track', subjectId: row.id,
predicate: 'library_fallback',
objectType: 'track', objectId: row.id,
fusedValue: 0.05,
}],
}));
}
// ---------------------------------------------------------------------------
// All generators, ordered by priority (comfort first, experimental last)
// ---------------------------------------------------------------------------
@@ -548,4 +576,5 @@ export const ALL_GENERATORS: Generator[] = [
noveltyGenerator,
contextualGenerator,
experimentalGenerator,
libraryFallbackGenerator,
];
+2 -2
View File
@@ -199,8 +199,8 @@ describe('generators', () => {
});
describe('ALL_GENERATORS', () => {
it('contains 8 generators', () => {
expect(ALL_GENERATORS).toHaveLength(8);
it('contains 9 generators', () => {
expect(ALL_GENERATORS).toHaveLength(9);
ALL_GENERATORS.forEach(g => expect(typeof g).toBe('function'));
});
});