@deepseek-ai/dsh-api-session-controller 0.1.2-alpha.3 → 0.1.2-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.i18n.yaml CHANGED
@@ -2,5 +2,5 @@
2
2
  # side as of the last confirmed-consistent state. Both languages carry equal authority;
3
3
  # after editing either side, bring the other along and re-record with:
4
4
  # pnpm run verify-translation-pairing --write packages/api/session-controller/README.md
5
- README.md: 94e8697cd7f5bf75746edd5d691956e680d9b261
6
- README.zh.md: 0228ca15624413fcd5cb6e17afb8aaf0a28f780a
5
+ README.md: b3d22a340fff6a49270de520a043a1c8dfdbf096
6
+ README.zh.md: 45590a53db17b32cc59f6107957c284b53ee8302
package/README.md CHANGED
@@ -72,3 +72,5 @@ No direct effect; model requests remain owned by the Agent and LLM packages.
72
72
  None.
73
73
 
74
74
  </details>
75
+
76
+ **Runtime invariant:** No companion is published. Every page and frame is checked against the addressed durable Session.
package/README.zh.md CHANGED
@@ -72,3 +72,5 @@ Session 对象还承载本地提交回显:`session.beginSubmission` 在调用
72
72
  无。
73
73
 
74
74
  </details>
75
+
76
+ **运行时不变式:** 不发布伴生入口。每个分页与帧都会对照其指向的持久 Session 校验。
package/lib/client.js CHANGED
@@ -176,6 +176,36 @@ window.__ModuleLoader__.load({
176
176
  }
177
177
  };
178
178
  //#endregion
179
+ //#region ../../util/brand/lib/index.js
180
+ /**
181
+ * Apply a compile-time number brand without changing the value.
182
+ * @param value - number admitted by the domain that owns the target brand.
183
+ * @returns the same number with the requested compile-time brand.
184
+ */
185
+ function brandNumber(value) {
186
+ return value;
187
+ }
188
+ //#endregion
189
+ //#region ../../core/session/lib/types/types.js
190
+ /**
191
+ * Admit a numeric value as an existing Session event position.
192
+ * @param value - non-negative safe integer admitted by the owning log operation.
193
+ * @returns the same number with the Session-sequence brand.
194
+ */
195
+ function SessionSeq(value) {
196
+ if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionSeq must be a non-negative safe integer, got ${String(value)}`);
197
+ return brandNumber(value);
198
+ }
199
+ /**
200
+ * Admit a numeric value as a Session log offset.
201
+ * @param value - non-negative safe integer used as a gap or prefix length.
202
+ * @returns the same number with the Session-log-offset brand.
203
+ */
204
+ function SessionLogOffset(value) {
205
+ if (!Number.isSafeInteger(value) || value < 0 || Object.is(value, -0)) throw new TypeError(`SessionLogOffset must be a non-negative safe integer, got ${String(value)}`);
206
+ return brandNumber(value);
207
+ }
208
+ //#endregion
179
209
  //#region ../../util/workspace-path/lib/index.js
180
210
  /**
181
211
  * Read the final non-empty segment of a Workspace path for display.
@@ -721,6 +751,14 @@ window.__ModuleLoader__.load({
721
751
  return true;
722
752
  }
723
753
  };
754
+ //#endregion
755
+ //#region lib/types/client/sessions/session.js
756
+ function projectionsBaseline(value) {
757
+ return {
758
+ ...value,
759
+ asOfSeq: value.asOfSeq === -1 ? -1 : SessionSeq(value.asOfSeq)
760
+ };
761
+ }
724
762
  /**
725
763
  * Owns a session's event window, lifecycle state, and observable
726
764
  * snapshot. React bindings remain outside this data layer. Features see only
@@ -731,7 +769,7 @@ window.__ModuleLoader__.load({
731
769
  sessionId;
732
770
  remote;
733
771
  options;
734
- baseSeq = 0;
772
+ baseSeq = SessionLogOffset(0);
735
773
  hasMore = false;
736
774
  openState = "cold";
737
775
  openError = null;
@@ -972,8 +1010,16 @@ window.__ModuleLoader__.load({
972
1010
  sessionId: this.sessionId,
973
1011
  title
974
1012
  });
975
- if (result.ok) this.projections.apply("title", result.value.title, result.value.seq);
976
- return result;
1013
+ if (!result.ok) return result;
1014
+ const seq = SessionSeq(result.value.seq);
1015
+ this.projections.apply("title", result.value.title, seq);
1016
+ return {
1017
+ ok: true,
1018
+ value: {
1019
+ title: result.value.title,
1020
+ seq
1021
+ }
1022
+ };
977
1023
  }
978
1024
  /**
979
1025
  * Execute one slash-command line against this session's agent — pure
@@ -1023,7 +1069,7 @@ window.__ModuleLoader__.load({
1023
1069
  loadThrough(seq) {
1024
1070
  if (this.openState !== "open" || !this.hasMore || this.baseSeq <= seq) return Promise.resolve();
1025
1071
  if (this.jumpPromise !== null) {
1026
- this.jumpTargetSeq = Math.min(this.jumpTargetSeq ?? seq, seq);
1072
+ this.jumpTargetSeq = SessionSeq(Math.min(this.jumpTargetSeq ?? seq, seq));
1027
1073
  return this.jumpPromise;
1028
1074
  }
1029
1075
  if (this.loadingOlder) return Promise.resolve();
@@ -1067,7 +1113,7 @@ window.__ModuleLoader__.load({
1067
1113
  this.openPromise = null;
1068
1114
  this.openState = "cold";
1069
1115
  this.openError = null;
1070
- this.baseSeq = 0;
1116
+ this.baseSeq = SessionLogOffset(0);
1071
1117
  this.notifier.markDirty();
1072
1118
  await this.open();
1073
1119
  }
@@ -1211,7 +1257,7 @@ window.__ModuleLoader__.load({
1211
1257
  acceptEventChange(change) {
1212
1258
  switch (change.type) {
1213
1259
  case "replace":
1214
- this.installWindow(change.entries, change.hasMore, change.page.projections);
1260
+ this.installWindow(change.entries, change.hasMore, change.page.projections === void 0 ? void 0 : projectionsBaseline(change.page.projections));
1215
1261
  return;
1216
1262
  case "prepend":
1217
1263
  this.prependWindow(change.entries, change.hasMore);
@@ -1221,7 +1267,7 @@ window.__ModuleLoader__.load({
1221
1267
  }
1222
1268
  /** Replace the complete contiguous window and apply page-owned projection metadata. */
1223
1269
  installWindow(entries, hasMore, projections) {
1224
- this.baseSeq = entries[0]?.event.seq ?? 0;
1270
+ this.baseSeq = SessionLogOffset(entries[0]?.event.seq ?? 0);
1225
1271
  this.hasMore = hasMore;
1226
1272
  if (entries.some((entry) => entry.event.type === "turn/start")) this.firstPromptPendingTurn = false;
1227
1273
  if (projections !== void 0) this.projections.seed(projections);
@@ -1231,7 +1277,7 @@ window.__ModuleLoader__.load({
1231
1277
  }
1232
1278
  /** Prepend one stream-validated history page. */
1233
1279
  prependWindow(entries, hasMore) {
1234
- this.baseSeq = entries[0]?.event.seq ?? this.baseSeq;
1280
+ this.baseSeq = entries[0] === void 0 ? this.baseSeq : SessionLogOffset(entries[0].event.seq);
1235
1281
  this.hasMore = hasMore;
1236
1282
  this.eventSource.prepend(entries, hasMore);
1237
1283
  }
@@ -1356,6 +1402,9 @@ window.__ModuleLoader__.load({
1356
1402
  }
1357
1403
  //#endregion
1358
1404
  //#region lib/types/client/sessions/manager.js
1405
+ function sessionSeqCursor(value) {
1406
+ return value === -1 ? -1 : SessionSeq(value);
1407
+ }
1359
1408
  function catalogAvailability(parentAvailable) {
1360
1409
  return parentAvailable === void 0 ? {} : { parentAvailable };
1361
1410
  }
@@ -1689,7 +1738,7 @@ window.__ModuleLoader__.load({
1689
1738
  if (block === void 0) continue;
1690
1739
  const store = this.projectionStore(s.sessionId);
1691
1740
  const values = block.values;
1692
- for (const key of Object.keys(values)) store.apply(key, values[key], block.asOfSeq);
1741
+ for (const key of Object.keys(values)) store.apply(key, values[key], sessionSeqCursor(block.asOfSeq));
1693
1742
  }
1694
1743
  } else {
1695
1744
  this.listState = "error";
@@ -1840,7 +1889,7 @@ window.__ModuleLoader__.load({
1840
1889
  return;
1841
1890
  }
1842
1891
  if (frame.type === "projection") {
1843
- this.projectionStore(frame.sessionId).apply(frame.key, frame.value, frame.seq);
1892
+ this.projectionStore(frame.sessionId).apply(frame.key, frame.value, SessionSeq(frame.seq));
1844
1893
  this.notifier.markDirty();
1845
1894
  return;
1846
1895
  }
@@ -1860,8 +1909,12 @@ window.__ModuleLoader__.load({
1860
1909
  for (const [sessionId, jobs] of Object.entries(baseline.jobs)) if (jobs.length > 0) this.jobsBySession.set(sessionId, jobs);
1861
1910
  for (const [sessionId, block] of Object.entries(baseline.projections)) {
1862
1911
  const store = this.projectionStore(sessionId);
1863
- store.truncate(block.asOfSeq);
1864
- store.seed(block);
1912
+ const asOfSeq = sessionSeqCursor(block.asOfSeq);
1913
+ store.truncate(asOfSeq);
1914
+ store.seed({
1915
+ ...block,
1916
+ asOfSeq
1917
+ });
1865
1918
  }
1866
1919
  for (const [sessionId, session] of this.sessions) session.replaceControl(this.queues.get(sessionId) ?? []);
1867
1920
  this.notifier.markDirty();
@@ -1876,7 +1929,7 @@ window.__ModuleLoader__.load({
1876
1929
  const projections = summary.projections;
1877
1930
  if (projections !== void 0) {
1878
1931
  const store = this.projectionStore(summary.sessionId);
1879
- for (const [key, value] of Object.entries(projections.values)) store.apply(key, value, projections.asOfSeq);
1932
+ for (const [key, value] of Object.entries(projections.values)) store.apply(key, value, sessionSeqCursor(projections.asOfSeq));
1880
1933
  }
1881
1934
  if (summary.origin === "subagent" && summary.parentSessionId !== void 0) this.markCatalogParentExpandable(summary.parentSessionId);
1882
1935
  if (summary.parentSessionId !== void 0 && (this.selected === summary.parentSessionId || this.openCatalogs.has(summary.parentSessionId))) this.scheduleCatalogRefresh(summary.parentSessionId);
@@ -2400,7 +2453,7 @@ window.__ModuleLoader__.load({
2400
2453
  const sourceTitle = opts.increaseTitle ? this.list.getSnapshot().byId[opts.sessionId]?.title : void 0;
2401
2454
  const result = await this.manager.fork({
2402
2455
  sessionId: opts.sessionId,
2403
- ...opts.atSeq === void 0 ? {} : { atSeq: Math.floor(opts.atSeq) }
2456
+ ...opts.atSeq === void 0 ? {} : { atSeq: SessionSeq(Math.floor(opts.atSeq)) }
2404
2457
  });
2405
2458
  if (!result.ok) throw new SessionForkError(result.error, opts.sessionId);
2406
2459
  this.projectList();
package/lib/index.js CHANGED
@@ -8,10 +8,10 @@ import { SessionQueryError } from "@deepseek-ai/dsh-session-query";
8
8
  import { randomUUID } from "node:crypto";
9
9
  import { brandString } from "@deepseek-ai/dsh-brand";
10
10
  import { AttachmentError, admitPromptContent } from "@deepseek-ai/dsh-attachment";
11
+ import { SessionLogOffset, SessionSeq, isAppendSurfaceEvent } from "@deepseek-ai/dsh-session";
11
12
  import { SessionTitleInvalidError } from "@deepseek-ai/dsh-session-title";
12
13
  import { canonicalClientTimeZone } from "@deepseek-ai/dsh-util-time";
13
14
  import { Deque } from "@deepseek-ai/dsh-deque";
14
- import { isAppendSurfaceEvent } from "@deepseek-ai/dsh-session";
15
15
  import { isChunkRow, packChunkRuns } from "@deepseek-ai/dsh-session/chunk-rows";
16
16
  import { z as z$1 } from "zod";
17
17
  import { isUserInvocable } from "@deepseek-ai/dsh-skill";
@@ -154,6 +154,7 @@ async function inspectApiSession(ctx, sessionId, signal) {
154
154
  if (observation.header.cwd === void 0) throw new ApiSessionNotFound(`session "${sessionId}" not found`);
155
155
  return {
156
156
  meta: observation.header,
157
+ inheritedEventCount: observation.inheritedEventCount,
157
158
  events: [...observation.events]
158
159
  };
159
160
  } catch (e_1) {
@@ -658,7 +659,12 @@ var SessionCommandController = class {
658
659
  hasError: false
659
660
  };
660
661
  try {
661
- if (request.atSeq !== void 0 && (!Number.isInteger(request.atSeq) || request.atSeq < 0)) throw new RemoteError("gateway/bad-request", "atSeq must be a non-negative integer", {});
662
+ let atSeq;
663
+ try {
664
+ atSeq = request.atSeq === void 0 ? void 0 : SessionSeq(request.atSeq);
665
+ } catch {
666
+ throw new RemoteError("gateway/bad-request", "atSeq must be a non-negative safe integer", {});
667
+ }
662
668
  let observed;
663
669
  try {
664
670
  observed = await this.ctx.sessionQuery.observeSession(request.sessionId);
@@ -668,11 +674,10 @@ var SessionCommandController = class {
668
674
  }
669
675
  const source = __addDisposableResource$4(env_1, observed, false);
670
676
  const lastSeq = source.events.at(-1)?.seq ?? -1;
671
- const atSeq = request.atSeq;
672
677
  const boundary = (atSeq === void 0 ? void 0 : source.events.find((event) => event.type === "turn/end" && event.seq >= atSeq)) ?? (atSeq === void 0 || atSeq > lastSeq ? source.events.findLast((event) => event.type === "turn/end") : void 0);
673
678
  if (boundary === void 0) throw new RemoteError("session/fork-unavailable", atSeq !== void 0 && atSeq <= lastSeq ? `session "${request.sessionId}" has not completed the turn containing event ${String(atSeq)}` : `session "${request.sessionId}" has no completed turn to fork from`, { sessionId: request.sessionId });
674
- let cut = boundary.seq + 1;
675
- while (cut < source.events.length && source.events[cut]?.type !== "turn/start") cut++;
679
+ let cut = SessionLogOffset(boundary.seq + 1);
680
+ while (cut < source.events.length && source.events[cut]?.type !== "turn/start") cut = SessionLogOffset(cut + 1);
676
681
  let workspace;
677
682
  try {
678
683
  workspace = await this.forkWorkspace(source.header);
@@ -686,10 +691,11 @@ var SessionCommandController = class {
686
691
  await this.ctx.agents.create({
687
692
  sessionId: childId,
688
693
  seed: source.events.slice(0, cut),
694
+ inheritedEventCount: cut,
689
695
  meta: {
690
696
  ...source.header.cwd === void 0 ? {} : { cwd: source.header.cwd },
691
697
  parentSession: source.header.id,
692
- seedLength: cut,
698
+ isSeeded: true,
693
699
  ...composition.agentPreset === void 0 ? {} : { agentPreset: composition.agentPreset }
694
700
  },
695
701
  agentOptions: {
@@ -854,7 +860,7 @@ var SessionCommandController = class {
854
860
  if (attached !== void 0) return {
855
861
  id: attached.id,
856
862
  header: attached.header,
857
- events: [...attached.events]
863
+ events: attached.snapshotEvents()
858
864
  };
859
865
  const inspected = await inspectApiSession(this.ctx, sessionId);
860
866
  return {
@@ -1203,14 +1209,16 @@ var SessionHistoryController = class {
1203
1209
  };
1204
1210
  try {
1205
1211
  validatePageRequest(request);
1212
+ const throughSeq = request.throughSeq === -1 ? -1 : SessionSeq(request.throughSeq);
1213
+ const beforeSeq = request.beforeSeq === void 0 ? void 0 : SessionLogOffset(request.beforeSeq);
1206
1214
  const source = __addDisposableResource$3(env_1, await this.sourceFor(request.address, signal, false), false);
1207
1215
  signal.throwIfAborted();
1208
1216
  const sourceLog = source.events;
1209
1217
  const sourceCursor = sourceLog.at(-1)?.seq ?? -1;
1210
- if (request.throughSeq > sourceCursor) throw new RemoteError("gateway/bad-request", `session page through seq ${String(request.throughSeq)} is past cursor ${String(sourceCursor)}`, {});
1218
+ if (throughSeq > sourceCursor) throw new RemoteError("gateway/bad-request", `session page through seq ${String(throughSeq)} is past cursor ${String(sourceCursor)}`, {});
1211
1219
  /* v8 ignore next -- Session and persistence validation guarantee a dense zero-based event prefix. */
1212
- if (request.throughSeq >= 0 && sourceLog[request.throughSeq]?.seq !== request.throughSeq) throw new RemoteError("gateway/internal", `session log does not contain through seq ${String(request.throughSeq)}`, {});
1213
- const page = paginate(sourceLog, request.beforeSeq, request.maxMessages ?? DEFAULT_MAX_MESSAGES, request.throughSeq);
1220
+ if (throughSeq >= 0 && sourceLog[throughSeq]?.seq !== throughSeq) throw new RemoteError("gateway/internal", `session log does not contain through seq ${String(throughSeq)}`, {});
1221
+ const page = paginate(sourceLog, beforeSeq, request.maxMessages ?? DEFAULT_MAX_MESSAGES, throughSeq);
1214
1222
  return {
1215
1223
  records: pageRecords(page.events),
1216
1224
  hasMore: page.hasMore
@@ -1253,7 +1261,7 @@ var SessionHistoryController = class {
1253
1261
  }, { global: true });
1254
1262
  const disposeCreated = this.ctx.on("session/created", (session) => {
1255
1263
  if (session.id !== target) return;
1256
- const suffix = session.events.slice(snapshotCursor === void 0 ? session.firstLiveSeq : snapshotCursor + 1);
1264
+ const suffix = session.snapshotEvents(snapshotCursor === void 0 ? session.firstLiveSeq : SessionLogOffset(snapshotCursor + 1));
1257
1265
  for (let index = suffix.length - 1; index >= 0; index -= 1) buffered.pushFront(suffix[index]);
1258
1266
  notify();
1259
1267
  }, { global: true });
@@ -1276,7 +1284,7 @@ var SessionHistoryController = class {
1276
1284
  const page = paginate(events, void 0, request.maxMessages ?? DEFAULT_MAX_MESSAGES);
1277
1285
  yield {
1278
1286
  type: "snapshot",
1279
- header: source.header,
1287
+ header: wireHeader(source.header, source.inheritedEventCount),
1280
1288
  cursor,
1281
1289
  records: pageRecords(page.events),
1282
1290
  hasMore: page.hasMore,
@@ -1294,7 +1302,7 @@ var SessionHistoryController = class {
1294
1302
  throw error;
1295
1303
  }
1296
1304
  }
1297
- let nextSeq = cursor + 1;
1305
+ let nextOffset = SessionLogOffset(cursor + 1);
1298
1306
  while (!follower.closed && !signal.aborted) {
1299
1307
  const item = buffered.popFront();
1300
1308
  if (item === void 0) {
@@ -1303,9 +1311,10 @@ var SessionHistoryController = class {
1303
1311
  });
1304
1312
  continue;
1305
1313
  }
1306
- if (item.seq < nextSeq) continue;
1307
- if (item.seq !== nextSeq) throw new RemoteError("gateway/internal", `session event stream skipped seq ${String(nextSeq)}`, {});
1308
- nextSeq++;
1314
+ const expectedSeq = SessionSeq(nextOffset);
1315
+ if (item.seq < expectedSeq) continue;
1316
+ if (item.seq !== expectedSeq) throw new RemoteError("gateway/internal", `session event stream skipped seq ${String(expectedSeq)}`, {});
1317
+ nextOffset = SessionLogOffset(nextOffset + 1);
1309
1318
  yield entryFor(item);
1310
1319
  }
1311
1320
  } catch (e_2) {
@@ -1333,7 +1342,7 @@ var SessionHistoryController = class {
1333
1342
  rejectNotFound(address);
1334
1343
  }
1335
1344
  try {
1336
- validateAddress(address, observation.header, observation.projections);
1345
+ validateAddress(address, observation.header, observation.inheritedEventCount, observation.projections);
1337
1346
  } catch (error) {
1338
1347
  observation[Symbol.dispose]();
1339
1348
  throw error;
@@ -1352,8 +1361,8 @@ function projectionBlock(snapshot) {
1352
1361
  };
1353
1362
  }
1354
1363
  function validatePageRequest(request) {
1355
- if (!Number.isSafeInteger(request.throughSeq) || request.throughSeq < -1) throw new RemoteError("gateway/bad-request", "throughSeq must be an integer greater than or equal to -1", {});
1356
- if (request.beforeSeq !== void 0 && (!Number.isSafeInteger(request.beforeSeq) || request.beforeSeq < 0)) throw new RemoteError("gateway/bad-request", "beforeSeq must be a non-negative safe integer", {});
1364
+ if (!Number.isSafeInteger(request.throughSeq) || request.throughSeq < -1 || Object.is(request.throughSeq, -0)) throw new RemoteError("gateway/bad-request", "throughSeq must be an integer greater than or equal to -1", {});
1365
+ if (request.beforeSeq !== void 0 && (!Number.isSafeInteger(request.beforeSeq) || request.beforeSeq < 0 || Object.is(request.beforeSeq, -0))) throw new RemoteError("gateway/bad-request", "beforeSeq must be a non-negative safe integer", {});
1357
1366
  if (request.maxMessages !== void 0 && (!Number.isSafeInteger(request.maxMessages) || request.maxMessages <= 0)) throw new RemoteError("gateway/bad-request", "maxMessages must be a positive safe integer", {});
1358
1367
  }
1359
1368
  function validateFollowRequest(request) {
@@ -1362,7 +1371,7 @@ function validateFollowRequest(request) {
1362
1371
  function addressId(address) {
1363
1372
  return address.kind === "session" ? address.sessionId : address.childSessionId;
1364
1373
  }
1365
- function validateAddress(address, header, projections) {
1374
+ function validateAddress(address, header, inheritedEventCount, projections) {
1366
1375
  if (address.kind === "session") {
1367
1376
  if (header.origin === "subagent") throw new RemoteError("session/agent-busy", "subagent Sessions require their durable parent address", { reason: "use subagent delivery for this child session" });
1368
1377
  return;
@@ -1374,7 +1383,7 @@ function validateAddress(address, header, projections) {
1374
1383
  childSessionId: address.childSessionId,
1375
1384
  reason: "corrupt"
1376
1385
  });
1377
- if (identity === void 0 || identity.seq < (header.seedLength ?? 0)) throw new RemoteError("subagent/catalog-diagnostic", "subagent descriptor is unavailable", {
1386
+ if (identity === void 0 || identity.seq < inheritedEventCount) throw new RemoteError("subagent/catalog-diagnostic", "subagent descriptor is unavailable", {
1378
1387
  parentSessionId: address.parentSessionId,
1379
1388
  childSessionId: address.childSessionId,
1380
1389
  reason: "unsupported"
@@ -1389,18 +1398,20 @@ function rejectNotFound(address) {
1389
1398
  });
1390
1399
  }
1391
1400
  function paginate(events, beforeSeq, maxMessages, throughSeq = events.at(-1)?.seq ?? -1) {
1392
- const end = Math.min(throughSeq + 1, beforeSeq ?? throughSeq + 1);
1401
+ const end = SessionLogOffset(Math.min(throughSeq + 1, beforeSeq ?? throughSeq + 1));
1393
1402
  let count = 0;
1394
- let cut = 0;
1403
+ let cut = SessionLogOffset(0);
1395
1404
  for (let index = end - 1; index >= 0; index--) {
1396
1405
  const event = events[index];
1397
1406
  if (!MESSAGE_TYPES$1.has(event.type) || !isAppendSurfaceEvent(event)) continue;
1398
1407
  count++;
1399
1408
  const sources = event.sourceEventSeqs;
1400
1409
  let groupStart = event.seq;
1401
- if (sources !== void 0) for (const source of sources) groupStart = Math.min(groupStart, source);
1410
+ if (sources !== void 0) {
1411
+ for (const source of sources) if (source < groupStart) groupStart = source;
1412
+ }
1402
1413
  if (count >= maxMessages) {
1403
- cut = groupStart;
1414
+ cut = SessionLogOffset(groupStart);
1404
1415
  break;
1405
1416
  }
1406
1417
  }
@@ -1409,6 +1420,14 @@ function paginate(events, beforeSeq, maxMessages, throughSeq = events.at(-1)?.se
1409
1420
  hasMore: cut > 0
1410
1421
  };
1411
1422
  }
1423
+ /** Translate logical Session metadata to the unchanged v0 browser wire. */
1424
+ function wireHeader(header, inheritedEventCount) {
1425
+ const { isSeeded, ...wire } = header;
1426
+ return {
1427
+ ...wire,
1428
+ ...isSeeded ? { seedLength: inheritedEventCount } : {}
1429
+ };
1430
+ }
1412
1431
  function entryFor(event) {
1413
1432
  return {
1414
1433
  type: "event",
@@ -1874,7 +1893,7 @@ var ApiSessionList = class {
1874
1893
  }
1875
1894
  projectionsFor(header, session) {
1876
1895
  try {
1877
- const block = session === void 0 ? this.ctx.get("sessionProjectionCache")?.cachedSnapshot(header) : this.ctx.sessionProjections.cachedSnapshot(session);
1896
+ const block = session === void 0 ? header.isSeeded ? void 0 : this.ctx.get("sessionProjectionCache")?.cachedSnapshot(header, SessionLogOffset(0)) : this.ctx.sessionProjections.cachedSnapshot(session);
1878
1897
  return block !== void 0 && Object.keys(block.values).length > 0 ? {
1879
1898
  asOfSeq: block.asOfSeq,
1880
1899
  values: block.values
@@ -2657,7 +2676,8 @@ let SessionController = (() => {
2657
2676
  const attached = this.ctx.sessions.get(sessionId);
2658
2677
  if (attached !== void 0) return Promise.resolve({
2659
2678
  meta: attached.header,
2660
- events: [...attached.events]
2679
+ inheritedEventCount: attached.inheritedEventCount,
2680
+ events: attached.snapshotEvents()
2661
2681
  });
2662
2682
  return inspectApiSession(this.ctx, sessionId, signal);
2663
2683
  }