feat(vibe): plan musical arcs and callbacks

This commit is contained in:
kami
2026-08-02 00:49:27 +04:00
parent 89a23e3703
commit fe13798c99
7 changed files with 910 additions and 42 deletions
@@ -54,6 +54,7 @@ function setup() {
const director = {
buildPlan: vi.fn().mockResolvedValue([{ trackId: TRACK_ID, generatorId: 'comfort', relevance: 0.8, explanation: [] }]),
buildState: vi.fn().mockResolvedValue({ energy: 0.5, noveltyHunger: 0.3 }),
replan: vi.fn().mockResolvedValue([{ trackId: TRACK_ID, generatorId: 'comfort', relevance: 0.8, explanation: [] }]),
} as any;
return { db, director, coordinator: new VibeSessionCoordinator(db, director) };
}
@@ -80,6 +81,31 @@ describe('VibeSessionCoordinator', () => {
.toEqual(['session_started']);
});
it('persists a director-selected arc role and explainable sequence score', async () => {
const { db, director, coordinator } = setup();
(director.buildPlan as any).mockResolvedValueOnce([{
trackId: TRACK_ID, generatorId: 'discovery', relevance: 0.7, explanation: [{ predicate: 'near' }],
plan: {
slotRole: 'surprise', score: 0.82,
scoreBreakdown: { relevance: 0.7, transition: 0.9, arcTarget: 0.85 },
explanation: { arcRole: 'surprise', surprise: { recoveryRole: 'favorite' } },
},
}]);
await coordinator.start('user-1', {});
expect(db.publishVibePlan).toHaveBeenCalledWith(expect.objectContaining({
items: [expect.objectContaining({
slot_role: 'surprise', score: 0.82,
score_breakdown: expect.objectContaining({ transition: 0.9 }),
explanation: expect.objectContaining({
paths: [{ predicate: 'near' }],
planner: expect.objectContaining({ arcRole: 'surprise' }),
}),
})],
}));
});
it('returns the canonical replacement on an idempotent material-event retry without replanning', async () => {
const { db, coordinator } = setup();
(db.recordVibeEvent as any).mockResolvedValueOnce({
@@ -118,13 +144,42 @@ describe('VibeSessionCoordinator', () => {
const { db, director, coordinator } = setup();
const response = await coordinator.appendEvent('user-1', SESSION_ID, { type: 'completed', trackId: TRACK_ID });
expect(director.buildPlan).toHaveBeenCalledWith('user-1', SESSION_ID, TRACK_ID);
expect(director.replan).toHaveBeenCalledWith(
'user-1', SESSION_ID, expect.any(Array), [TRACK_ID], TRACK_ID,
{ excludedTrackIds: new Set() },
);
expect(db.publishVibePlan).toHaveBeenCalledWith(expect.objectContaining({
sessionId: SESSION_ID, reason: 'feedback:completed', items: [expect.objectContaining({ committed: false })],
}));
expect(response).toMatchObject({ replanned: true, replanReason: 'feedback:completed', planVersion: 2 });
});
it('passes the durable unserved callback tail into the retention-aware replan', async () => {
const { db, director, coordinator } = setup();
(db.getVibePlan as any).mockResolvedValue({
...plan(),
items: [{
...plan().items[0],
slot_role: 'favorite',
explanation: {
paths: [],
planner: { arcRole: 'favorite', callback: { id: 'theme:0', phase: 'return' } },
},
}],
});
await coordinator.appendEvent('user-1', SESSION_ID, { type: 'completed', trackId: 'other-track' });
expect(director.replan).toHaveBeenCalledWith(
'user-1', SESSION_ID,
[expect.objectContaining({
trackId: TRACK_ID,
plan: expect.objectContaining({ slotRole: 'favorite', explanation: expect.objectContaining({ arcRole: 'favorite' }) }),
})],
['other-track'], 'other-track', { excludedTrackIds: new Set() },
);
});
it('keeps the durable seed excluded when feedback supplies a different local replan anchor', async () => {
const { db, director, coordinator } = setup();
const seedTrackId = '44444444-4444-4444-8444-444444444444';
@@ -132,9 +187,11 @@ describe('VibeSessionCoordinator', () => {
await coordinator.appendEvent('user-1', SESSION_ID, { type: 'completed', trackId: TRACK_ID });
expect(director.buildPlan).toHaveBeenCalledWith(
expect(director.replan).toHaveBeenCalledWith(
'user-1',
SESSION_ID,
expect.any(Array),
[TRACK_ID],
TRACK_ID,
{ excludedTrackIds: new Set([seedTrackId]) },
);