fix(web): follow a blank session's preset switch in the slash catalog
Presets own the rows that decide what a session's `/` menu contains, but both browser catalogs cache per session and had no invalidation edge for a recompose: `commands/changed` is registry-wide and recomposing registers nothing, so the menu kept serving the composition the session no longer ran. The host stream now frames the logged `agent-preset/selected` commit as `host/session-preset-changed`; the runtime bridges it to the typed `session/preset-changed` event, `ui-command` soft-refreshes that session's directory key and `ui-skill` invalidates its catalog entry. Reaching the host on a second switch was a separate defect: the list-row identity guard compared every summary field except `agentPreset`, and the merge keeps the row's `updatedAt`, so a switched row looked unchanged and served its cached instance forever. The hero chip compares the pick against that row, so switching back to the creation-time preset sent no RPC at all.
This commit is contained in:
@@ -617,6 +617,30 @@ describe('directory invalidation events', () => {
|
||||
expect(source.matchSpace!(proj('s1'), '/goal')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('session/preset-changed repulls the recomposed session and leaves the others served', async () => {
|
||||
const rounds = new Map<SessionId, number>()
|
||||
const { ctx, source, warm } = await bench({
|
||||
commands: (payload) => {
|
||||
const round = (rounds.get(payload.sessionId) ?? 0) + 1
|
||||
rounds.set(payload.sessionId, round)
|
||||
return Promise.resolve({
|
||||
commands: round === 1
|
||||
? S1_CMDS
|
||||
: [{ name: 'fresh', description: '', input: { hint: 'h' } }],
|
||||
})
|
||||
},
|
||||
})
|
||||
await warm(proj('s1'))
|
||||
await warm(proj('s2'))
|
||||
// A preset switch changes which commands one session's agent resolves;
|
||||
// every other session keeps the catalog its own composition serves.
|
||||
ctx.emit('session/preset-changed', sid('s1'), 'minimal')
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(source.matchSpace!(proj('s1'), '/fresh')).not.toBeUndefined()
|
||||
expect(source.matchSpace!(proj('s1'), '/goal')).toBeUndefined()
|
||||
expect(source.matchSpace!(proj('s2'), '/goal')).not.toBeUndefined()
|
||||
})
|
||||
|
||||
it('connection/reset hard-drops every session key until its rewarm lands', async () => {
|
||||
let block = false
|
||||
let release!: (value: { commands: CommandDescriptor[] }) => void
|
||||
|
||||
Reference in New Issue
Block a user