mirror of
https://github.com/deepseek-ai/deepseek-harness
synced 2026-08-15 21:04:50 +00:00
fix(web): retain live capacity before session creation
This commit is contained in:
@@ -57,6 +57,12 @@ export class SessionManager {
|
||||
* drop-and-backfill path; replayed and cleared on instantiation. Bounded per session (these
|
||||
* frames are low-frequency; overflow drops oldest) and dropped on session-removed (audit S7). */
|
||||
private readonly pendingBuffers = new Map<SessionId, RpcRequest<MuxFrame>[]>()
|
||||
/**
|
||||
* Latest model capacity observed for an uninstantiated session on the
|
||||
* current mux generation. Unlike durable history, this transient frame
|
||||
* cannot be backfilled when get() lazily creates the Session.
|
||||
*/
|
||||
private readonly modelRequestContextWindows = new Map<SessionId, number>()
|
||||
/** Per-session projection value stores, retained independently of instance arrival (the
|
||||
* title-snapshot precedent, generalized): push frames land here whether or not the Session
|
||||
* is instantiated (list rows read the 'title' key), and an instantiated Session adopts the
|
||||
@@ -160,6 +166,7 @@ export class SessionManager {
|
||||
}
|
||||
|
||||
private createSession(sessionId: SessionId): Session {
|
||||
const modelRequestContextWindow = this.modelRequestContextWindows.get(sessionId)
|
||||
return new Session(sessionId, this.api, {
|
||||
// The sender's local first-send flip mirrors into the list row so the
|
||||
// session surfaces (lists filter on blank) before any host frame lands.
|
||||
@@ -167,6 +174,7 @@ export class SessionManager {
|
||||
this.recordMutation({ kind: 'engaged', sessionId: engaged.sessionId })
|
||||
},
|
||||
projections: this.projectionStore(sessionId),
|
||||
...(modelRequestContextWindow === undefined ? {} : { modelRequestContextWindow }),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -328,7 +336,14 @@ export class SessionManager {
|
||||
this.notifier.markDirty()
|
||||
return
|
||||
}
|
||||
if (frame.type === 'session/model-request') {
|
||||
// Transient and non-replayable: retain the latest capacity until lazy
|
||||
// instantiation. An absent value explicitly clears an earlier one.
|
||||
if (frame.contextWindow === undefined) this.modelRequestContextWindows.delete(frame.sessionId)
|
||||
else this.modelRequestContextWindows.set(frame.sessionId, frame.contextWindow)
|
||||
}
|
||||
if (frame.type === 'session/subscribed') {
|
||||
this.modelRequestContextWindows.delete(frame.sessionId)
|
||||
// Rows past the host's durable baseline rode state a restart lost; drop
|
||||
// them so last-wins cannot pin a phantom value over recomputed truth.
|
||||
this.projectionStores.get(frame.sessionId)?.truncate(frame.lastSeq)
|
||||
@@ -391,6 +406,7 @@ export class SessionManager {
|
||||
this.recordMutation({ kind: 'remove', sessionId: frame.sessionId })
|
||||
this.sessions.get(frame.sessionId)?.handleRemoved() // instance survives (resident-instance rule), only flagged in the snapshot
|
||||
this.pendingBuffers.delete(frame.sessionId) // a removed session's buffered frames must not replay on a future instantiation
|
||||
this.modelRequestContextWindows.delete(frame.sessionId) // connection-local request capacity dies with the Host session
|
||||
this.projectionStores.delete(frame.sessionId) // removed sessions drop their projection rows with the instance
|
||||
return
|
||||
}
|
||||
|
||||
@@ -43,6 +43,8 @@ export interface SessionOptions {
|
||||
* private store (bare object-layer construction).
|
||||
*/
|
||||
projections?: ProjectionValueStore
|
||||
/** Model capacity already observed on this mux generation before lazy construction. */
|
||||
modelRequestContextWindow?: number
|
||||
}
|
||||
|
||||
/** Queue-row preview cap: the dock renders one line, the full content never leaves the host mirror. */
|
||||
@@ -172,6 +174,7 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
|
||||
private readonly options: SessionOptions = {},
|
||||
) {
|
||||
this.projections = options.projections ?? new ProjectionValueStore()
|
||||
this.contextWindow = options.modelRequestContextWindow
|
||||
this.snapshotCache = this.buildSnapshot()
|
||||
}
|
||||
|
||||
@@ -479,10 +482,12 @@ export class Session implements ObservableSnapshot<ConversationSnapshot> {
|
||||
this.notifier.markDirty()
|
||||
}
|
||||
|
||||
/** host/session-removed relay: flag the snapshot (instance survives — resident-instance rule). */
|
||||
/** host/session-removed relay: flag the resident snapshot and clear connection-local capacity. */
|
||||
handleRemoved(): void {
|
||||
const changed = !this.removed || this.contextWindow !== undefined
|
||||
this.removed = true
|
||||
this.notifier.markDirty()
|
||||
this.contextWindow = undefined
|
||||
if (changed) this.notifier.markDirty()
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -41,6 +41,127 @@ describe('instances', () => {
|
||||
expect(manager.get(S2).getSnapshot().pending).toEqual([])
|
||||
})
|
||||
|
||||
it('retains the latest transient model capacity until lazy instantiation', () => {
|
||||
const api = new FakeApiClient()
|
||||
const manager = new SessionManager(api)
|
||||
manager.handleMuxEnvelope({
|
||||
rpcId: 'request-1' as never,
|
||||
payload: {
|
||||
type: 'session/model-request',
|
||||
sessionId: S1,
|
||||
turn: 1,
|
||||
step: 1,
|
||||
provider: 'test',
|
||||
model: 'alpha',
|
||||
contextWindow: 128_000,
|
||||
},
|
||||
})
|
||||
manager.handleMuxEnvelope({
|
||||
rpcId: 'request-2' as never,
|
||||
payload: {
|
||||
type: 'session/model-request',
|
||||
sessionId: S1,
|
||||
turn: 1,
|
||||
step: 2,
|
||||
provider: 'test',
|
||||
model: 'beta',
|
||||
contextWindow: 256_000,
|
||||
},
|
||||
})
|
||||
|
||||
expect(manager.get(S1).getSnapshot().modelRequestContextWindow).toBe(256_000)
|
||||
})
|
||||
|
||||
it('retains explicit capacity clearing before lazy instantiation', () => {
|
||||
const api = new FakeApiClient()
|
||||
const manager = new SessionManager(api)
|
||||
manager.handleMuxEnvelope({
|
||||
rpcId: 'request-with-capacity' as never,
|
||||
payload: {
|
||||
type: 'session/model-request',
|
||||
sessionId: S1,
|
||||
turn: 1,
|
||||
step: 1,
|
||||
provider: 'test',
|
||||
model: 'alpha',
|
||||
contextWindow: 128_000,
|
||||
},
|
||||
})
|
||||
manager.handleMuxEnvelope({
|
||||
rpcId: 'request-without-capacity' as never,
|
||||
payload: {
|
||||
type: 'session/model-request',
|
||||
sessionId: S1,
|
||||
turn: 1,
|
||||
step: 2,
|
||||
provider: 'test',
|
||||
model: 'unknown-capacity',
|
||||
},
|
||||
})
|
||||
|
||||
expect(manager.get(S1).getSnapshot().modelRequestContextWindow).toBeUndefined()
|
||||
})
|
||||
|
||||
it('clears retained capacity on subscribed and resident capacity on removal', () => {
|
||||
const api = new FakeApiClient()
|
||||
const manager = new SessionManager(api)
|
||||
manager.handleMuxEnvelope({
|
||||
rpcId: 'request-before-subscribe' as never,
|
||||
payload: {
|
||||
type: 'session/model-request',
|
||||
sessionId: S1,
|
||||
turn: 1,
|
||||
step: 1,
|
||||
provider: 'test',
|
||||
model: 'alpha',
|
||||
contextWindow: 128_000,
|
||||
},
|
||||
})
|
||||
manager.handleMuxEnvelope({
|
||||
rpcId: 'subscribed' as never,
|
||||
payload: { type: 'session/subscribed', sessionId: S1, lastSeq: 0 },
|
||||
})
|
||||
const session = manager.get(S1)
|
||||
expect(session.getSnapshot().modelRequestContextWindow).toBeUndefined()
|
||||
|
||||
manager.handleMuxEnvelope({
|
||||
rpcId: 'request-after-subscribe' as never,
|
||||
payload: {
|
||||
type: 'session/model-request',
|
||||
sessionId: S1,
|
||||
turn: 1,
|
||||
step: 2,
|
||||
provider: 'test',
|
||||
model: 'beta',
|
||||
contextWindow: 256_000,
|
||||
},
|
||||
})
|
||||
expect(session.getSnapshot().modelRequestContextWindow).toBe(256_000)
|
||||
manager.handleHostEnvelope({
|
||||
rpcId: 'removed' as never,
|
||||
payload: { type: 'host/session-removed', sessionId: S1 },
|
||||
})
|
||||
expect(session.getSnapshot().modelRequestContextWindow).toBeUndefined()
|
||||
|
||||
manager.handleMuxEnvelope({
|
||||
rpcId: 'request-before-lazy-removal' as never,
|
||||
payload: {
|
||||
type: 'session/model-request',
|
||||
sessionId: S2,
|
||||
turn: 1,
|
||||
step: 1,
|
||||
provider: 'test',
|
||||
model: 'gamma',
|
||||
contextWindow: 64_000,
|
||||
},
|
||||
})
|
||||
manager.handleHostEnvelope({
|
||||
rpcId: 'lazy-removed' as never,
|
||||
payload: { type: 'host/session-removed', sessionId: S2 },
|
||||
})
|
||||
expect(manager.get(S2).getSnapshot().modelRequestContextWindow).toBeUndefined()
|
||||
})
|
||||
|
||||
it('caps the pending buffer at 32 keeping the newest, and drops it on session-removed', () => {
|
||||
const api = new FakeApiClient()
|
||||
const manager = new SessionManager(api)
|
||||
|
||||
Reference in New Issue
Block a user