fix(web): preserve reconnect metrics baseline
This commit is contained in:
@@ -309,11 +309,10 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
|
|||||||
* in-flight open first — its history request rode the dead connection and must not settle
|
* in-flight open first — its history request rode the dead connection and must not settle
|
||||||
* the fresh generation into 'error' (audit S4). */
|
* the fresh generation into 'error' (audit S4). */
|
||||||
async resync(): Promise<void> {
|
async resync(): Promise<void> {
|
||||||
// The queue mirror is NOT cleared here: onConnected (which drives resync)
|
// Queue, metrics, and request capacity are NOT cleared here: onConnected
|
||||||
// races the mux frames — the fresh generation's baseline may have landed
|
// (which drives resync) races the mux frames — fresh-generation state may
|
||||||
// already, and the host never resends it. The mirror re-baselines on the
|
// have landed already, and the host never resends it. session/subscribed
|
||||||
// session/subscribed frame instead (same stream as the queue snapshot
|
// owns the generation reset before the queue snapshot and metrics frames.
|
||||||
// that follows it, so ordering is guaranteed).
|
|
||||||
if (this.openState === 'cold') return // never opened: no window to rebuild (doOpen flips to 'loading' synchronously, so cold implies no in-flight open)
|
if (this.openState === 'cold') return // never opened: no window to rebuild (doOpen flips to 'loading' synchronously, so cold implies no in-flight open)
|
||||||
this.openGeneration++
|
this.openGeneration++
|
||||||
this.openPromise = null
|
this.openPromise = null
|
||||||
@@ -322,7 +321,6 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
|
|||||||
this.events = []
|
this.events = []
|
||||||
this.views = []
|
this.views = []
|
||||||
this.baseSeq = 0
|
this.baseSeq = 0
|
||||||
this.metrics = null
|
|
||||||
// Superseded, not settled: the baseline replay re-sends still-pending requested frames verbatim
|
// Superseded, not settled: the baseline replay re-sends still-pending requested frames verbatim
|
||||||
// (same rpcId), re-minting fresh waits; a stale reference's respond() still reaches the host.
|
// (same rpcId), re-minting fresh waits; a stale reference's respond() still reaches the host.
|
||||||
this.pending.clear()
|
this.pending.clear()
|
||||||
|
|||||||
@@ -813,6 +813,40 @@ describe('remaining branches', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('resync', () => {
|
describe('resync', () => {
|
||||||
|
it('preserves fresh-generation metrics that arrive before a failing history refresh', async () => {
|
||||||
|
const { api, session } = makeSession()
|
||||||
|
const oldMetrics = metrics(8, 10)
|
||||||
|
api.onHistory = () => histResponse(plainTurn(0, 0, 'a', 'b'), false, undefined, oldMetrics)
|
||||||
|
await session.open()
|
||||||
|
expect(session.getSnapshot().metrics).toBe(oldMetrics)
|
||||||
|
|
||||||
|
session.handleMuxEnvelope('sub' as never, {
|
||||||
|
type: 'session/subscribed',
|
||||||
|
sessionId: SID,
|
||||||
|
lastSeq: 5,
|
||||||
|
})
|
||||||
|
expect(session.getSnapshot().metrics).toBeNull()
|
||||||
|
|
||||||
|
const freshMetrics = metrics(0, 10, { contextTokens: 20 })
|
||||||
|
session.handleMuxEnvelope('fresh-metrics' as never, {
|
||||||
|
type: 'session/metrics',
|
||||||
|
sessionId: SID,
|
||||||
|
metrics: freshMetrics,
|
||||||
|
})
|
||||||
|
api.onHistory = () => Promise.resolve(err({
|
||||||
|
code: 'internal',
|
||||||
|
message: 'history refresh failed',
|
||||||
|
details: {},
|
||||||
|
}))
|
||||||
|
|
||||||
|
await session.resync()
|
||||||
|
|
||||||
|
expect(session.getSnapshot()).toMatchObject({
|
||||||
|
openState: 'error',
|
||||||
|
metrics: freshMetrics,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('rebuilds the window and clears pending; cold instances no-op', async () => {
|
it('rebuilds the window and clears pending; cold instances no-op', async () => {
|
||||||
const { api, session } = makeSession()
|
const { api, session } = makeSession()
|
||||||
api.onHistory = () => histResponse(plainTurn(0, 0, 'a', 'b'))
|
api.onHistory = () => histResponse(plainTurn(0, 0, 'a', 'b'))
|
||||||
|
|||||||
Reference in New Issue
Block a user