@floegence/floeterm-terminal-web 0.5.19 → 0.5.21

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.
Files changed (55) hide show
  1. package/README.md +31 -0
  2. package/dist/core/PagedTerminalOutputCoordinator.d.ts +26 -3
  3. package/dist/core/PagedTerminalOutputCoordinator.d.ts.map +1 -1
  4. package/dist/core/PagedTerminalOutputCoordinator.js +324 -10
  5. package/dist/core/PagedTerminalOutputCoordinator.js.map +1 -1
  6. package/dist/core/TerminalCore.d.ts +6 -2
  7. package/dist/core/TerminalCore.d.ts.map +1 -1
  8. package/dist/core/TerminalCore.js +176 -44
  9. package/dist/core/TerminalCore.js.map +1 -1
  10. package/dist/core/TerminalInputBridge.d.ts +1 -1
  11. package/dist/core/TerminalInputBridge.d.ts.map +1 -1
  12. package/dist/core/TerminalInputBridge.js +1 -1
  13. package/dist/core/TerminalInputBridge.js.map +1 -1
  14. package/dist/core/TerminalOutputPipeline.js +1 -1
  15. package/dist/core/TerminalOutputPipeline.js.map +1 -1
  16. package/dist/fabric/BeamtermFabricRenderer.d.ts +1 -1
  17. package/dist/fabric/BeamtermFabricRenderer.d.ts.map +1 -1
  18. package/dist/fabric/BeamtermFabricRenderer.js +4 -1
  19. package/dist/fabric/BeamtermFabricRenderer.js.map +1 -1
  20. package/dist/fabric/TerminalFabricCoordinator.d.ts +1 -1
  21. package/dist/fabric/TerminalFabricCoordinator.d.ts.map +1 -1
  22. package/dist/fabric/TerminalLiveFabric.d.ts +2 -2
  23. package/dist/fabric/TerminalLiveFabric.d.ts.map +1 -1
  24. package/dist/fabric/TerminalLiveFabric.js +2 -2
  25. package/dist/fabric/TerminalLiveFabric.js.map +1 -1
  26. package/dist/fabric/types.d.ts +1 -1
  27. package/dist/fabric/types.d.ts.map +1 -1
  28. package/dist/index.d.ts +18 -16
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.js +11 -10
  31. package/dist/index.js.map +1 -1
  32. package/dist/internal/SequenceBuffer.d.ts +1 -1
  33. package/dist/internal/SequenceBuffer.d.ts.map +1 -1
  34. package/dist/internal/TerminalInitializationScheduler.d.ts +25 -9
  35. package/dist/internal/TerminalInitializationScheduler.d.ts.map +1 -1
  36. package/dist/internal/TerminalInitializationScheduler.js +62 -29
  37. package/dist/internal/TerminalInitializationScheduler.js.map +1 -1
  38. package/dist/manager/TerminalInstanceController.d.ts +1 -3
  39. package/dist/manager/TerminalInstanceController.d.ts.map +1 -1
  40. package/dist/manager/TerminalInstanceController.js +45 -61
  41. package/dist/manager/TerminalInstanceController.js.map +1 -1
  42. package/dist/sessions/TerminalSessionsCoordinator.d.ts +7 -1
  43. package/dist/sessions/TerminalSessionsCoordinator.d.ts.map +1 -1
  44. package/dist/sessions/TerminalSessionsCoordinator.js +67 -17
  45. package/dist/sessions/TerminalSessionsCoordinator.js.map +1 -1
  46. package/dist/types.d.ts +10 -1
  47. package/dist/types.d.ts.map +1 -1
  48. package/dist/types.js.map +1 -1
  49. package/dist/utils/config.d.ts +1 -1
  50. package/dist/utils/config.d.ts.map +1 -1
  51. package/dist/utils/errors.d.ts +1 -1
  52. package/dist/utils/errors.d.ts.map +1 -1
  53. package/dist/utils/logger.d.ts +1 -1
  54. package/dist/utils/logger.d.ts.map +1 -1
  55. package/package.json +3 -2
package/README.md CHANGED
@@ -51,6 +51,17 @@ const core = new TerminalCore(
51
51
  await core.initialize();
52
52
  ```
53
53
 
54
+ Hosts can preload the dynamic Ghostty module, WASM, and renderer resources before a terminal surface is visible without creating a `TerminalCore`:
55
+
56
+ ```ts
57
+ import { preloadTerminalResources } from '@floegence/floeterm-terminal-web';
58
+
59
+ await preloadTerminalResources({ signal });
60
+ await core.initialize({ priority: 'interactive', signal });
61
+ ```
62
+
63
+ Resource loading is single-flight and retryable after a real import or WASM initialization failure. Caller cancellation only stops that caller from waiting; it does not interrupt shared resource initialization. Core initialization is globally bounded, gives queued interactive work priority over background work, and shares the actual ready promise across duplicate calls.
64
+
54
65
  ## Notes
55
66
 
56
67
  - You must provide a `TerminalTransport` and `TerminalEventSource` for the managed controller.
@@ -96,6 +107,26 @@ If a page reports `historyReset` or changes `historyGeneration` while catch-up i
96
107
 
97
108
  Use `TerminalCore.writeHistory` for history batches. Its auto-response suppression is scoped to that parser write and ends at its completion callback, so later live output and user input use normal terminal behavior.
98
109
 
110
+ Running sessions can prepare bounded history before a visible terminal exists. Preparation does not create a Core, attach a session, resize a PTY, or subscribe to live output:
111
+
112
+ ```ts
113
+ const preparedHistory = await preparePagedTerminalHistory({
114
+ fetchPage: request => transport.historyPage(sessionId, request),
115
+ maxBytes: 4 * 1024 * 1024,
116
+ signal,
117
+ });
118
+
119
+ const attachGeneration = output.beginAttach(0);
120
+ const attach = await transport.attachWithHistoryBoundary(sessionId, cols, rows);
121
+ await output.completeAttach(attachGeneration, attach.historyBoundarySequence, {
122
+ preparedHistory,
123
+ });
124
+ ```
125
+
126
+ Prepared history is renderer-neutral and pins `historyGeneration`, `firstRetainedSequence`, and `snapshotEndSequence`. The visible attach validates those boundaries, replays the seed once, and fetches only the missing delta. A generation reset, retention advance, stale fence, or malformed seed discards it and falls back to the normal full recovery path.
127
+
128
+ `preparePagedTerminalHistory` requires those three metadata fields on every prepared page and passes the remaining retained-byte budget as `request.maxBytes` so transports can bound each page as well. The helper always enforces its retained seed budget after a page returns; adapters should honor the request hint to keep peak response memory bounded too.
129
+
99
130
  ## Restorable In-Memory Snapshots
100
131
 
101
132
  Adaptive host worksets can release inactive renderers without closing their PTY sessions:
@@ -1,10 +1,11 @@
1
- import type { TerminalOutputPipelineChunk, TerminalOutputPipelineScheduler } from './TerminalOutputPipeline';
1
+ import type { TerminalOutputPipelineChunk, TerminalOutputPipelineScheduler } from './TerminalOutputPipeline.js';
2
2
  export type PagedTerminalOutputState = 'idle' | 'initial-replay' | 'live' | 'catching-up' | 'retry-wait' | 'failed' | 'disposed';
3
3
  export interface PagedTerminalHistoryRequest {
4
4
  startSequence: number;
5
5
  endSequence?: number;
6
6
  historyGeneration?: number;
7
7
  cursor?: string | number;
8
+ maxBytes?: number;
8
9
  signal: AbortSignal;
9
10
  }
10
11
  export interface PagedTerminalHistoryPage {
@@ -21,6 +22,27 @@ export interface PagedTerminalHistoryPage {
21
22
  coveredBytes?: number;
22
23
  totalBytes?: number;
23
24
  }
25
+ export interface PreparePagedTerminalHistoryOptions {
26
+ fetchPage(request: PagedTerminalHistoryRequest): Promise<PagedTerminalHistoryPage>;
27
+ startSequence?: number;
28
+ maxBytes?: number;
29
+ signal?: AbortSignal;
30
+ yieldControl?: () => void | Promise<void>;
31
+ }
32
+ export interface PreparedPagedTerminalHistory {
33
+ readonly chunks: readonly Readonly<TerminalOutputPipelineChunk>[];
34
+ readonly requestedStartSequence: number;
35
+ readonly firstRetainedSequence: number;
36
+ readonly coveredThroughSequence: number;
37
+ readonly snapshotEndSequence: number;
38
+ readonly historyGeneration: number;
39
+ readonly byteLength: number;
40
+ readonly pageCount: number;
41
+ readonly complete: boolean;
42
+ }
43
+ export interface PagedTerminalCompleteAttachOptions {
44
+ preparedHistory?: PreparedPagedTerminalHistory;
45
+ }
24
46
  export type PagedTerminalHistoryTruncationReason = 'history-evicted' | 'retained-live-overflow';
25
47
  export type PagedTerminalOutputFailureCode = 'history_fetch_failed' | 'history_coverage_incomplete' | 'history_contract_missing' | 'history_contract_invalid' | 'retained_live_overflow' | 'history_evicted';
26
48
  export interface PagedTerminalOutputFailure {
@@ -84,9 +106,10 @@ export interface PagedTerminalOutputCoordinatorHandle {
84
106
  }
85
107
  export interface AtomicPagedTerminalOutputCoordinatorHandle extends PagedTerminalOutputCoordinatorHandle {
86
108
  beginAttach(startSequence?: number): number;
87
- completeAttach(attachGeneration: number, snapshotEndSequence?: number): Promise<void>;
88
- attach(startSequence?: number, snapshotEndSequence?: number): Promise<void>;
109
+ completeAttach(attachGeneration: number, snapshotEndSequence?: number, options?: PagedTerminalCompleteAttachOptions): Promise<void>;
110
+ attach(startSequence?: number, snapshotEndSequence?: number, options?: PagedTerminalCompleteAttachOptions): Promise<void>;
89
111
  }
112
+ export declare const preparePagedTerminalHistory: (options: PreparePagedTerminalHistoryOptions) => Promise<PreparedPagedTerminalHistory>;
90
113
  export declare const createPagedTerminalOutputCoordinator: (options: PagedTerminalOutputCoordinatorOptions) => AtomicPagedTerminalOutputCoordinatorHandle;
91
114
  export {};
92
115
  //# sourceMappingURL=PagedTerminalOutputCoordinator.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"PagedTerminalOutputCoordinator.d.ts","sourceRoot":"","sources":["../../src/core/PagedTerminalOutputCoordinator.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"PagedTerminalOutputCoordinator.d.ts","sourceRoot":"","sources":["../../src/core/PagedTerminalOutputCoordinator.ts"],"names":[],"mappings":"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"}
@@ -1,9 +1,11 @@
1
+ import { scheduleUiTurn } from '../internal/scheduleUiTurn.js';
1
2
  const DEFAULT_POLICY = {
2
3
  maxRetainedLiveChunks: 2048,
3
4
  maxRetainedLiveBytes: 8 * 1024 * 1024,
4
5
  retryDelaysMs: [250, 1000, 4000],
5
6
  maxWriteBatchBytes: 256 * 1024,
6
7
  };
8
+ const DEFAULT_PREPARED_HISTORY_MAX_BYTES = 32 * 1024 * 1024;
7
9
  const normalizePositiveInteger = (value, fallback) => (typeof value === 'number' && Number.isSafeInteger(value) && value > 0
8
10
  ? value
9
11
  : fallback);
@@ -33,6 +35,213 @@ class HistoryContractError extends Error {
33
35
  this.firstRetainedSequence = firstRetainedSequence;
34
36
  }
35
37
  }
38
+ const createAbortError = () => {
39
+ if (typeof DOMException !== 'undefined')
40
+ return new DOMException('Operation aborted', 'AbortError');
41
+ const error = new Error('Operation aborted');
42
+ error.name = 'AbortError';
43
+ return error;
44
+ };
45
+ const throwIfAborted = (signal) => {
46
+ if (signal?.aborted)
47
+ throw createAbortError();
48
+ };
49
+ const waitForPreparedPage = (promise, signal) => {
50
+ if (!signal)
51
+ return promise;
52
+ if (signal.aborted)
53
+ return Promise.reject(createAbortError());
54
+ return new Promise((resolve, reject) => {
55
+ const onAbort = () => reject(createAbortError());
56
+ signal.addEventListener('abort', onAbort, { once: true });
57
+ promise.then(value => {
58
+ signal.removeEventListener('abort', onAbort);
59
+ resolve(value);
60
+ }, error => {
61
+ signal.removeEventListener('abort', onAbort);
62
+ reject(error);
63
+ });
64
+ });
65
+ };
66
+ const yieldHistoryPreparation = async (signal, yieldControl) => {
67
+ throwIfAborted(signal);
68
+ if (yieldControl) {
69
+ await yieldControl();
70
+ throwIfAborted(signal);
71
+ return;
72
+ }
73
+ await new Promise((resolve, reject) => {
74
+ const cancelTurn = scheduleUiTurn(() => {
75
+ signal?.removeEventListener('abort', onAbort);
76
+ resolve();
77
+ });
78
+ const onAbort = () => {
79
+ cancelTurn();
80
+ reject(createAbortError());
81
+ };
82
+ signal?.addEventListener('abort', onAbort, { once: true });
83
+ });
84
+ };
85
+ const clonePreparedChunk = (chunk) => {
86
+ const immutableData = new Uint8Array(chunk.data);
87
+ return Object.freeze({
88
+ get data() {
89
+ return new Uint8Array(immutableData);
90
+ },
91
+ ...(chunk.sequence !== undefined ? { sequence: chunk.sequence } : {}),
92
+ ...(chunk.timestampMs !== undefined ? { timestampMs: chunk.timestampMs } : {}),
93
+ });
94
+ };
95
+ export const preparePagedTerminalHistory = async (options) => {
96
+ const requestedStartSequence = normalizeSequence(options.startSequence ?? 1, 'startSequence');
97
+ const maxBytes = normalizePositiveInteger(options.maxBytes, DEFAULT_PREPARED_HISTORY_MAX_BYTES);
98
+ let startSequence = Math.max(1, requestedStartSequence);
99
+ let cursor;
100
+ let historyGeneration;
101
+ let snapshotEndSequence;
102
+ let firstRetainedSequence;
103
+ let coveredThroughSequence = Math.max(0, startSequence - 1);
104
+ let byteLength = 0;
105
+ let pageCount = 0;
106
+ let chunks = [];
107
+ let rebaseAttempts = 0;
108
+ const requestSignal = options.signal ?? new AbortController().signal;
109
+ while (true) {
110
+ throwIfAborted(options.signal);
111
+ const page = await waitForPreparedPage(options.fetchPage({
112
+ startSequence,
113
+ endSequence: snapshotEndSequence,
114
+ historyGeneration,
115
+ cursor,
116
+ maxBytes: Math.max(1, maxBytes - byteLength),
117
+ signal: requestSignal,
118
+ }), options.signal);
119
+ throwIfAborted(options.signal);
120
+ pageCount += 1;
121
+ if (!Object.prototype.hasOwnProperty.call(page, 'coveredThroughSequence')) {
122
+ throw new HistoryContractError('history_contract_missing', 'coveredThroughSequence is required');
123
+ }
124
+ if (!Object.prototype.hasOwnProperty.call(page, 'snapshotEndSequence')) {
125
+ throw new HistoryContractError('history_contract_missing', 'snapshotEndSequence is required');
126
+ }
127
+ if (!Object.prototype.hasOwnProperty.call(page, 'historyGeneration')) {
128
+ throw new HistoryContractError('history_contract_missing', 'historyGeneration is required');
129
+ }
130
+ if (!Object.prototype.hasOwnProperty.call(page, 'firstRetainedSequence')
131
+ && !Object.prototype.hasOwnProperty.call(page, 'firstAvailableSequence')) {
132
+ throw new HistoryContractError('history_contract_missing', 'firstRetainedSequence is required');
133
+ }
134
+ const pageCoverage = normalizeSequence(page.coveredThroughSequence, 'coveredThroughSequence');
135
+ const pageSnapshotEnd = normalizeSequence(page.snapshotEndSequence, 'snapshotEndSequence');
136
+ const pageGeneration = normalizeSequence(page.historyGeneration, 'historyGeneration');
137
+ const pageFirstRetained = normalizeSequence(page.firstRetainedSequence ?? page.firstAvailableSequence, 'firstRetainedSequence');
138
+ const generationChanged = historyGeneration !== undefined && pageGeneration !== historyGeneration;
139
+ const snapshotChanged = snapshotEndSequence !== undefined && pageSnapshotEnd !== snapshotEndSequence;
140
+ const retentionAdvanced = firstRetainedSequence !== undefined
141
+ && pageFirstRetained > firstRetainedSequence;
142
+ if (historyGeneration !== undefined
143
+ && (page.historyReset || page.historyTruncated || generationChanged || snapshotChanged || retentionAdvanced)) {
144
+ if (rebaseAttempts >= 2) {
145
+ throw new HistoryContractError('history_contract_invalid', 'terminal history changed repeatedly while preparing a stable snapshot');
146
+ }
147
+ rebaseAttempts += 1;
148
+ startSequence = Math.max(1, pageFirstRetained);
149
+ cursor = undefined;
150
+ historyGeneration = undefined;
151
+ snapshotEndSequence = undefined;
152
+ firstRetainedSequence = undefined;
153
+ coveredThroughSequence = startSequence - 1;
154
+ byteLength = 0;
155
+ chunks = [];
156
+ await yieldHistoryPreparation(options.signal, options.yieldControl);
157
+ continue;
158
+ }
159
+ if (pageCoverage < coveredThroughSequence) {
160
+ throw new HistoryContractError('history_contract_invalid', 'coveredThroughSequence regressed');
161
+ }
162
+ if (pageCoverage > pageSnapshotEnd) {
163
+ throw new HistoryContractError('history_contract_invalid', 'coveredThroughSequence exceeded snapshotEndSequence');
164
+ }
165
+ let previousChunkSequence = coveredThroughSequence;
166
+ for (const chunk of page.chunks) {
167
+ const sequence = normalizeSequence(chunk.sequence, 'chunk.sequence');
168
+ if (sequence <= previousChunkSequence || sequence > pageCoverage) {
169
+ throw new HistoryContractError('history_contract_invalid', 'prepared history chunks must have strictly increasing sequences within page coverage');
170
+ }
171
+ previousChunkSequence = sequence;
172
+ }
173
+ if (pageCoverage > Math.max(0, pageFirstRetained - 1)
174
+ && previousChunkSequence !== pageCoverage) {
175
+ throw new HistoryContractError('history_contract_invalid', 'prepared history page did not include its covered terminal sequence');
176
+ }
177
+ historyGeneration = pageGeneration;
178
+ snapshotEndSequence = pageSnapshotEnd;
179
+ firstRetainedSequence = pageFirstRetained;
180
+ if (pageFirstRetained > startSequence) {
181
+ coveredThroughSequence = Math.max(coveredThroughSequence, pageFirstRetained - 1);
182
+ }
183
+ const pageBytes = page.chunks.reduce((sum, chunk) => sum + chunk.data.byteLength, 0);
184
+ const remainingBytes = maxBytes - byteLength;
185
+ if (pageBytes <= remainingBytes) {
186
+ chunks.push(...page.chunks.map(clonePreparedChunk));
187
+ byteLength += pageBytes;
188
+ coveredThroughSequence = Math.max(coveredThroughSequence, pageCoverage);
189
+ }
190
+ else {
191
+ for (const chunk of page.chunks) {
192
+ const sequence = normalizeSequence(chunk.sequence, 'chunk.sequence');
193
+ if (byteLength + chunk.data.byteLength > maxBytes)
194
+ break;
195
+ chunks.push(clonePreparedChunk(chunk));
196
+ byteLength += chunk.data.byteLength;
197
+ coveredThroughSequence = Math.max(coveredThroughSequence, sequence);
198
+ }
199
+ return Object.freeze({
200
+ chunks: Object.freeze(chunks),
201
+ requestedStartSequence,
202
+ firstRetainedSequence,
203
+ coveredThroughSequence,
204
+ snapshotEndSequence,
205
+ historyGeneration,
206
+ byteLength,
207
+ pageCount,
208
+ complete: false,
209
+ });
210
+ }
211
+ if (!page.hasMore) {
212
+ return Object.freeze({
213
+ chunks: Object.freeze(chunks),
214
+ requestedStartSequence,
215
+ firstRetainedSequence,
216
+ coveredThroughSequence,
217
+ snapshotEndSequence,
218
+ historyGeneration,
219
+ byteLength,
220
+ pageCount,
221
+ complete: coveredThroughSequence >= snapshotEndSequence,
222
+ });
223
+ }
224
+ if (byteLength >= maxBytes) {
225
+ return Object.freeze({
226
+ chunks: Object.freeze(chunks),
227
+ requestedStartSequence,
228
+ firstRetainedSequence,
229
+ coveredThroughSequence,
230
+ snapshotEndSequence,
231
+ historyGeneration,
232
+ byteLength,
233
+ pageCount,
234
+ complete: false,
235
+ });
236
+ }
237
+ if (page.nextCursor === undefined) {
238
+ throw new HistoryContractError('history_contract_invalid', 'nextCursor is required when hasMore is true');
239
+ }
240
+ cursor = page.nextCursor;
241
+ startSequence = pageCoverage + 1;
242
+ await yieldHistoryPreparation(options.signal, options.yieldControl);
243
+ }
244
+ };
36
245
  class PagedTerminalOutputCoordinator {
37
246
  constructor(options) {
38
247
  this.active = true;
@@ -64,6 +273,7 @@ class PagedTerminalOutputCoordinator {
64
273
  this.writerQuiescenceWaiters = [];
65
274
  this.pausedRecoveryPending = false;
66
275
  this.droppedLiveThroughSequence = 0;
276
+ this.preparedHistory = null;
67
277
  this.options = options;
68
278
  this.policy = {
69
279
  maxRetainedLiveChunks: normalizePositiveInteger(options.policy?.maxRetainedLiveChunks, DEFAULT_POLICY.maxRetainedLiveChunks),
@@ -74,9 +284,9 @@ class PagedTerminalOutputCoordinator {
74
284
  maxWriteBatchBytes: normalizePositiveInteger(options.policy?.maxWriteBatchBytes, DEFAULT_POLICY.maxWriteBatchBytes),
75
285
  };
76
286
  }
77
- async attach(startSequence = 1, snapshotEndSequence) {
287
+ async attach(startSequence = 1, snapshotEndSequence, options) {
78
288
  const attachGeneration = this.beginAttach(startSequence);
79
- await this.completeAttach(attachGeneration, snapshotEndSequence);
289
+ await this.completeAttach(attachGeneration, snapshotEndSequence, options);
80
290
  }
81
291
  beginAttach(startSequence = 1) {
82
292
  if (this.disposed)
@@ -102,14 +312,16 @@ class PagedTerminalOutputCoordinator {
102
312
  this.failure = null;
103
313
  this.lastError = null;
104
314
  this.historyRebasePrepared = false;
315
+ this.preparedHistory = null;
105
316
  this.setState('idle');
106
317
  return this.generation;
107
318
  }
108
- async completeAttach(attachGeneration, snapshotEndSequence) {
319
+ async completeAttach(attachGeneration, snapshotEndSequence, options = {}) {
109
320
  if (this.disposed || !this.isCurrent(attachGeneration))
110
321
  return;
111
322
  this.explicitAttachFence = snapshotEndSequence !== undefined;
112
323
  this.recoveryEndSequence = normalizeSequence(snapshotEndSequence, 'snapshotEndSequence', true);
324
+ this.preparedHistory = this.acceptPreparedHistory(options.preparedHistory);
113
325
  if (this.explicitAttachFence && this.recoveryEndSequence === 0) {
114
326
  if (this.needsRebase) {
115
327
  this.prepareRetainedLiveRebase();
@@ -201,6 +413,7 @@ class PagedTerminalOutputCoordinator {
201
413
  this.failure = null;
202
414
  this.lastError = null;
203
415
  this.historyRebasePrepared = false;
416
+ this.preparedHistory = null;
204
417
  this.setState('idle');
205
418
  this.resolveBaselineWaiters();
206
419
  }
@@ -237,6 +450,7 @@ class PagedTerminalOutputCoordinator {
237
450
  this.pendingLiveWrites = [];
238
451
  this.liveWriteScheduled = false;
239
452
  this.pausedRecoveryPending = false;
453
+ this.preparedHistory = null;
240
454
  this.setState('disposed');
241
455
  this.resolveBaselineWaiters();
242
456
  }
@@ -252,6 +466,62 @@ class PagedTerminalOutputCoordinator {
252
466
  this.retryAttempt = 0;
253
467
  void this.runRecovery();
254
468
  }
469
+ acceptPreparedHistory(preparedHistory) {
470
+ if (!preparedHistory || this.recoveryEndSequence === undefined)
471
+ return null;
472
+ try {
473
+ const requestedStart = normalizeSequence(preparedHistory.requestedStartSequence, 'preparedHistory.requestedStartSequence');
474
+ const firstRetained = normalizeSequence(preparedHistory.firstRetainedSequence, 'preparedHistory.firstRetainedSequence');
475
+ const coveredThrough = normalizeSequence(preparedHistory.coveredThroughSequence, 'preparedHistory.coveredThroughSequence');
476
+ const snapshotEnd = normalizeSequence(preparedHistory.snapshotEndSequence, 'preparedHistory.snapshotEndSequence');
477
+ normalizeSequence(preparedHistory.historyGeneration, 'preparedHistory.historyGeneration');
478
+ normalizeSequence(preparedHistory.byteLength, 'preparedHistory.byteLength');
479
+ normalizeSequence(preparedHistory.pageCount, 'preparedHistory.pageCount');
480
+ const expectedStart = Math.max(1, this.recoveryStartSequence);
481
+ if (requestedStart > expectedStart)
482
+ return null;
483
+ if (firstRetained > coveredThrough + 1)
484
+ return null;
485
+ if (coveredThrough < expectedStart - 1 || coveredThrough > snapshotEnd)
486
+ return null;
487
+ if (snapshotEnd > this.recoveryEndSequence || coveredThrough > this.recoveryEndSequence)
488
+ return null;
489
+ if (!Array.isArray(preparedHistory.chunks))
490
+ return null;
491
+ let previousChunkSequence = Math.max(0, firstRetained - 1);
492
+ let preparedBytes = 0;
493
+ for (const chunk of preparedHistory.chunks) {
494
+ if (!(chunk.data instanceof Uint8Array))
495
+ return null;
496
+ const sequence = normalizeSequence(chunk.sequence, 'preparedHistory.chunk.sequence');
497
+ if (sequence <= previousChunkSequence || sequence > coveredThrough)
498
+ return null;
499
+ previousChunkSequence = sequence;
500
+ preparedBytes += chunk.data.byteLength;
501
+ }
502
+ if (preparedBytes !== preparedHistory.byteLength)
503
+ return null;
504
+ if (coveredThrough >= Math.max(1, firstRetained)
505
+ && previousChunkSequence !== coveredThrough)
506
+ return null;
507
+ if (preparedHistory.complete !== (coveredThrough >= snapshotEnd))
508
+ return null;
509
+ if (firstRetained > expectedStart) {
510
+ this.options.clear?.();
511
+ this.options.onHistoryTruncated?.('history-evicted');
512
+ }
513
+ return preparedHistory;
514
+ }
515
+ catch {
516
+ return null;
517
+ }
518
+ }
519
+ discardPreparedHistoryForFullRecovery() {
520
+ this.preparedHistory = null;
521
+ this.coveredThroughSequence = Math.max(0, this.recoveryStartSequence - 1);
522
+ this.scheduledThroughSequence = this.coveredThroughSequence;
523
+ this.historyRebasePrepared = false;
524
+ }
255
525
  async runRecovery() {
256
526
  if (this.disposed || this.recoveryRunning)
257
527
  return;
@@ -268,13 +538,26 @@ class PagedTerminalOutputCoordinator {
268
538
  await this.writeChain;
269
539
  if (!this.isRecoveryCurrent(generation, recoverySerial, controller))
270
540
  return;
271
- const historyChunks = [];
541
+ const preparedHistory = this.preparedHistory;
542
+ const historyChunks = preparedHistory
543
+ ? preparedHistory.chunks.map(item => ({ ...item, data: new Uint8Array(item.data), source: 'history' }))
544
+ : [];
272
545
  let cursor;
273
- let startSequence = this.recoveryStartSequence;
546
+ let startSequence = preparedHistory
547
+ ? Math.max(1, preparedHistory.coveredThroughSequence
548
+ + (preparedHistory.coveredThroughSequence < (this.recoveryEndSequence ?? 0) ? 1 : 0))
549
+ : this.recoveryStartSequence;
274
550
  let snapshotEnd = this.recoveryEndSequence;
275
- let historyGeneration;
276
- let coveredEnd = this.coveredThroughSequence;
277
- let firstPage = true;
551
+ let historyGeneration = preparedHistory?.historyGeneration;
552
+ let coveredEnd = preparedHistory?.coveredThroughSequence ?? this.coveredThroughSequence;
553
+ let firstPage = preparedHistory === null;
554
+ const preparedBaseline = preparedHistory === null
555
+ ? undefined
556
+ : Math.max(Math.max(1, this.recoveryStartSequence) - 1, preparedHistory.firstRetainedSequence - 1);
557
+ if (preparedBaseline !== undefined) {
558
+ this.coveredThroughSequence = preparedBaseline;
559
+ this.scheduledThroughSequence = preparedBaseline;
560
+ }
278
561
  do {
279
562
  const page = await this.options.fetchPage({
280
563
  startSequence,
@@ -288,21 +571,32 @@ class PagedTerminalOutputCoordinator {
288
571
  const pageSnapshotEnd = normalizeSequence(page.snapshotEndSequence, 'snapshotEndSequence', true);
289
572
  const pageGeneration = normalizeSequence(page.historyGeneration, 'historyGeneration', true);
290
573
  const firstRetained = normalizeSequence(page.firstRetainedSequence ?? page.firstAvailableSequence, 'firstRetainedSequence', true);
574
+ if (preparedHistory !== null
575
+ && (pageSnapshotEnd === undefined || pageGeneration === undefined || firstRetained === undefined)) {
576
+ this.discardPreparedHistoryForFullRecovery();
577
+ this.recoveryRunning = false;
578
+ await this.runRecovery();
579
+ return;
580
+ }
291
581
  const generationChanged = historyGeneration !== undefined
292
582
  && pageGeneration !== undefined
293
583
  && pageGeneration !== historyGeneration;
294
584
  const effectivePageStart = Math.max(1, startSequence);
295
585
  const retentionAdvanced = firstRetained !== undefined
296
- && firstRetained > effectivePageStart;
586
+ && (firstRetained > effectivePageStart
587
+ || (preparedHistory !== null && firstRetained > preparedHistory.firstRetainedSequence));
297
588
  const generationNeedsRebase = page.historyReset || generationChanged;
298
589
  const retentionNeedsRebase = page.historyTruncated || retentionAdvanced;
299
590
  const fencedRangeFullyEvicted = this.recoveryEndSequence !== undefined
300
591
  && firstRetained !== undefined
301
592
  && firstRetained > this.recoveryEndSequence;
302
593
  if (!this.historyRebasePrepared && (generationNeedsRebase || retentionNeedsRebase)) {
594
+ this.preparedHistory = null;
303
595
  if (fencedRangeFullyEvicted) {
304
596
  this.options.clear?.();
305
597
  this.options.onHistoryTruncated?.('history-evicted');
598
+ if (preparedHistory !== null)
599
+ historyChunks.length = 0;
306
600
  if (generationNeedsRebase) {
307
601
  this.historyRebasePrepared = true;
308
602
  this.recoveryRunning = false;
@@ -320,7 +614,19 @@ class PagedTerminalOutputCoordinator {
320
614
  if (this.historyRebasePrepared && generationNeedsRebase) {
321
615
  throw new HistoryContractError('history_contract_invalid', 'history generation reset persisted after rebase');
322
616
  }
323
- const coverage = this.validatePage(page, coveredEnd);
617
+ let coverage;
618
+ try {
619
+ coverage = this.validatePage(page, coveredEnd);
620
+ }
621
+ catch (error) {
622
+ if (preparedHistory === null)
623
+ throw error;
624
+ this.preparedHistory = null;
625
+ this.prepareHistoryGenerationRebase(firstRetained);
626
+ this.recoveryRunning = false;
627
+ await this.runRecovery();
628
+ return;
629
+ }
324
630
  if (snapshotEnd !== undefined && coverage > snapshotEnd) {
325
631
  throw new HistoryContractError('history_contract_invalid', 'coveredThroughSequence exceeded the recovery fence');
326
632
  }
@@ -348,6 +654,13 @@ class PagedTerminalOutputCoordinator {
348
654
  }
349
655
  else {
350
656
  if (snapshotEnd !== undefined && pageSnapshotEnd !== undefined && pageSnapshotEnd !== snapshotEnd) {
657
+ if (preparedHistory !== null) {
658
+ this.preparedHistory = null;
659
+ this.prepareHistoryGenerationRebase(firstRetained);
660
+ this.recoveryRunning = false;
661
+ await this.runRecovery();
662
+ return;
663
+ }
351
664
  throw new HistoryContractError('history_contract_invalid', 'snapshotEndSequence changed during pagination');
352
665
  }
353
666
  }
@@ -399,6 +712,7 @@ class PagedTerminalOutputCoordinator {
399
712
  this.retryAttempt = 0;
400
713
  this.lastError = null;
401
714
  this.failure = null;
715
+ this.preparedHistory = null;
402
716
  this.setState('live');
403
717
  this.recoveryRunning = false;
404
718
  this.drainRetainedLive();