fix: keep client session projection stores across removal and revival

The per-session ProjectionValueStore was deleted on host/session-removed
even though the Session instance survives removal (open() is idempotent and
drop() only removes the map entry). A re-added or re-listed session resumed
on the same instance with an empty projection face, so projection-backed UI
(such as the OpenRouter cost readout) reverted to blank until a fresh host
baseline landed.

Retain the store on removal and lift the removed tombstone from the
surviving instance on revival (host/session-added and list-refresh
re-listing), so chat switching keeps cost readouts populated.
This commit is contained in:
2026-08-20 22:10:43 +07:00
parent ed152416d5
commit d2ad46b8aa
8 changed files with 266 additions and 5 deletions

View File

@@ -209,7 +209,7 @@ describe('manager frame routing', () => {
expect(manager.getListSnapshot().items[0]?.projectionValues).not.toBe(baseline)
})
it('drops the projection store with the removed session', async () => {
it('retains the projection store with the removed session (the instance survives removal)', async () => {
const api = new FakeApiClient()
const manager = new SessionManager(api, fakeRemote())
api.onList = () => Promise.resolve(ok({
@@ -224,6 +224,9 @@ describe('manager frame routing', () => {
rpcId: 'rm' as never,
payload: { type: 'host/session-removed', sessionId: sid('s1') } as never,
})
expect(manager.get(sid('s1')).projections.get('title')).toBeUndefined()
// The Session instance survives removal (resident-instance rule), so its
// projection face survives with it: a re-listed session resumes its
// values instead of re-seeding from an empty face.
expect(manager.get(sid('s1')).projections.get('title')).toBe('Doomed')
})
})