feat(vibe): plan musical arcs and callbacks
This commit is contained in:
@@ -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]) },
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user