fix(apiproxy): harden cold session metadata probing

This commit is contained in:
_Kerman
2026-08-13 15:09:21 +08:00
parent c4226840f4
commit 2be3e12965
18 files changed
+138 -131

No files matched your search

+30 -26
View File
@@ -563,40 +563,40 @@ function summarize(session: Session, running: boolean): SessionSummary {
}
/**
* Verify a possibly blank cold Session only when its physical artifact is
* within the configured per-Session read bound. A stale `blank: true`, an
* Verify a possibly blank cold Session only when its physical artifact passes
* the configured per-Session size check. A stale `blank: true`, an
* absent cache row, a large or location-less artifact, and read failures all
* resolve to visible (`false`); listing must never hide a conversation on a
* cache hint or an unavailable optimization.
*/
async function probeColdSessionBlank(
async function probeColdSessionMetadata(
ctx: Context,
persistence: SessionPersistence,
meta: SessionHeader,
maxBytes: number,
signal?: AbortSignal,
): Promise<boolean> {
if (maxBytes === 0) return false
): Promise<SessionListMetadata | undefined> {
if (maxBytes === 0) return undefined
signal?.throwIfAborted()
const location = persistence.locate(meta)
if (location === undefined) return false
if (location === undefined) return undefined
signal?.throwIfAborted()
let size: number
try {
size = (await stat(location.path)).size
} catch {
signal?.throwIfAborted()
return false
return undefined
}
if (size > maxBytes) return false
if (size > maxBytes) return undefined
try {
const { events } = await persistence.readFrom(meta.id, 0, signal)
signal?.throwIfAborted()
return !events.some(event => event.type === 'turn/start')
return sessionListMetadata(events)
} catch (error) {
signal?.throwIfAborted()
ctx.logger.warn(`session.list: blank probe for "${meta.id}" failed (serving it as visible): ${String(error)}`)
return false
return undefined
}
}
@@ -609,14 +609,14 @@ async function summarizeCold(
blankProbeMaxBytes: number,
signal?: AbortSignal,
): Promise<SessionSummary> {
const blank = metadata?.blank === false
? false
: await probeColdSessionBlank(ctx, persistence, meta, blankProbeMaxBytes, signal)
const probed = metadata?.blank === false
? undefined
: await probeColdSessionMetadata(ctx, persistence, meta, blankProbeMaxBytes, signal)
return {
sessionId: meta.id,
updatedAt: sessionListUpdatedAt(meta, metadata),
updatedAt: sessionListUpdatedAt(meta, probed ?? metadata),
running: false,
blank,
blank: metadata?.blank === false ? false : probed?.blank ?? false,
// Header-only: reading the log for a blank-window preset switch would
// defeat the same index read, and attaching the session replaces this row
// with `summarize()`, which resolves the switch from the events.
@@ -1724,14 +1724,15 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
*/
async function listVisibleSessionSummaries(signal?: AbortSignal): Promise<SessionSummary[]> {
signal?.throwIfAborted()
const items = ctx.sessions.list().map((session) => {
const summarizeAttached = (session: Session): SessionSummary => {
const agent = ctx.agents.get(session.id)
const projections = listProjectionsFor(ctx, session.header, session)
return {
...summarize(session, agent?.status === 'running'),
...projections === undefined ? {} : { projections },
}
})
}
const items = ctx.sessions.list().map(summarizeAttached)
signal?.throwIfAborted()
const attached = new Set(items.map(item => item.sessionId))
const persistence = ctx.get('sessionPersistence')
@@ -1745,17 +1746,20 @@ export function createApiProxy(ctx: Context, defaults: ApiProxyDefaults): ApiPro
const settled = await Promise.allSettled(
batch.map(async (meta) => {
// Projection hints remain optional. Blank verification may read
// this Session's artifact only when it fits the configured bound.
// this Session's artifact only when it passes the configured size check.
const projections = listProjectionsFor(ctx, meta, undefined)
const summary = await summarizeCold(
ctx,
persistence,
meta,
projections?.values.sessionListMetadata,
coldBlankProbeMaxBytes,
signal,
)
const attachedSession = ctx.sessions.get(meta.id)
if (attachedSession !== undefined) return summarizeAttached(attachedSession)
return {
...await summarizeCold(
ctx,
persistence,
meta,
projections?.values.sessionListMetadata,
coldBlankProbeMaxBytes,
signal,
),
...summary,
...projections === undefined ? {} : { projections },
}
}),