@crazx/dsh-api-session-controller 0.1.2-alpha.3.zw.2 → 0.1.2-alpha.5.zw.1

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) {
@@ -675,7 +676,12 @@ var SessionCommandController = class {
675
676
  hasError: false
676
677
  };
677
678
  try {
678
- 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", {});
679
+ let atSeq;
680
+ try {
681
+ atSeq = request.atSeq === void 0 ? void 0 : SessionSeq(request.atSeq);
682
+ } catch {
683
+ throw new RemoteError("gateway/bad-request", "atSeq must be a non-negative safe integer", {});
684
+ }
679
685
  let observed;
680
686
  try {
681
687
  observed = await this.ctx.sessionQuery.observeSession(request.sessionId);
@@ -685,11 +691,10 @@ var SessionCommandController = class {
685
691
  }
686
692
  const source = __addDisposableResource$4(env_1, observed, false);
687
693
  const lastSeq = source.events.at(-1)?.seq ?? -1;
688
- const atSeq = request.atSeq;
689
694
  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);
690
695
  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 });
691
- let cut = boundary.seq + 1;
692
- while (cut < source.events.length && source.events[cut]?.type !== "turn/start") cut++;
696
+ let cut = SessionLogOffset(boundary.seq + 1);
697
+ while (cut < source.events.length && source.events[cut]?.type !== "turn/start") cut = SessionLogOffset(cut + 1);
693
698
  let workspace;
694
699
  try {
695
700
  workspace = await this.forkWorkspace(source.header);
@@ -703,10 +708,11 @@ var SessionCommandController = class {
703
708
  await this.ctx.agents.create({
704
709
  sessionId: childId,
705
710
  seed: source.events.slice(0, cut),
711
+ inheritedEventCount: cut,
706
712
  meta: {
707
713
  ...source.header.cwd === void 0 ? {} : { cwd: source.header.cwd },
708
714
  parentSession: source.header.id,
709
- seedLength: cut,
715
+ isSeeded: true,
710
716
  ...composition.agentPreset === void 0 ? {} : { agentPreset: composition.agentPreset }
711
717
  },
712
718
  agentOptions: {
@@ -871,7 +877,7 @@ var SessionCommandController = class {
871
877
  if (attached !== void 0) return {
872
878
  id: attached.id,
873
879
  header: attached.header,
874
- events: [...attached.events]
880
+ events: attached.snapshotEvents()
875
881
  };
876
882
  const inspected = await inspectApiSession(this.ctx, sessionId);
877
883
  return {
@@ -1220,14 +1226,16 @@ var SessionHistoryController = class {
1220
1226
  };
1221
1227
  try {
1222
1228
  validatePageRequest(request);
1229
+ const throughSeq = request.throughSeq === -1 ? -1 : SessionSeq(request.throughSeq);
1230
+ const beforeSeq = request.beforeSeq === void 0 ? void 0 : SessionLogOffset(request.beforeSeq);
1223
1231
  const source = __addDisposableResource$3(env_1, await this.sourceFor(request.address, signal, false), false);
1224
1232
  signal.throwIfAborted();
1225
1233
  const sourceLog = source.events;
1226
1234
  const sourceCursor = sourceLog.at(-1)?.seq ?? -1;
1227
- if (request.throughSeq > sourceCursor) throw new RemoteError("gateway/bad-request", `session page through seq ${String(request.throughSeq)} is past cursor ${String(sourceCursor)}`, {});
1235
+ if (throughSeq > sourceCursor) throw new RemoteError("gateway/bad-request", `session page through seq ${String(throughSeq)} is past cursor ${String(sourceCursor)}`, {});
1228
1236
  /* v8 ignore next -- Session and persistence validation guarantee a dense zero-based event prefix. */
1229
- 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)}`, {});
1230
- const page = paginate(sourceLog, request.beforeSeq, request.maxMessages ?? DEFAULT_MAX_MESSAGES, request.throughSeq);
1237
+ if (throughSeq >= 0 && sourceLog[throughSeq]?.seq !== throughSeq) throw new RemoteError("gateway/internal", `session log does not contain through seq ${String(throughSeq)}`, {});
1238
+ const page = paginate(sourceLog, beforeSeq, request.maxMessages ?? DEFAULT_MAX_MESSAGES, throughSeq);
1231
1239
  return {
1232
1240
  records: pageRecords(page.events),
1233
1241
  hasMore: page.hasMore
@@ -1270,7 +1278,7 @@ var SessionHistoryController = class {
1270
1278
  }, { global: true });
1271
1279
  const disposeCreated = this.ctx.on("session/created", (session) => {
1272
1280
  if (session.id !== target) return;
1273
- const suffix = session.events.slice(snapshotCursor === void 0 ? session.firstLiveSeq : snapshotCursor + 1);
1281
+ const suffix = session.snapshotEvents(snapshotCursor === void 0 ? session.firstLiveSeq : SessionLogOffset(snapshotCursor + 1));
1274
1282
  for (let index = suffix.length - 1; index >= 0; index -= 1) buffered.pushFront(suffix[index]);
1275
1283
  notify();
1276
1284
  }, { global: true });
@@ -1293,7 +1301,7 @@ var SessionHistoryController = class {
1293
1301
  const page = paginate(events, void 0, request.maxMessages ?? DEFAULT_MAX_MESSAGES);
1294
1302
  yield {
1295
1303
  type: "snapshot",
1296
- header: source.header,
1304
+ header: wireHeader(source.header, source.inheritedEventCount),
1297
1305
  cursor,
1298
1306
  records: pageRecords(page.events),
1299
1307
  hasMore: page.hasMore,
@@ -1311,7 +1319,7 @@ var SessionHistoryController = class {
1311
1319
  throw error;
1312
1320
  }
1313
1321
  }
1314
- let nextSeq = cursor + 1;
1322
+ let nextOffset = SessionLogOffset(cursor + 1);
1315
1323
  while (!follower.closed && !signal.aborted) {
1316
1324
  const item = buffered.popFront();
1317
1325
  if (item === void 0) {
@@ -1320,9 +1328,10 @@ var SessionHistoryController = class {
1320
1328
  });
1321
1329
  continue;
1322
1330
  }
1323
- if (item.seq < nextSeq) continue;
1324
- if (item.seq !== nextSeq) throw new RemoteError("gateway/internal", `session event stream skipped seq ${String(nextSeq)}`, {});
1325
- nextSeq++;
1331
+ const expectedSeq = SessionSeq(nextOffset);
1332
+ if (item.seq < expectedSeq) continue;
1333
+ if (item.seq !== expectedSeq) throw new RemoteError("gateway/internal", `session event stream skipped seq ${String(expectedSeq)}`, {});
1334
+ nextOffset = SessionLogOffset(nextOffset + 1);
1326
1335
  yield entryFor(item);
1327
1336
  }
1328
1337
  } catch (e_2) {
@@ -1350,7 +1359,7 @@ var SessionHistoryController = class {
1350
1359
  rejectNotFound(address);
1351
1360
  }
1352
1361
  try {
1353
- validateAddress(address, observation.header, observation.projections);
1362
+ validateAddress(address, observation.header, observation.inheritedEventCount, observation.projections);
1354
1363
  } catch (error) {
1355
1364
  observation[Symbol.dispose]();
1356
1365
  throw error;
@@ -1369,8 +1378,8 @@ function projectionBlock(snapshot) {
1369
1378
  };
1370
1379
  }
1371
1380
  function validatePageRequest(request) {
1372
- 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", {});
1373
- 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", {});
1381
+ 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", {});
1382
+ 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", {});
1374
1383
  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", {});
1375
1384
  }
1376
1385
  function validateFollowRequest(request) {
@@ -1379,7 +1388,7 @@ function validateFollowRequest(request) {
1379
1388
  function addressId(address) {
1380
1389
  return address.kind === "session" ? address.sessionId : address.childSessionId;
1381
1390
  }
1382
- function validateAddress(address, header, projections) {
1391
+ function validateAddress(address, header, inheritedEventCount, projections) {
1383
1392
  if (address.kind === "session") {
1384
1393
  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" });
1385
1394
  return;
@@ -1391,7 +1400,7 @@ function validateAddress(address, header, projections) {
1391
1400
  childSessionId: address.childSessionId,
1392
1401
  reason: "corrupt"
1393
1402
  });
1394
- if (identity === void 0 || identity.seq < (header.seedLength ?? 0)) throw new RemoteError("subagent/catalog-diagnostic", "subagent descriptor is unavailable", {
1403
+ if (identity === void 0 || identity.seq < inheritedEventCount) throw new RemoteError("subagent/catalog-diagnostic", "subagent descriptor is unavailable", {
1395
1404
  parentSessionId: address.parentSessionId,
1396
1405
  childSessionId: address.childSessionId,
1397
1406
  reason: "unsupported"
@@ -1406,18 +1415,20 @@ function rejectNotFound(address) {
1406
1415
  });
1407
1416
  }
1408
1417
  function paginate(events, beforeSeq, maxMessages, throughSeq = events.at(-1)?.seq ?? -1) {
1409
- const end = Math.min(throughSeq + 1, beforeSeq ?? throughSeq + 1);
1418
+ const end = SessionLogOffset(Math.min(throughSeq + 1, beforeSeq ?? throughSeq + 1));
1410
1419
  let count = 0;
1411
- let cut = 0;
1420
+ let cut = SessionLogOffset(0);
1412
1421
  for (let index = end - 1; index >= 0; index--) {
1413
1422
  const event = events[index];
1414
1423
  if (!MESSAGE_TYPES$1.has(event.type) || !isAppendSurfaceEvent(event)) continue;
1415
1424
  count++;
1416
1425
  const sources = event.sourceEventSeqs;
1417
1426
  let groupStart = event.seq;
1418
- if (sources !== void 0) for (const source of sources) groupStart = Math.min(groupStart, source);
1427
+ if (sources !== void 0) {
1428
+ for (const source of sources) if (source < groupStart) groupStart = source;
1429
+ }
1419
1430
  if (count >= maxMessages) {
1420
- cut = groupStart;
1431
+ cut = SessionLogOffset(groupStart);
1421
1432
  break;
1422
1433
  }
1423
1434
  }
@@ -1426,6 +1437,14 @@ function paginate(events, beforeSeq, maxMessages, throughSeq = events.at(-1)?.se
1426
1437
  hasMore: cut > 0
1427
1438
  };
1428
1439
  }
1440
+ /** Translate logical Session metadata to the unchanged v0 browser wire. */
1441
+ function wireHeader(header, inheritedEventCount) {
1442
+ const { isSeeded, ...wire } = header;
1443
+ return {
1444
+ ...wire,
1445
+ ...isSeeded ? { seedLength: inheritedEventCount } : {}
1446
+ };
1447
+ }
1429
1448
  function entryFor(event) {
1430
1449
  return {
1431
1450
  type: "event",
@@ -1891,7 +1910,7 @@ var ApiSessionList = class {
1891
1910
  }
1892
1911
  projectionsFor(header, session) {
1893
1912
  try {
1894
- const block = session === void 0 ? this.ctx.get("sessionProjectionCache")?.cachedSnapshot(header) : this.ctx.sessionProjections.cachedSnapshot(session);
1913
+ const block = session === void 0 ? header.isSeeded ? void 0 : this.ctx.get("sessionProjectionCache")?.cachedSnapshot(header, SessionLogOffset(0)) : this.ctx.sessionProjections.cachedSnapshot(session);
1895
1914
  return block !== void 0 && Object.keys(block.values).length > 0 ? {
1896
1915
  asOfSeq: block.asOfSeq,
1897
1916
  values: block.values
@@ -2674,7 +2693,8 @@ let SessionController = (() => {
2674
2693
  const attached = this.ctx.sessions.get(sessionId);
2675
2694
  if (attached !== void 0) return Promise.resolve({
2676
2695
  meta: attached.header,
2677
- events: [...attached.events]
2696
+ inheritedEventCount: attached.inheritedEventCount,
2697
+ events: attached.snapshotEvents()
2678
2698
  });
2679
2699
  return inspectApiSession(this.ctx, sessionId, signal);
2680
2700
  }