fix(session-projection-cache): drain in-flight writes on disposal; sync stale lockfile and generated docs

- Track fire-and-forget durable writes and await them at plugin disposal so
  a late flush can never land after teardown (fixes the ENOTEMPTY cleanup
  race in the disposal test).
- Drop the now-async-less Service.init and flushSoft void operators to keep
  lint clean, and remove the redundant dsh-storage-json devDependency.
- Regenerate the stale pnpm lockfile and the config/persistence/module-graph
  catalogs (with zh mirrors and pairing records) that the per-session cache
  merge left out of sync, and fix the session-projection type-equiv doc
  blocks to match the source.
- Add coverage for the unrelated-log-identity and no-per-session-directory
  (sqlite) cold-read paths.
This commit is contained in:
_Kerman 2026-08-20 10:49:43 +08:00
parent 30334322eb
commit c26ca6acb6
16 changed files with 102 additions and 62 deletions

View file

@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/config-catalog.md
config-catalog.md: 1d9a4a7b0839b92ac7445a1a4cfb74fa70f0227f
config-catalog.zh.md: b93bcd9c9ff9776ef5b5f7c2b467e596eee13cff
config-catalog.md: f0f989a56dd063c583694cfc08ffa6a023fa7936
config-catalog.zh.md: a371c6d09f0aa73623646f30d36ced37effa0183

View file

@ -632,7 +632,7 @@ Source: [`packages/fs/fs-sandbox/src/index.ts:49`](../packages/fs/fs-sandbox/src
## `@deepseek-ai/dsh-goal`
Requires: `agents` · `sessionProjections`
Requires: `agents`
```ts config-catalog
/** Deployment defaults for goal creation. */
@ -642,7 +642,7 @@ export interface Config {
}
```
Source: [`packages/goal/goal/src/index.ts:115`](../packages/goal/goal/src/index.ts)
Source: [`packages/goal/goal/src/index.ts:116`](../packages/goal/goal/src/index.ts)
<a id="deepseek-aidsh-headless"></a>
@ -1398,7 +1398,7 @@ export interface PresetSpec {
Depends on: [`ApprovalPolicy`](subsystems/approval.md) · [`SandboxMode`](subsystems/sandbox.md)
Source: [`packages/interaction/permission-presets/src/index.ts:116`](../packages/interaction/permission-presets/src/index.ts)
Source: [`packages/interaction/permission-presets/src/index.ts:112`](../packages/interaction/permission-presets/src/index.ts)
<a id="deepseek-aidsh-persona"></a>
@ -1702,7 +1702,7 @@ Source: [`packages/session/session-persistence-sqlite/src/index.ts:70`](../packa
## `@deepseek-ai/dsh-session-projection-cache`
Requires: `storageDomain` · `sessionProjections` · `sessionPersistence` · `sessions`
Requires: `sessionProjections` · `sessionPersistence` · `sessions`
```ts config-catalog
/**
@ -1719,13 +1719,13 @@ export interface Config {
}
```
Source: [`packages/session/session-projection-cache/src/index.ts:42`](../packages/session/session-projection-cache/src/index.ts)
Source: [`packages/session/session-projection-cache/src/index.ts:47`](../packages/session/session-projection-cache/src/index.ts)
<a id="deepseek-aidsh-session-query-sqlite"></a>
## `@deepseek-ai/dsh-session-query-sqlite`
Requires: `sessions` · `sessionProjections`
Requires: `sessions`
```ts config-catalog
/** Combined session-query configuration backed by SQLite full-text search. */
@ -1851,7 +1851,7 @@ export interface Config {
}
```
Source: [`packages/session/session-title/src/index.ts:54`](../packages/session/session-title/src/index.ts)
Source: [`packages/session/session-title/src/index.ts:53`](../packages/session/session-title/src/index.ts)
<a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
@ -2049,7 +2049,7 @@ export interface Config {
}
```
Source: [`packages/storage/storage-json/src/index.ts:27`](../packages/storage/storage-json/src/index.ts)
Source: [`packages/storage/storage-json/src/index.ts:28`](../packages/storage/storage-json/src/index.ts)
<a id="deepseek-aidsh-storage-sqlite"></a>

View file

@ -634,7 +634,7 @@ export type Config = LocalConfig
## `@deepseek-ai/dsh-goal`
需要:`agents` · `sessionProjections`
需要:`agents`
```ts config-catalog
/** Deployment defaults for goal creation. */
@ -644,7 +644,7 @@ export interface Config {
}
```
来源:[`packages/goal/goal/src/index.ts:115`](../packages/goal/goal/src/index.ts)
来源:[`packages/goal/goal/src/index.ts:116`](../packages/goal/goal/src/index.ts)
<a id="deepseek-aidsh-headless"></a>
@ -1400,7 +1400,7 @@ export interface PresetSpec {
依赖:[`ApprovalPolicy`](subsystems/approval.md) · [`SandboxMode`](subsystems/sandbox.md)
来源:[`packages/interaction/permission-presets/src/index.ts:116`](../packages/interaction/permission-presets/src/index.ts)
来源:[`packages/interaction/permission-presets/src/index.ts:112`](../packages/interaction/permission-presets/src/index.ts)
<a id="deepseek-aidsh-persona"></a>
@ -1704,7 +1704,7 @@ export type JournalMode = 'wal' | 'delete' | 'truncate' | 'persist'
## `@deepseek-ai/dsh-session-projection-cache`
需要:`storageDomain` · `sessionProjections` · `sessionPersistence` · `sessions`
需要:`sessionProjections` · `sessionPersistence` · `sessions`
```ts config-catalog
/**
@ -1721,13 +1721,13 @@ export interface Config {
}
```
来源:[`packages/session/session-projection-cache/src/index.ts:42`](../packages/session/session-projection-cache/src/index.ts)
来源:[`packages/session/session-projection-cache/src/index.ts:47`](../packages/session/session-projection-cache/src/index.ts)
<a id="deepseek-aidsh-session-query-sqlite"></a>
## `@deepseek-ai/dsh-session-query-sqlite`
需要:`sessions` · `sessionProjections`
需要:`sessions`
```ts config-catalog
/** Combined session-query configuration backed by SQLite full-text search. */
@ -1853,7 +1853,7 @@ export interface Config {
}
```
来源:[`packages/session/session-title/src/index.ts:54`](../packages/session/session-title/src/index.ts)
来源:[`packages/session/session-title/src/index.ts:53`](../packages/session/session-title/src/index.ts)
<a id="deepseek-aidsh-session-title-all-prompts-llm"></a>
@ -2051,7 +2051,7 @@ export interface Config {
}
```
来源:[`packages/storage/storage-json/src/index.ts:27`](../packages/storage/storage-json/src/index.ts)
来源:[`packages/storage/storage-json/src/index.ts:28`](../packages/storage/storage-json/src/index.ts)
<a id="deepseek-aidsh-storage-sqlite"></a>

View file

@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/module-graph.md
module-graph.md: 8422bbae71ab14fdac7b11eba17a1a2fc065302c
module-graph.zh.md: b16d6963e3c8fb597fbe3992fc90bd59bf67a85c
module-graph.md: 1228a7fe6be159c96fcc17c3b8b4397bb9762f5c
module-graph.zh.md: 2a2069c077bcf903dae9143458705407b6b7b1dd

View file

@ -507,7 +507,6 @@ flowchart TD
pkg_session_projection_cache --> pkg_session
pkg_session_projection_cache --> pkg_session_persistence
pkg_session_projection_cache --> pkg_session_projection
pkg_session_projection_cache --> pkg_storage_domain
pkg_session_stats --> pkg_invariants
pkg_session_stats --> pkg_llm
pkg_session_stats --> pkg_session
@ -1509,7 +1508,7 @@ flowchart TD
| [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
| [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
| [`session-persistence-sqlite`](../packages/session/session-persistence-sqlite) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
| [`session-projection-cache`](../packages/session/session-projection-cache) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain) |
| [`session-projection-cache`](../packages/session/session-projection-cache) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection) |
| [`session-stats`](../packages/session/session-stats) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
| [`shell`](../packages/shell/shell) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`settings`](../packages/settings/settings), [`subprocess`](../packages/subprocess/subprocess) |
| [`workspace`](../packages/workspace/workspace) | `workspace` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain) |

View file

@ -509,7 +509,6 @@ flowchart TD
pkg_session_projection_cache --> pkg_session
pkg_session_projection_cache --> pkg_session_persistence
pkg_session_projection_cache --> pkg_session_projection
pkg_session_projection_cache --> pkg_storage_domain
pkg_session_stats --> pkg_invariants
pkg_session_stats --> pkg_llm
pkg_session_stats --> pkg_session
@ -1511,7 +1510,7 @@ flowchart TD
| [`sandbox-local`](../packages/sandbox/sandbox-local) | `sandbox` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`sandbox`](../packages/sandbox/sandbox), [`session`](../packages/core/session) |
| [`session-persistence-jsonl`](../packages/session/session-persistence-jsonl) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
| [`session-persistence-sqlite`](../packages/session/session-persistence-sqlite) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence) |
| [`session-projection-cache`](../packages/session/session-projection-cache) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection), [`storage-domain`](../packages/storage/storage-domain) |
| [`session-projection-cache`](../packages/session/session-projection-cache) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`session-projection`](../packages/session/session-projection) |
| [`session-stats`](../packages/session/session-stats) | `session` | [`invariants`](../packages/runtime-diagnostics/invariants), [`llm`](../packages/llm/llm), [`session`](../packages/core/session), [`session-projection`](../packages/session/session-projection) |
| [`shell`](../packages/shell/shell) | `shell` | [`invariants`](../packages/runtime-diagnostics/invariants), [`sandbox`](../packages/sandbox/sandbox), [`settings`](../packages/settings/settings), [`subprocess`](../packages/subprocess/subprocess) |
| [`workspace`](../packages/workspace/workspace) | `workspace` | [`brand`](../packages/util/brand), [`invariants`](../packages/runtime-diagnostics/invariants), [`session`](../packages/core/session), [`session-persistence`](../packages/session/session-persistence), [`storage`](../packages/storage/storage), [`storage-domain`](../packages/storage/storage-domain) |

View file

@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/persistence-catalog.md
persistence-catalog.md: db96226b648afecd65e74f50e14a82d036f7de74
persistence-catalog.zh.md: 0a247b4c4c5bf7eedbf8b7b8120bac3828090bc6
persistence-catalog.md: ffd61bc1ea7e2a1e0e1edd60c067c8e84fc7fdbc
persistence-catalog.zh.md: bfbf896520f575052ecbff3424ae62f454fd55df

View file

@ -115,7 +115,7 @@ Sources: [`packages/core/session/src/types.ts:336`](../packages/core/session/src
}
```
Source: [`packages/core/agent/src/types.ts:33`](../packages/core/agent/src/types.ts)
Source: [`packages/core/agent/src/types.ts:40`](../packages/core/agent/src/types.ts)
### `agent-preset/*`
@ -649,7 +649,7 @@ Source: [`packages/core/session/src/types.ts:332`](../packages/core/session/src/
Types: [SessionTitleEventData](subsystems/session-title.md)
Source: [`packages/session/session-title/src/index.ts:75`](../packages/session/session-title/src/index.ts)
Source: [`packages/session/session-title/src/index.ts:74`](../packages/session/session-title/src/index.ts)
<a id="sessiontitle-llm-request--log-only"></a>

View file

@ -117,7 +117,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
}
```
来源:[`packages/core/agent/src/types.ts:33`](../packages/core/agent/src/types.ts)
来源:[`packages/core/agent/src/types.ts:40`](../packages/core/agent/src/types.ts)
### `agent-preset/*`
@ -651,7 +651,7 @@ export type SessionEvent<T extends SessionEventType = SessionEventType> = {
类型:[SessionTitleEventData](subsystems/session-title.md)
来源:[`packages/session/session-title/src/index.ts:75`](../packages/session/session-title/src/index.ts)
来源:[`packages/session/session-title/src/index.ts:74`](../packages/session/session-title/src/index.ts)
<a id="sessiontitle-llm-request--log-only"></a>

View file

@ -2,5 +2,5 @@
# side as of the last confirmed-consistent state. Both languages carry equal authority;
# after editing either side, bring the other along and re-record with:
# pnpm run verify-translation-pairing --write docs/subsystems/session-projection.md
session-projection.md: f4fed9421f22c13961f95ba50dac35353b784dca
session-projection.zh.md: 4076edc073748cab1f3da6b6474a962aa2fa0e3b
session-projection.md: 6056b54970857de8d5edf21188c1a7098e06241d
session-projection.zh.md: a39f2c57a2778752779c96e058e2a6ea012a5a44

View file

@ -27,8 +27,6 @@ interface ProjectionDefinition<
key: K
/** Validates persisted state before it seeds a fold. */
stateSchema: ZodType<S>
/** Persist a host-only unit. Client-visible units are always persisted. */
persist?: boolean
/**
* State for the empty log.
* @returns the initial state.
@ -70,15 +68,15 @@ The whole-value event rule is load-bearing: a state-carrying log event carries t
```ts type-equiv
/**
* One consistent read cut over every registered client-visible unit for one session.
* One consistent read cut over every registered unit for one session.
* `asOfSeq` is the shared watermark — the seq of the last event every value
* reflects (`-1` for an empty log, mirroring `session/subscribed.lastSeq`).
*/
interface ProjectionSnapshot {
/** Seq of the last event the values reflect; -1 for an empty log. */
asOfSeq: number
/** Whole current client value per registered key. */
values: Partial<SessionProjectionMap>
/** Whole current value per registered key. */
values: Partial<ProjectionValues>
}
```

View file

@ -27,8 +27,6 @@ interface ProjectionDefinition<
key: K
/** Validates persisted state before it seeds a fold. */
stateSchema: ZodType<S>
/** Persist a host-only unit. Client-visible units are always persisted. */
persist?: boolean
/**
* State for the empty log.
* @returns the initial state.
@ -70,15 +68,15 @@ interface ProjectionDefinition<
```ts type-equiv
/**
* One consistent read cut over every registered client-visible unit for one session.
* One consistent read cut over every registered unit for one session.
* `asOfSeq` is the shared watermark — the seq of the last event every value
* reflects (`-1` for an empty log, mirroring `session/subscribed.lastSeq`).
*/
interface ProjectionSnapshot {
/** Seq of the last event the values reflect; -1 for an empty log. */
asOfSeq: number
/** Whole current client value per registered key. */
values: Partial<SessionProjectionMap>
/** Whole current value per registered key. */
values: Partial<ProjectionValues>
}
```

View file

@ -44,11 +44,10 @@
"@deepseek-ai/cordis": "workspace:^"
},
"devDependencies": {
"@deepseek-ai/cordis": "workspace:^",
"@deepseek-ai/dsh-invariants": "workspace:^",
"@deepseek-ai/dsh-session": "workspace:^",
"@deepseek-ai/dsh-session-persistence": "workspace:^",
"@deepseek-ai/dsh-session-projection": "workspace:^",
"@deepseek-ai/dsh-storage-json": "workspace:^",
"@deepseek-ai/cordis": "workspace:^"
"@deepseek-ai/dsh-session-projection": "workspace:^"
}
}

View file

@ -82,12 +82,15 @@ export class SessionProjectionCache extends Service {
private readonly dirty = new Map<Session, DirtyState>()
/** In-flight durable writes, drained on disposal so a late flush can never land after teardown. */
private readonly inFlight = new Set<Promise<unknown>>()
constructor(ctx: Context, public config: Config) {
super(ctx, 'sessionProjectionCache')
}
/** Install the write-behind listeners. */
protected async [Service.init](): Promise<void> {
protected [Service.init](): void {
this.installWritePath()
}
@ -238,18 +241,18 @@ export class SessionProjectionCache extends Service {
// one), count/interval throttle the in-turn stream.
this.ctx.on('session/event', (session: Session, event: SessionEvent) => {
if (event.type === 'turn/end') {
void this.flushSoft(session, 'turn/end')
this.flushSoft(session, 'turn/end')
return
}
const state = this.dirty.get(session) ?? { pending: 0, timer: undefined }
this.dirty.set(session, state)
state.pending += 1
if (state.pending >= this.config.writeEveryEvents) {
void this.flushSoft(session, 'count threshold')
this.flushSoft(session, 'count threshold')
return
}
state.timer ??= setTimeout(() => {
void this.flushSoft(session, 'interval')
this.flushSoft(session, 'interval')
}, this.config.writeIntervalMs)
})
@ -258,17 +261,20 @@ export class SessionProjectionCache extends Service {
// flushSoft's synchronous prefix reads and resets the dirty state, so
// dropping it (timer already cleared by markClean) right after is safe.
this.ctx.on('session/disposed', (session: Session) => {
void this.flushSoft(session, 'detach')
this.flushSoft(session, 'detach')
this.markClean(session)
this.dirty.delete(session)
})
// Clear pending timers with the plugin (their sessions outlive the cache).
// With the plugin (their sessions outlive the cache): clear pending
// timers, stop accepting new work, and drain in-flight durable writes so
// a late flush can never land after disposal (or overwrite a successor).
this.ctx.effect(() => () => {
for (const state of this.dirty.values()) {
if (state.timer !== undefined) clearTimeout(state.timer)
}
this.dirty.clear()
return Promise.allSettled([...this.inFlight])
}, 'sessionProjectionCache.timers')
}
@ -277,12 +283,16 @@ export class SessionProjectionCache extends Service {
* the throttle triggers only fire dirty (markClean clears the timer with
* the counter) and the two mandatory points write unconditionally.
*/
private async flushSoft(session: Session, trigger: string): Promise<void> {
try {
await this.write(session)
} catch (error) {
this.ctx.logger.warn(`session projection cache: ${trigger} write for "${session.id}" failed (cache stays stale): ${String(error)}`)
}
private flushSoft(session: Session, trigger: string): void {
const run = (async () => {
try {
await this.write(session)
} catch (error) {
this.ctx.logger.warn(`session projection cache: ${trigger} write for "${session.id}" failed (cache stays stale): ${String(error)}`)
}
})()
this.inFlight.add(run)
void run.finally(() => this.inFlight.delete(run))
}
/** Reset one session's dirty bookkeeping (its checkpoint is being written). */

View file

@ -360,15 +360,27 @@ describe('SessionProjectionCache cold read', () => {
it('discards a record bound to a different log lifecycle and refolds from the actual log', async () => {
const root = await mkdtemp(join(tmpdir(), 'dsh-projcache-'))
roots.push(root)
const logs = new Map([['reborn', storedLog([['real']])]]) // stored header stamps createdAt 0
const logs = new Map([
// stored headers stamp createdAt 0
['reborn', storedLog([['real']])],
['reborn-stale', storedLog([['real']])],
])
// A checkpoint from a PRIOR lifecycle of the same id (different createdAt):
// its rows pass every watermark check, but the identity does not match.
await seedRecord(root, 'reborn', { 'cache-test/marks': { ver: 1, seq: 2, val: { marks: ['phantom'] } } }, { createdAt: 999 })
await seedRecord(root, 'reborn-stale', { 'cache-test/marks': { ver: 1, seq: 2, val: { marks: ['phantom'] } } }, { createdAt: 999 })
const { cache, root: sameRoot } = await harness({ root, logs })
// Caller header agrees with the stored log (createdAt 0): the record is
// rejected at read, so the fold seeds from the log alone.
const snapshot = await cache.coldSnapshot(headerOf(SessionId('reborn')))
expect(snapshot.values['cache-test/marks']).toEqual({ marks: ['real'] })
// The write-back rebinds the record to the actual log's identity.
// Caller header matches the STALE record (createdAt 999): the record is
// read, then discarded whole because the log's identity disagrees.
const staleHeader = await cache.coldSnapshot(headerOf(SessionId('reborn-stale'), 999))
expect(staleHeader.values['cache-test/marks']).toEqual({ marks: ['real'] })
// The write-back rebinds each record to the actual log's identity.
expect((await storedRecord(sameRoot, SessionId('reborn')))?.identity).toEqual({ createdAt: 0 })
expect((await storedRecord(sameRoot, SessionId('reborn-stale')))?.identity).toEqual({ createdAt: 0 })
})
it('cachedSnapshot returns undefined when every stored row is version-mismatched', async () => {
@ -435,4 +447,32 @@ describe('SessionProjectionCache cold read', () => {
await expect(ctx.sessionProjectionCache.coldSnapshot(headerOf(SessionId('bare'))))
.resolves.toEqual({ asOfSeq: 2, values: {} })
})
it('disables the durable cache for a backend without a per-session directory (sqlite)', async () => {
// locate() undefined = no per-session artifact: live writes no-op, cold
// reads fall to the full-log rung, and no write-back ever lands.
const root = await mkdtemp(join(tmpdir(), 'dsh-projcache-'))
roots.push(root)
const logs = new Map([['flat', storedLog([['a']])]])
const ctx = new Context()
contexts.push(ctx)
await ctx.plugin(SessionStore)
await ctx.plugin(SessionProjectionRegistry)
ctx.sessionProjections.register(marksUnit())
ctx.provide('sessionPersistence', { ...fakePersistence(root, logs), locate: () => undefined } as never)
await ctx.plugin(SessionProjectionCache, { writeEveryEvents: 100, writeIntervalMs: 60_000 })
const cache = ctx.sessionProjectionCache
// A live mandatory-point write no-ops: no cache file lands.
const session = ctx.sessions.create(SessionId('flat'))
mark(session, ['live'])
endTurn(session)
await settle()
expect(await storedRows(root, session.id)).toBeUndefined()
// The cold read refolds the whole log with no write-back.
const snapshot = await cache.coldSnapshot(headerOf(SessionId('flat')))
expect(snapshot.values['cache-test/marks']).toEqual({ marks: ['a'] })
expect(await storedRecord(root, SessionId('flat'))).toBeUndefined()
// The listing read finds no usable row.
expect(await cache.cachedSnapshot(headerOf(SessionId('flat')))).toBeUndefined()
})
})

3
pnpm-lock.yaml generated
View file

@ -6371,9 +6371,6 @@ importers:
'@deepseek-ai/dsh-session-projection':
specifier: workspace:^
version: link:../session-projection
'@deepseek-ai/dsh-storage':
specifier: workspace:^
version: link:../../storage/storage
packages/session/session-stats:
dependencies: