@floegence/floeterm-terminal-web 0.14.0 → 0.15.0

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 (156) hide show
  1. package/README.md +78 -445
  2. package/dist/core/TerminalInputBridge.d.ts +7 -3
  3. package/dist/core/TerminalInputBridge.d.ts.map +1 -1
  4. package/dist/core/TerminalInputBridge.js +63 -4
  5. package/dist/core/TerminalInputBridge.js.map +1 -1
  6. package/dist/entries/live.d.ts +4 -4
  7. package/dist/entries/live.d.ts.map +1 -1
  8. package/dist/entries/live.js +2 -2
  9. package/dist/entries/live.js.map +1 -1
  10. package/dist/entries/semantic.d.ts +9 -0
  11. package/dist/entries/semantic.d.ts.map +1 -0
  12. package/dist/entries/semantic.js +5 -0
  13. package/dist/entries/semantic.js.map +1 -0
  14. package/dist/entries/sessions.d.ts +1 -1
  15. package/dist/entries/sessions.d.ts.map +1 -1
  16. package/dist/index.d.ts +4 -26
  17. package/dist/index.d.ts.map +1 -1
  18. package/dist/index.js +3 -16
  19. package/dist/index.js.map +1 -1
  20. package/dist/live/client.d.ts +4 -6
  21. package/dist/live/client.d.ts.map +1 -1
  22. package/dist/live/client.js +19 -72
  23. package/dist/live/client.js.map +1 -1
  24. package/dist/live/codec.d.ts +8 -21
  25. package/dist/live/codec.d.ts.map +1 -1
  26. package/dist/live/codec.js +79 -111
  27. package/dist/live/codec.js.map +1 -1
  28. package/dist/live/transport.d.ts +38 -18
  29. package/dist/live/transport.d.ts.map +1 -1
  30. package/dist/live/transport.js +59 -133
  31. package/dist/live/transport.js.map +1 -1
  32. package/dist/semantic/RendererSurface.d.ts +65 -0
  33. package/dist/semantic/RendererSurface.d.ts.map +1 -0
  34. package/dist/semantic/RendererSurface.js +566 -0
  35. package/dist/semantic/RendererSurface.js.map +1 -0
  36. package/dist/semantic/presentation.d.ts +97 -0
  37. package/dist/semantic/presentation.d.ts.map +1 -0
  38. package/dist/semantic/presentation.js +102 -0
  39. package/dist/semantic/presentation.js.map +1 -0
  40. package/dist/types.d.ts +13 -494
  41. package/dist/types.d.ts.map +1 -1
  42. package/dist/types.js +1 -37
  43. package/dist/types.js.map +1 -1
  44. package/package.json +11 -19
  45. package/dist/checkpoint/GhosttyCheckpoint.worker.d.ts +0 -2
  46. package/dist/checkpoint/GhosttyCheckpoint.worker.d.ts.map +0 -1
  47. package/dist/checkpoint/GhosttyCheckpoint.worker.js +0 -9
  48. package/dist/checkpoint/GhosttyCheckpoint.worker.js.map +0 -1
  49. package/dist/checkpoint/GhosttyCheckpointActor.d.ts +0 -44
  50. package/dist/checkpoint/GhosttyCheckpointActor.d.ts.map +0 -1
  51. package/dist/checkpoint/GhosttyCheckpointActor.js +0 -284
  52. package/dist/checkpoint/GhosttyCheckpointActor.js.map +0 -1
  53. package/dist/checkpoint/GhosttyCheckpointEngine.d.ts +0 -62
  54. package/dist/checkpoint/GhosttyCheckpointEngine.d.ts.map +0 -1
  55. package/dist/checkpoint/GhosttyCheckpointEngine.js +0 -175
  56. package/dist/checkpoint/GhosttyCheckpointEngine.js.map +0 -1
  57. package/dist/checkpoint/GhosttyCheckpointWorkerRuntime.d.ts +0 -31
  58. package/dist/checkpoint/GhosttyCheckpointWorkerRuntime.d.ts.map +0 -1
  59. package/dist/checkpoint/GhosttyCheckpointWorkerRuntime.js +0 -112
  60. package/dist/checkpoint/GhosttyCheckpointWorkerRuntime.js.map +0 -1
  61. package/dist/core/PagedTerminalOutputCoordinator.d.ts +0 -130
  62. package/dist/core/PagedTerminalOutputCoordinator.d.ts.map +0 -1
  63. package/dist/core/PagedTerminalOutputCoordinator.js +0 -1197
  64. package/dist/core/PagedTerminalOutputCoordinator.js.map +0 -1
  65. package/dist/core/TerminalCore.d.ts +0 -286
  66. package/dist/core/TerminalCore.d.ts.map +0 -1
  67. package/dist/core/TerminalCore.js +0 -3566
  68. package/dist/core/TerminalCore.js.map +0 -1
  69. package/dist/core/TerminalOutputPipeline.d.ts +0 -86
  70. package/dist/core/TerminalOutputPipeline.d.ts.map +0 -1
  71. package/dist/core/TerminalOutputPipeline.js +0 -511
  72. package/dist/core/TerminalOutputPipeline.js.map +0 -1
  73. package/dist/core/TerminalRenderScheduler.d.ts +0 -45
  74. package/dist/core/TerminalRenderScheduler.d.ts.map +0 -1
  75. package/dist/core/TerminalRenderScheduler.js +0 -145
  76. package/dist/core/TerminalRenderScheduler.js.map +0 -1
  77. package/dist/core/TerminalScrollbarOverlay.d.ts +0 -63
  78. package/dist/core/TerminalScrollbarOverlay.d.ts.map +0 -1
  79. package/dist/core/TerminalScrollbarOverlay.js +0 -493
  80. package/dist/core/TerminalScrollbarOverlay.js.map +0 -1
  81. package/dist/entries/history.d.ts +0 -4
  82. package/dist/entries/history.d.ts.map +0 -1
  83. package/dist/entries/history.js +0 -2
  84. package/dist/entries/history.js.map +0 -1
  85. package/dist/entries/preload.d.ts +0 -3
  86. package/dist/entries/preload.d.ts.map +0 -1
  87. package/dist/entries/preload.js +0 -2
  88. package/dist/entries/preload.js.map +0 -1
  89. package/dist/fabric/BeamtermFabricRenderer.d.ts +0 -67
  90. package/dist/fabric/BeamtermFabricRenderer.d.ts.map +0 -1
  91. package/dist/fabric/BeamtermFabricRenderer.js +0 -659
  92. package/dist/fabric/BeamtermFabricRenderer.js.map +0 -1
  93. package/dist/fabric/TerminalFabricCoordinator.d.ts +0 -40
  94. package/dist/fabric/TerminalFabricCoordinator.d.ts.map +0 -1
  95. package/dist/fabric/TerminalFabricCoordinator.js +0 -141
  96. package/dist/fabric/TerminalFabricCoordinator.js.map +0 -1
  97. package/dist/fabric/TerminalLiveFabric.d.ts +0 -44
  98. package/dist/fabric/TerminalLiveFabric.d.ts.map +0 -1
  99. package/dist/fabric/TerminalLiveFabric.js +0 -117
  100. package/dist/fabric/TerminalLiveFabric.js.map +0 -1
  101. package/dist/fabric/types.d.ts +0 -152
  102. package/dist/fabric/types.d.ts.map +0 -1
  103. package/dist/fabric/types.js +0 -2
  104. package/dist/fabric/types.js.map +0 -1
  105. package/dist/internal/BeamtermResourceLoader.d.ts +0 -3
  106. package/dist/internal/BeamtermResourceLoader.d.ts.map +0 -1
  107. package/dist/internal/BeamtermResourceLoader.js +0 -14
  108. package/dist/internal/BeamtermResourceLoader.js.map +0 -1
  109. package/dist/internal/GhosttyCompatibilityVersion.d.ts +0 -2
  110. package/dist/internal/GhosttyCompatibilityVersion.d.ts.map +0 -1
  111. package/dist/internal/GhosttyCompatibilityVersion.js +0 -2
  112. package/dist/internal/GhosttyCompatibilityVersion.js.map +0 -1
  113. package/dist/internal/GhosttyScrollbackCompat.d.ts +0 -16
  114. package/dist/internal/GhosttyScrollbackCompat.d.ts.map +0 -1
  115. package/dist/internal/GhosttyScrollbackCompat.js +0 -43
  116. package/dist/internal/GhosttyScrollbackCompat.js.map +0 -1
  117. package/dist/internal/GhosttyScrollbarCompat.d.ts +0 -12
  118. package/dist/internal/GhosttyScrollbarCompat.d.ts.map +0 -1
  119. package/dist/internal/GhosttyScrollbarCompat.js +0 -160
  120. package/dist/internal/GhosttyScrollbarCompat.js.map +0 -1
  121. package/dist/internal/SequenceBuffer.d.ts +0 -19
  122. package/dist/internal/SequenceBuffer.d.ts.map +0 -1
  123. package/dist/internal/SequenceBuffer.js +0 -83
  124. package/dist/internal/SequenceBuffer.js.map +0 -1
  125. package/dist/internal/TerminalInitializationScheduler.d.ts +0 -38
  126. package/dist/internal/TerminalInitializationScheduler.d.ts.map +0 -1
  127. package/dist/internal/TerminalInitializationScheduler.js +0 -112
  128. package/dist/internal/TerminalInitializationScheduler.js.map +0 -1
  129. package/dist/internal/TerminalResourceLoader.d.ts +0 -40
  130. package/dist/internal/TerminalResourceLoader.d.ts.map +0 -1
  131. package/dist/internal/TerminalResourceLoader.js +0 -165
  132. package/dist/internal/TerminalResourceLoader.js.map +0 -1
  133. package/dist/internal/UnicodeSafeUrlProvider.d.ts +0 -17
  134. package/dist/internal/UnicodeSafeUrlProvider.d.ts.map +0 -1
  135. package/dist/internal/UnicodeSafeUrlProvider.js +0 -32
  136. package/dist/internal/UnicodeSafeUrlProvider.js.map +0 -1
  137. package/dist/internal/scheduleUiTurn.d.ts +0 -5
  138. package/dist/internal/scheduleUiTurn.d.ts.map +0 -1
  139. package/dist/internal/scheduleUiTurn.js +0 -37
  140. package/dist/internal/scheduleUiTurn.js.map +0 -1
  141. package/dist/manager/TerminalInstanceController.d.ts +0 -88
  142. package/dist/manager/TerminalInstanceController.d.ts.map +0 -1
  143. package/dist/manager/TerminalInstanceController.js +0 -1076
  144. package/dist/manager/TerminalInstanceController.js.map +0 -1
  145. package/dist/utils/errors.d.ts +0 -3
  146. package/dist/utils/errors.d.ts.map +0 -1
  147. package/dist/utils/errors.js +0 -11
  148. package/dist/utils/errors.js.map +0 -1
  149. package/dist/utils/history.d.ts +0 -2
  150. package/dist/utils/history.d.ts.map +0 -1
  151. package/dist/utils/history.js +0 -12
  152. package/dist/utils/history.js.map +0 -1
  153. package/dist/utils/xtermAutoResponseFilter.d.ts +0 -2
  154. package/dist/utils/xtermAutoResponseFilter.d.ts.map +0 -1
  155. package/dist/utils/xtermAutoResponseFilter.js +0 -109
  156. package/dist/utils/xtermAutoResponseFilter.js.map +0 -1
@@ -1,1076 +0,0 @@
1
- import { TerminalCore } from '../core/TerminalCore.js';
2
- import { SequenceBuffer } from '../internal/SequenceBuffer.js';
3
- import { createTerminalError } from '../utils/errors.js';
4
- import { getDefaultTerminalConfig, getThemeColors } from '../utils/config.js';
5
- import { createConsoleLogger, noopLogger } from '../utils/logger.js';
6
- import { concatChunks } from '../utils/history.js';
7
- import { createGhosttyCheckpointActor } from '../checkpoint/GhosttyCheckpointActor.js';
8
- import { TerminalState, computeConnectionState, computeTerminalState, } from '../types.js';
9
- var ConnectionState;
10
- (function (ConnectionState) {
11
- ConnectionState["IDLE"] = "idle";
12
- ConnectionState["CONNECTING"] = "connecting";
13
- ConnectionState["CONNECTED"] = "connected";
14
- ConnectionState["DISCONNECTED"] = "disconnected";
15
- ConnectionState["RETRYING"] = "retrying";
16
- ConnectionState["FAILED"] = "failed";
17
- ConnectionState["ABORTED"] = "aborted";
18
- })(ConnectionState || (ConnectionState = {}));
19
- const MAX_WRITE_BATCH_CHUNKS = 2048;
20
- const MAX_WRITE_BATCH_BYTES = 512 * 1024;
21
- const MAX_IMMEDIATE_LIVE_BATCH_BYTES = 256;
22
- const DEFAULT_CHECKPOINT_CAPTURE_BYTES = 4 * 1024 * 1024;
23
- const isAtomicTransport = (transport) => (typeof transport.attachWithHistoryBoundary === 'function'
24
- && typeof transport.historyPage === 'function');
25
- const createDefaultScheduler = () => ({
26
- requestFrame: callback => {
27
- if (typeof requestAnimationFrame === 'function') {
28
- return requestAnimationFrame(callback);
29
- }
30
- return setTimeout(() => callback(Date.now()), 0);
31
- },
32
- cancelFrame: id => {
33
- if (typeof cancelAnimationFrame === 'function') {
34
- cancelAnimationFrame(id);
35
- return;
36
- }
37
- clearTimeout(id);
38
- },
39
- setTimer: (callback, delayMs) => setTimeout(callback, delayMs),
40
- clearTimer: id => clearTimeout(id),
41
- });
42
- export class FrameworkNeutralTerminalInstanceController {
43
- constructor(options) {
44
- this.listeners = new Set();
45
- this.container = null;
46
- this.terminalCore = null;
47
- this.isInitializing = false;
48
- this.terminalDataUnsubscribe = null;
49
- this.terminalGeometryUnsubscribe = null;
50
- this.lastGeometryGeneration = 0;
51
- this.pendingGeometryEvents = [];
52
- this.checkpointActor = null;
53
- this.checkpointActorTail = Promise.resolve();
54
- this.checkpointBytesSinceCommit = 0;
55
- this.checkpointActorGeneration = 0;
56
- this.sequenceBuffer = new SequenceBuffer();
57
- this.replayCompleteReceived = false;
58
- this.isReplayActive = false;
59
- this.lastAppliedSequence = 0;
60
- this.loadingState = 'idle';
61
- this.loadingMessage = '';
62
- this.connectionState = ConnectionState.IDLE;
63
- this.connectionError = null;
64
- this.retryCount = 0;
65
- this.connectGeneration = 0;
66
- this.resizeGeneration = 0;
67
- this.retryTimeout = null;
68
- this.initializationAbortController = null;
69
- this.queueRetryTimeout = null;
70
- this.state = {
71
- state: TerminalState.IDLE,
72
- error: undefined,
73
- dimensions: { cols: 80, rows: 24 }
74
- };
75
- this.dimensions = { cols: 80, rows: 24 };
76
- this.dataQueue = [];
77
- this.isProcessing = false;
78
- this.queueGeneration = 0;
79
- this.flushRaf = null;
80
- this.flushMicrotaskScheduled = false;
81
- this.flushMicrotaskGeneration = 0;
82
- this.immediateFlushEligible = false;
83
- this.disposed = false;
84
- this.actions = {
85
- write: data => {
86
- const chunk = {
87
- data: new TextEncoder().encode(data),
88
- sequence: 0,
89
- timestampMs: Date.now()
90
- };
91
- this.addChunkToQueue(chunk);
92
- },
93
- clear: () => {
94
- this.disposeCheckpointActor();
95
- this.terminalCore?.clear();
96
- this.dataQueue = [];
97
- const sessionId = this.options.sessionId;
98
- if (!sessionId) {
99
- return;
100
- }
101
- this.options.transport.clear(sessionId)
102
- .catch(error => this.logger.warn('[TerminalInstanceController] Clear history failed', { error }))
103
- .finally(() => {
104
- this.options.transport.sendInput(sessionId, '\r')
105
- .catch(error => this.logger.warn('[TerminalInstanceController] Clear redraw failed', { error }));
106
- });
107
- },
108
- findNext: (term, options) => this.terminalCore?.findNext(term, options) ?? false,
109
- findPrevious: (term, options) => this.terminalCore?.findPrevious(term, options) ?? false,
110
- clearSearch: () => this.terminalCore?.clearSearch(),
111
- serialize: () => this.terminalCore?.serialize() ?? '',
112
- getSelectionText: () => this.terminalCore?.getSelectionText() ?? '',
113
- hasSelection: () => this.terminalCore?.hasSelection() ?? false,
114
- copySelection: source => this.terminalCore?.copySelection(source) ?? Promise.resolve({
115
- copied: false,
116
- reason: 'empty_selection',
117
- source: source ?? 'command'
118
- }),
119
- setConnected: connected => this.terminalCore?.setConnected(connected),
120
- forceResize: () => this.terminalCore?.forceResize(),
121
- measureHostDimensions: () => this.terminalCore?.measureHostDimensions?.(),
122
- setSearchResultsCallback: callback => this.terminalCore?.setSearchResultsCallback(callback),
123
- focus: options => this.terminalCore?.focus(options),
124
- getTerminalInfo: () => this.terminalCore?.getTerminalInfo() ?? null,
125
- sendInput: data => this.handleUserInput(data),
126
- setAppearance: appearance => this.applyCoreAppearance(appearance),
127
- setTheme: theme => this.applyCoreAppearance({ themeName: theme }),
128
- setFontSize: size => this.applyCoreAppearance({ fontSize: size }),
129
- setPresentationScale: scale => this.applyCoreAppearance({ presentationScale: scale }),
130
- reinitialize: () => this.reinitialize(),
131
- };
132
- this.handleStateChange = (newState) => {
133
- this.updateState({ state: newState });
134
- };
135
- this.handleResize = async (size) => {
136
- const resizeGeneration = this.resizeGeneration;
137
- this.dimensions = size;
138
- this.updateState({ dimensions: size });
139
- this.options.onResize?.(size.cols, size.rows);
140
- const sessionId = this.options.sessionId;
141
- if (!sessionId) {
142
- return;
143
- }
144
- if (isAtomicTransport(this.options.transport) && this.connectionState !== ConnectionState.CONNECTED) {
145
- return;
146
- }
147
- try {
148
- await this.options.transport.resize(sessionId, size.cols, size.rows);
149
- }
150
- catch (error) {
151
- if (this.disposed || resizeGeneration !== this.resizeGeneration) {
152
- return;
153
- }
154
- this.logger.warn('[TerminalInstanceController] Resize request failed', { error });
155
- }
156
- };
157
- this.handleError = (error) => {
158
- this.updateState({ error, state: TerminalState.ERROR });
159
- this.options.onError?.(error);
160
- };
161
- this.options = { ...options };
162
- this.logger = options.logger ?? createConsoleLogger();
163
- this.scheduler = options.scheduler ?? createDefaultScheduler();
164
- this.subscribeToTerminalData();
165
- this.subscribeToTerminalGeometry();
166
- }
167
- get connection() {
168
- return computeConnectionState(this.createConnectionState());
169
- }
170
- async mount(container) {
171
- if (this.disposed) {
172
- throw new Error('Cannot mount a disposed TerminalInstanceController');
173
- }
174
- this.container = container;
175
- this.scheduleInitialize();
176
- }
177
- unmount() {
178
- this.initializationAbortController?.abort();
179
- this.initializationAbortController = null;
180
- this.cleanupTerminal();
181
- this.container = null;
182
- }
183
- dispose() {
184
- if (this.disposed) {
185
- return;
186
- }
187
- this.disposed = true;
188
- this.connectGeneration += 1;
189
- this.initializationAbortController?.abort();
190
- this.initializationAbortController = null;
191
- this.clearRetryTimeout();
192
- this.clearQueueRetryTimeout();
193
- this.cancelDataQueueFlush();
194
- this.terminalDataUnsubscribe?.();
195
- this.terminalDataUnsubscribe = null;
196
- this.terminalGeometryUnsubscribe?.();
197
- this.terminalGeometryUnsubscribe = null;
198
- this.cleanupTerminal();
199
- this.updateState({ state: TerminalState.DISPOSED });
200
- this.listeners.clear();
201
- }
202
- updateOptions(options) {
203
- if (this.disposed) {
204
- return;
205
- }
206
- const previousSessionId = this.options.sessionId;
207
- const previousIsActive = this.options.isActive;
208
- const previousTheme = this.options.themeName;
209
- const previousFontSize = this.options.fontSize;
210
- const previousFontFamily = this.options.config?.fontFamily;
211
- const previousPresentationScale = this.options.presentationScale;
212
- const previousSharedGeometry = Boolean(this.options.config?.responsive?.reportHostDimensionsWithFixedGrid);
213
- this.options = {
214
- ...this.options,
215
- ...options,
216
- config: options.config !== undefined ? options.config : this.options.config,
217
- };
218
- if (options.sessionId !== undefined && options.sessionId !== previousSessionId) {
219
- this.resetSessionState();
220
- this.subscribeToTerminalData();
221
- this.subscribeToTerminalGeometry();
222
- if (this.terminalCore) {
223
- void this.reinitialize();
224
- }
225
- return;
226
- }
227
- const nextSharedGeometry = Boolean(this.options.config?.responsive?.reportHostDimensionsWithFixedGrid);
228
- if (previousSharedGeometry !== nextSharedGeometry) {
229
- this.lastGeometryGeneration = 0;
230
- this.pendingGeometryEvents = [];
231
- if (!nextSharedGeometry)
232
- this.terminalCore?.setFixedDimensions(null);
233
- this.subscribeToTerminalGeometry();
234
- }
235
- if (options.isActive !== undefined && options.isActive !== previousIsActive) {
236
- if (this.options.isActive) {
237
- this.scheduleInitialize();
238
- if (this.terminalCore && this.options.sessionId) {
239
- void this.connectToSession();
240
- }
241
- }
242
- else {
243
- this.terminalCore?.setConnected(false);
244
- }
245
- }
246
- const nextFontFamily = this.options.config?.fontFamily;
247
- if (previousTheme !== this.options.themeName
248
- || previousFontSize !== this.options.fontSize
249
- || previousFontFamily !== nextFontFamily
250
- || previousPresentationScale !== this.options.presentationScale) {
251
- this.applyCoreAppearance({
252
- themeName: this.options.themeName,
253
- ...(typeof this.options.fontSize === 'number' ? { fontSize: this.options.fontSize } : {}),
254
- ...(typeof nextFontFamily === 'string' ? { fontFamily: nextFontFamily } : {}),
255
- presentationScale: this.options.presentationScale ?? 1
256
- });
257
- }
258
- }
259
- getSnapshot() {
260
- return {
261
- state: computeTerminalState(this.state),
262
- connection: computeConnectionState(this.createConnectionState()),
263
- loadingState: this.loadingState,
264
- loadingMessage: this.loadingMessage,
265
- };
266
- }
267
- subscribe(listener) {
268
- this.listeners.add(listener);
269
- listener(this.getSnapshot());
270
- return () => {
271
- this.listeners.delete(listener);
272
- };
273
- }
274
- getCore() {
275
- return this.terminalCore;
276
- }
277
- emit() {
278
- if (this.disposed) {
279
- return;
280
- }
281
- const snapshot = this.getSnapshot();
282
- for (const listener of this.listeners) {
283
- listener(snapshot);
284
- }
285
- }
286
- updateState(updates) {
287
- this.state = { ...this.state, ...updates };
288
- this.emit();
289
- }
290
- setLoading(state, message) {
291
- this.loadingState = state;
292
- this.loadingMessage = message;
293
- this.emit();
294
- }
295
- setConnectionState(state) {
296
- this.connectionState = state;
297
- this.terminalCore?.setConnected(state === ConnectionState.CONNECTED);
298
- this.emit();
299
- }
300
- setConnectionError(error) {
301
- this.connectionError = error;
302
- this.emit();
303
- }
304
- createConnectionState() {
305
- return {
306
- state: this.connectionState,
307
- error: this.connectionError,
308
- retryCount: this.retryCount,
309
- connect: () => void this.connectToSession(),
310
- disconnect: () => {
311
- this.clearRetryTimeout();
312
- this.connectGeneration += 1;
313
- this.setConnectionState(ConnectionState.ABORTED);
314
- },
315
- retry: () => {
316
- this.clearRetryTimeout();
317
- this.retryCount = 0;
318
- this.setConnectionError(null);
319
- this.setConnectionState(ConnectionState.IDLE);
320
- void this.connectToSession();
321
- },
322
- clearError: () => this.setConnectionError(null),
323
- };
324
- }
325
- resetSessionState() {
326
- this.queueGeneration += 1;
327
- this.connectGeneration += 1;
328
- this.sequenceBuffer.reset(1);
329
- this.replayCompleteReceived = false;
330
- this.isReplayActive = false;
331
- this.lastAppliedSequence = 0;
332
- this.lastGeometryGeneration = 0;
333
- this.pendingGeometryEvents = [];
334
- this.terminalCore?.setFixedDimensions(null);
335
- this.dataQueue = [];
336
- this.isProcessing = false;
337
- this.cancelDataQueueFlush();
338
- this.clearQueueRetryTimeout();
339
- this.setConnectionState(ConnectionState.IDLE);
340
- this.setConnectionError(null);
341
- this.retryCount = 0;
342
- this.clearRetryTimeout();
343
- }
344
- handleUserInput(data) {
345
- const sessionId = this.options.sessionId;
346
- if (!sessionId) {
347
- return;
348
- }
349
- this.options.transport.sendInput(sessionId, data).catch(error => {
350
- this.logger.warn('[TerminalInstanceController] sendInput failed', { error });
351
- this.handleError(error instanceof Error ? error : new Error(String(error)));
352
- });
353
- }
354
- scheduleFocus() {
355
- if (!this.options.autoFocus) {
356
- return;
357
- }
358
- this.scheduler.requestFrame(() => {
359
- this.scheduler.requestFrame(() => {
360
- this.terminalCore?.focus();
361
- });
362
- });
363
- }
364
- cleanupTerminal() {
365
- this.resizeGeneration += 1;
366
- this.initializationAbortController?.abort();
367
- this.initializationAbortController = null;
368
- this.isInitializing = false;
369
- this.disposeCheckpointActor();
370
- if (this.terminalCore) {
371
- this.terminalCore.dispose();
372
- this.terminalCore = null;
373
- }
374
- this.dataQueue = [];
375
- this.isProcessing = false;
376
- }
377
- finishReplayIfIdle() {
378
- if (!this.replayCompleteReceived || this.dataQueue.length > 0 || this.isProcessing) {
379
- return;
380
- }
381
- if (this.isReplayActive) {
382
- this.terminalCore?.endHistoryReplay?.();
383
- this.isReplayActive = false;
384
- }
385
- this.setLoading('ready', '');
386
- this.scheduleFocus();
387
- }
388
- scheduleDataQueueFlush(allowImmediate = false) {
389
- if (this.flushRaf !== null) {
390
- this.immediateFlushEligible = false;
391
- return;
392
- }
393
- this.immediateFlushEligible = allowImmediate;
394
- const generation = this.queueGeneration;
395
- this.flushRaf = this.scheduler.requestFrame(() => {
396
- this.flushRaf = null;
397
- if (this.queueGeneration !== generation) {
398
- return;
399
- }
400
- void this.processDataQueue();
401
- });
402
- if (!allowImmediate || this.isReplayActive || this.flushMicrotaskScheduled) {
403
- return;
404
- }
405
- this.flushMicrotaskScheduled = true;
406
- const microtaskGeneration = ++this.flushMicrotaskGeneration;
407
- queueMicrotask(() => {
408
- if (microtaskGeneration !== this.flushMicrotaskGeneration) {
409
- return;
410
- }
411
- this.flushMicrotaskScheduled = false;
412
- if (this.disposed || this.queueGeneration !== generation || this.isReplayActive || this.isProcessing) {
413
- return;
414
- }
415
- if (!this.immediateFlushEligible
416
- || this.dataQueue.length !== 1
417
- || this.dataQueue[0].data.byteLength > MAX_IMMEDIATE_LIVE_BATCH_BYTES) {
418
- return;
419
- }
420
- this.cancelDataQueueFlush();
421
- void this.processDataQueue();
422
- });
423
- }
424
- cancelDataQueueFlush() {
425
- this.immediateFlushEligible = false;
426
- this.flushMicrotaskScheduled = false;
427
- this.flushMicrotaskGeneration += 1;
428
- if (this.flushRaf === null) {
429
- return;
430
- }
431
- this.scheduler.cancelFrame(this.flushRaf);
432
- this.flushRaf = null;
433
- }
434
- async processDataQueue() {
435
- const generation = this.queueGeneration;
436
- if (this.isProcessing || !this.terminalCore) {
437
- return;
438
- }
439
- this.applyPendingGeometryEvents();
440
- if (this.dataQueue.length === 0)
441
- return;
442
- const terminalState = this.terminalCore.getState();
443
- if (terminalState !== TerminalState.READY && terminalState !== TerminalState.CONNECTED) {
444
- this.clearQueueRetryTimeout();
445
- this.queueRetryTimeout = this.scheduler.setTimer(() => {
446
- this.queueRetryTimeout = null;
447
- if (this.queueGeneration !== generation) {
448
- return;
449
- }
450
- void this.processDataQueue();
451
- }, 100);
452
- return;
453
- }
454
- this.isProcessing = true;
455
- try {
456
- let batchLength = 0;
457
- let batchBytes = 0;
458
- let batchGeometry;
459
- const geometryBoundary = this.pendingGeometryEvents[0]?.outputSequenceBoundary;
460
- for (const chunk of this.dataQueue) {
461
- if (batchLength >= MAX_WRITE_BATCH_CHUNKS) {
462
- break;
463
- }
464
- if (geometryBoundary !== undefined
465
- && chunk.sequence > 0
466
- && chunk.sequence > geometryBoundary) {
467
- break;
468
- }
469
- const hasGeometry = chunk.geometryGeneration !== undefined
470
- || chunk.cols !== undefined
471
- || chunk.rows !== undefined;
472
- if (batchLength > 0 && hasGeometry !== (batchGeometry !== undefined)) {
473
- break;
474
- }
475
- if (hasGeometry) {
476
- if (chunk.geometryGeneration === undefined
477
- || chunk.cols === undefined
478
- || chunk.rows === undefined
479
- || !Number.isSafeInteger(chunk.geometryGeneration)
480
- || chunk.geometryGeneration <= 0
481
- || !Number.isSafeInteger(chunk.cols)
482
- || chunk.cols <= 0
483
- || !Number.isSafeInteger(chunk.rows)
484
- || chunk.rows <= 0) {
485
- throw new Error('terminal history chunk geometry is invalid');
486
- }
487
- const geometry = {
488
- generation: chunk.geometryGeneration,
489
- cols: chunk.cols,
490
- rows: chunk.rows,
491
- };
492
- if (batchGeometry && (batchGeometry.generation !== geometry.generation
493
- || batchGeometry.cols !== geometry.cols
494
- || batchGeometry.rows !== geometry.rows)) {
495
- break;
496
- }
497
- batchGeometry = geometry;
498
- }
499
- if (batchLength > 0 && batchBytes + chunk.data.byteLength > MAX_WRITE_BATCH_BYTES) {
500
- break;
501
- }
502
- batchLength += 1;
503
- batchBytes += chunk.data.byteLength;
504
- }
505
- if (batchLength === 0) {
506
- return;
507
- }
508
- const batch = this.dataQueue.splice(0, batchLength);
509
- if (this.queueGeneration !== generation) {
510
- return;
511
- }
512
- const payload = batch.length === 1 ? batch[0].data : concatChunks(batch.map(chunk => chunk.data));
513
- if (batchGeometry) {
514
- this.terminalCore.setFixedDimensions({ cols: batchGeometry.cols, rows: batchGeometry.rows }, { notifyResize: false });
515
- }
516
- this.terminalCore.writeFrame(payload);
517
- for (const chunk of batch) {
518
- if (this.queueGeneration !== generation) {
519
- return;
520
- }
521
- if (chunk.sequence > this.lastAppliedSequence) {
522
- this.lastAppliedSequence = chunk.sequence;
523
- }
524
- }
525
- this.scheduleCheckpointChunks(batch);
526
- this.applyPendingGeometryEvents();
527
- }
528
- finally {
529
- this.isProcessing = false;
530
- if (this.dataQueue.length > 0) {
531
- this.scheduleDataQueueFlush();
532
- }
533
- else {
534
- this.finishReplayIfIdle();
535
- }
536
- }
537
- }
538
- addChunkToQueue(chunk, allowImmediate = false) {
539
- if (chunk.sequence > 0 && chunk.sequence <= this.lastAppliedSequence) {
540
- return;
541
- }
542
- const ready = this.sequenceBuffer.push(chunk);
543
- if (ready.length === 0) {
544
- return;
545
- }
546
- const queueWasEmpty = this.dataQueue.length === 0;
547
- this.dataQueue.push(...ready);
548
- if (!this.isProcessing) {
549
- this.scheduleDataQueueFlush(queueWasEmpty && ready.length === 1 && allowImmediate);
550
- }
551
- }
552
- applyCoreAppearance(appearance) {
553
- const core = this.terminalCore;
554
- if (!core) {
555
- return;
556
- }
557
- const theme = appearance.themeName ? getThemeColors(appearance.themeName) : undefined;
558
- if (core.setAppearance) {
559
- core.setAppearance({
560
- ...(theme ? { theme } : {}),
561
- ...(typeof appearance.fontSize === 'number' ? { fontSize: appearance.fontSize } : {}),
562
- ...(typeof appearance.fontFamily === 'string' ? { fontFamily: appearance.fontFamily } : {}),
563
- ...(typeof appearance.presentationScale === 'number' ? { presentationScale: appearance.presentationScale } : {})
564
- });
565
- return;
566
- }
567
- if (theme) {
568
- core.setTheme(theme);
569
- }
570
- if (typeof appearance.fontSize === 'number') {
571
- core.setFontSize(appearance.fontSize);
572
- }
573
- if (typeof appearance.fontFamily === 'string') {
574
- core.setFontFamily?.(appearance.fontFamily);
575
- }
576
- if (typeof appearance.presentationScale === 'number') {
577
- core.setPresentationScale(appearance.presentationScale);
578
- }
579
- }
580
- scheduleInitialize() {
581
- if (!this.options.isActive || !this.container || this.terminalCore || this.isInitializing) {
582
- return;
583
- }
584
- void this.initializeTerminal();
585
- }
586
- async initializeTerminal() {
587
- if (!this.container || this.isInitializing || this.terminalCore || this.disposed) {
588
- return;
589
- }
590
- this.isInitializing = true;
591
- const abortController = new AbortController();
592
- this.initializationAbortController = abortController;
593
- this.setLoading('initializing_terminal', 'Initializing terminal...');
594
- try {
595
- const configOverrides = {
596
- ...(this.options.config ?? {}),
597
- sessionId: this.options.sessionId,
598
- ...(this.options.fontSize ? { fontSize: this.options.fontSize } : {}),
599
- ...(this.options.presentationScale ? { presentationScale: this.options.presentationScale } : {}),
600
- };
601
- const config = getDefaultTerminalConfig(this.options.themeName ?? 'dark', configOverrides);
602
- const CoreCtor = this.options.coreConstructor ?? TerminalCore;
603
- const terminalCore = new CoreCtor(this.container, config, {
604
- onData: data => this.handleUserInput(data),
605
- onResize: size => void this.handleResize(size),
606
- onStateChange: this.handleStateChange,
607
- onError: this.handleError,
608
- onRender: durationMs => this.options.onRender?.(durationMs),
609
- }, this.logger ?? noopLogger);
610
- this.terminalCore = terminalCore;
611
- await terminalCore.initialize({ priority: 'interactive', signal: abortController.signal });
612
- if (abortController.signal.aborted
613
- || this.disposed
614
- || !this.container
615
- || this.terminalCore !== terminalCore) {
616
- return;
617
- }
618
- if (this.isReplayActive) {
619
- terminalCore.startHistoryReplay(30000);
620
- }
621
- const sessionId = this.options.sessionId;
622
- if (sessionId && !isAtomicTransport(this.options.transport)) {
623
- const dimensions = terminalCore.getDimensions();
624
- await this.options.transport.resize(sessionId, dimensions.cols, dimensions.rows);
625
- }
626
- void this.processDataQueue();
627
- if (!this.isReplayActive || this.replayCompleteReceived) {
628
- this.setLoading('ready', '');
629
- }
630
- else {
631
- this.setLoading('processing_history', 'Restoring terminal...');
632
- }
633
- if (this.options.isActive && sessionId) {
634
- await this.connectToSession();
635
- }
636
- }
637
- catch (error) {
638
- if (abortController.signal.aborted) {
639
- return;
640
- }
641
- this.handleError(error instanceof Error ? error : new Error(String(error)));
642
- this.setLoading('ready', '');
643
- }
644
- finally {
645
- if (this.initializationAbortController === abortController) {
646
- this.initializationAbortController = null;
647
- this.isInitializing = false;
648
- }
649
- }
650
- }
651
- async connectToSession() {
652
- const sessionId = this.options.sessionId;
653
- if (!sessionId || this.disposed) {
654
- return;
655
- }
656
- this.clearRetryTimeout();
657
- this.setConnectionState(ConnectionState.CONNECTING);
658
- this.setConnectionError(null);
659
- this.setLoading('attaching', 'Attaching to session...');
660
- const connectGeneration = ++this.connectGeneration;
661
- try {
662
- const dims = this.terminalCore?.getDimensions() ?? this.dimensions;
663
- if (isAtomicTransport(this.options.transport)) {
664
- const attached = await this.options.transport.attachWithHistoryBoundary(sessionId, dims.cols, dims.rows);
665
- if (connectGeneration !== this.connectGeneration || this.disposed)
666
- return;
667
- await this.recoverAtomicHistory(sessionId, attached);
668
- }
669
- else {
670
- await this.options.transport.attach(sessionId, dims.cols, dims.rows);
671
- }
672
- if (connectGeneration !== this.connectGeneration || this.disposed)
673
- return;
674
- this.setConnectionState(ConnectionState.CONNECTED);
675
- this.retryCount = 0;
676
- this.emit();
677
- if (!this.isReplayActive || this.replayCompleteReceived) {
678
- this.setLoading('ready', '');
679
- }
680
- else {
681
- this.setLoading('processing_history', 'Restoring terminal...');
682
- }
683
- }
684
- catch (error) {
685
- if (connectGeneration !== this.connectGeneration || this.disposed)
686
- return;
687
- const terminalError = createTerminalError('transport', error);
688
- this.setConnectionState(ConnectionState.FAILED);
689
- this.setConnectionError(terminalError);
690
- this.setLoading('ready', '');
691
- this.scheduleConnectionRetry();
692
- }
693
- }
694
- async recoverAtomicHistory(sessionId, attachment) {
695
- const boundary = attachment.historyBoundarySequence;
696
- const historyGeneration = attachment.historyGeneration;
697
- const historyStartSequence = attachment.historyStartSequence;
698
- if (!Number.isSafeInteger(boundary) || boundary < 0) {
699
- throw new Error('terminal live history boundary is invalid');
700
- }
701
- if (!Number.isSafeInteger(historyGeneration) || historyGeneration < 1) {
702
- throw new Error('terminal live history generation is invalid');
703
- }
704
- if (!Number.isSafeInteger(historyStartSequence)
705
- || historyStartSequence < 1
706
- || historyStartSequence > boundary + 1) {
707
- throw new Error('terminal live history start sequence is invalid');
708
- }
709
- if (!isAtomicTransport(this.options.transport)) {
710
- throw new Error('terminal atomic history page transport is required');
711
- }
712
- this.lastAppliedSequence = Math.max(this.lastAppliedSequence, historyStartSequence - 1);
713
- this.applyPendingGeometryEvents();
714
- let queuedHistoryOutput = false;
715
- let restoredCheckpoint = false;
716
- let checkpointActorConfigured = false;
717
- let startSequence = Math.max(this.lastAppliedSequence + 1, historyStartSequence);
718
- if (boundary === 0 && historyStartSequence === 1) {
719
- checkpointActorConfigured = this.configureCheckpointActor(attachment, undefined, 0);
720
- }
721
- while (startSequence <= boundary) {
722
- const page = await this.options.transport.historyPage(sessionId, startSequence, boundary, historyGeneration);
723
- if (page.historyGeneration !== historyGeneration || page.historyReset) {
724
- throw new Error('terminal history generation changed during atomic replay');
725
- }
726
- if (page.snapshotEndSequence !== boundary) {
727
- throw new Error('terminal history snapshot boundary does not match live attachment');
728
- }
729
- if (page.checkpoint) {
730
- if (restoredCheckpoint) {
731
- throw new Error('terminal history page repeated its authoritative checkpoint');
732
- }
733
- const checkpoint = page.checkpoint;
734
- if (checkpoint.parserEpoch !== historyGeneration) {
735
- throw new Error('terminal history checkpoint parser epoch does not match history generation');
736
- }
737
- if (checkpoint.formatVersion !== 1
738
- || checkpoint.engineId !== 'floegence-ghostty-web'
739
- || !Number.isSafeInteger(checkpoint.coveredThroughSequence)
740
- || checkpoint.coveredThroughSequence < 0
741
- || checkpoint.coveredThroughSequence > boundary
742
- || !Number.isSafeInteger(checkpoint.geometryGeneration)
743
- || checkpoint.geometryGeneration <= 0
744
- || !Number.isSafeInteger(checkpoint.parserEpoch)
745
- || checkpoint.parserEpoch <= 0
746
- || !Number.isSafeInteger(checkpoint.cols)
747
- || checkpoint.cols <= 0
748
- || !Number.isSafeInteger(checkpoint.rows)
749
- || checkpoint.rows <= 0
750
- || !(checkpoint.bytes instanceof Uint8Array)
751
- || checkpoint.bytes.byteLength === 0
752
- || !/^[0-9a-f]{64}$/.test(checkpoint.checksumSha256)
753
- || !/^[0-9a-f]{64}$/.test(checkpoint.stateDigestSha256)) {
754
- throw new Error('terminal history authoritative checkpoint is invalid');
755
- }
756
- if (page.deltaStartSequence !== undefined && page.deltaStartSequence !== checkpoint.coveredThroughSequence + 1) {
757
- throw new Error('terminal history checkpoint delta boundary is invalid');
758
- }
759
- if (!this.terminalCore?.restoreAuthoritativeCheckpoint) {
760
- throw new Error('terminal history checkpoint restore is unavailable');
761
- }
762
- this.terminalCore.setFixedDimensions({ cols: checkpoint.cols, rows: checkpoint.rows }, { notifyResize: false });
763
- await this.terminalCore.restoreAuthoritativeCheckpoint({
764
- ...checkpoint,
765
- bytes: checkpoint.bytes.slice(),
766
- });
767
- restoredCheckpoint = true;
768
- checkpointActorConfigured = this.configureCheckpointActor(attachment, checkpoint, checkpoint.coveredThroughSequence);
769
- this.lastAppliedSequence = Math.max(this.lastAppliedSequence, checkpoint.coveredThroughSequence);
770
- startSequence = Math.max(startSequence, checkpoint.coveredThroughSequence + 1);
771
- }
772
- else if (page.historyTruncated) {
773
- throw new Error('terminal history was truncated before the acknowledged live boundary');
774
- }
775
- if (!checkpointActorConfigured && startSequence === historyStartSequence) {
776
- checkpointActorConfigured = this.configureCheckpointActor(attachment, undefined, historyStartSequence - 1);
777
- }
778
- if (!Number.isSafeInteger(page.coveredThroughSequence)
779
- || page.coveredThroughSequence < startSequence - 1
780
- || page.coveredThroughSequence > boundary) {
781
- throw new Error('terminal history page coverage is invalid');
782
- }
783
- let expectedSequence = startSequence;
784
- for (const chunk of page.chunks) {
785
- if (!Number.isSafeInteger(chunk.sequence) || chunk.sequence !== expectedSequence) {
786
- throw new Error(`missing terminal output sequence ${expectedSequence} before replay boundary ${boundary}`);
787
- }
788
- if (chunk.sequence > page.coveredThroughSequence) {
789
- throw new Error('terminal history chunk exceeded its page coverage');
790
- }
791
- this.addChunkToQueue(chunk);
792
- queuedHistoryOutput = true;
793
- expectedSequence += 1;
794
- }
795
- const explicitlyCleared = page.chunks.length === 0 && page.totalBytes === 0;
796
- if (page.coveredThroughSequence >= expectedSequence && !explicitlyCleared) {
797
- throw new Error(`missing terminal output sequence ${expectedSequence} before replay boundary ${boundary}`);
798
- }
799
- if (!page.hasMore) {
800
- if (page.coveredThroughSequence !== boundary) {
801
- throw new Error('terminal history did not cover the acknowledged live boundary');
802
- }
803
- startSequence = boundary + 1;
804
- break;
805
- }
806
- if (!Number.isSafeInteger(page.nextStartSequence)
807
- || page.nextStartSequence !== page.coveredThroughSequence + 1) {
808
- throw new Error('terminal history page cursor is invalid');
809
- }
810
- startSequence = page.nextStartSequence;
811
- }
812
- const ready = this.sequenceBuffer.coverThrough(boundary);
813
- if (ready.length > 0) {
814
- this.dataQueue.push(...ready);
815
- this.scheduleDataQueueFlush();
816
- }
817
- if (!queuedHistoryOutput) {
818
- this.lastAppliedSequence = Math.max(this.lastAppliedSequence, boundary);
819
- this.applyPendingGeometryEvents();
820
- }
821
- this.replayCompleteReceived = true;
822
- this.finishReplayIfIdle();
823
- }
824
- configureCheckpointActor(attachment, checkpoint, initialSequence) {
825
- if (this.checkpointActor || !isAtomicTransport(this.options.transport))
826
- return this.checkpointActor !== null;
827
- if (typeof this.options.transport.commitHistoryCheckpoint !== 'function')
828
- return false;
829
- const captureEveryBytes = this.options.checkpointCompaction?.captureEveryBytes
830
- ?? DEFAULT_CHECKPOINT_CAPTURE_BYTES;
831
- if (!Number.isSafeInteger(captureEveryBytes) || captureEveryBytes <= 0) {
832
- this.logger.warn('[TerminalInstanceController] Checkpoint compaction budget is invalid');
833
- return false;
834
- }
835
- const actor = this.options.checkpointCompaction?.createActor?.() ?? createGhosttyCheckpointActor();
836
- const generation = ++this.checkpointActorGeneration;
837
- this.checkpointActor = actor;
838
- this.checkpointBytesSinceCommit = 0;
839
- this.checkpointActorTail = actor.start({
840
- cols: checkpoint?.cols ?? attachment.cols,
841
- rows: checkpoint?.rows ?? attachment.rows,
842
- parserEpoch: checkpoint?.parserEpoch ?? attachment.historyGeneration,
843
- ...(!checkpoint && initialSequence > 0 ? { initialSequence } : {}),
844
- ...(checkpoint ? { checkpoint } : {}),
845
- }).catch(error => {
846
- this.disableCheckpointActor(actor, generation, error);
847
- });
848
- return true;
849
- }
850
- scheduleCheckpointChunks(chunks) {
851
- const actor = this.checkpointActor;
852
- const sessionId = this.options.sessionId;
853
- if (!actor || !sessionId || !isAtomicTransport(this.options.transport))
854
- return;
855
- const commit = this.options.transport.commitHistoryCheckpoint;
856
- if (!commit)
857
- return;
858
- const generation = this.checkpointActorGeneration;
859
- const captureEveryBytes = this.options.checkpointCompaction?.captureEveryBytes
860
- ?? DEFAULT_CHECKPOINT_CAPTURE_BYTES;
861
- let actorChunks;
862
- try {
863
- actorChunks = chunks.map(chunk => {
864
- if (!Number.isSafeInteger(chunk.sequence) || chunk.sequence <= 0
865
- || !Number.isSafeInteger(chunk.geometryGeneration) || (chunk.geometryGeneration ?? 0) <= 0
866
- || !Number.isSafeInteger(chunk.cols) || (chunk.cols ?? 0) <= 0
867
- || !Number.isSafeInteger(chunk.rows) || (chunk.rows ?? 0) <= 0) {
868
- throw new Error('checkpoint compaction requires sequenced geometry-tagged output');
869
- }
870
- return {
871
- sequence: chunk.sequence,
872
- data: chunk.data.slice(),
873
- geometryGeneration: chunk.geometryGeneration,
874
- cols: chunk.cols,
875
- rows: chunk.rows,
876
- };
877
- });
878
- }
879
- catch (error) {
880
- this.disableCheckpointActor(actor, generation, error);
881
- return;
882
- }
883
- const addedBytes = actorChunks.reduce((total, chunk) => total + chunk.data.byteLength, 0);
884
- this.checkpointActorTail = this.checkpointActorTail.then(async () => {
885
- if (this.checkpointActor !== actor || this.checkpointActorGeneration !== generation)
886
- return;
887
- actor.append(actorChunks);
888
- this.checkpointBytesSinceCommit += addedBytes;
889
- if (this.checkpointBytesSinceCommit < captureEveryBytes)
890
- return;
891
- const targetSequence = actorChunks[actorChunks.length - 1].sequence;
892
- const checkpoint = await actor.capture(targetSequence);
893
- if (this.checkpointActor !== actor || this.checkpointActorGeneration !== generation)
894
- return;
895
- await commit.call(this.options.transport, sessionId, checkpoint);
896
- if (this.checkpointActor === actor && this.checkpointActorGeneration === generation) {
897
- this.checkpointBytesSinceCommit = 0;
898
- }
899
- }).catch(error => {
900
- this.disableCheckpointActor(actor, generation, error);
901
- });
902
- }
903
- disableCheckpointActor(actor, generation, error) {
904
- if (this.checkpointActor !== actor || this.checkpointActorGeneration !== generation)
905
- return;
906
- this.logger.warn('[TerminalInstanceController] Checkpoint compaction stopped; durable raw history remains retained', {
907
- error: error instanceof Error ? error.message : String(error),
908
- });
909
- this.checkpointActor = null;
910
- this.checkpointBytesSinceCommit = 0;
911
- actor.dispose();
912
- }
913
- disposeCheckpointActor() {
914
- const actor = this.checkpointActor;
915
- this.checkpointActor = null;
916
- this.checkpointActorGeneration += 1;
917
- this.checkpointBytesSinceCommit = 0;
918
- actor?.dispose();
919
- this.checkpointActorTail = Promise.resolve();
920
- }
921
- scheduleConnectionRetry() {
922
- if (this.connectionState !== ConnectionState.FAILED || this.disposed) {
923
- return;
924
- }
925
- const delay = Math.min(5000, 1000 * Math.pow(2, this.retryCount));
926
- this.clearRetryTimeout();
927
- this.retryTimeout = this.scheduler.setTimer(() => {
928
- this.retryTimeout = null;
929
- this.retryCount += 1;
930
- this.setConnectionState(ConnectionState.RETRYING);
931
- void this.connectToSession();
932
- }, delay);
933
- }
934
- clearRetryTimeout() {
935
- if (!this.retryTimeout) {
936
- return;
937
- }
938
- this.scheduler.clearTimer(this.retryTimeout);
939
- this.retryTimeout = null;
940
- }
941
- clearQueueRetryTimeout() {
942
- if (!this.queueRetryTimeout) {
943
- return;
944
- }
945
- this.scheduler.clearTimer(this.queueRetryTimeout);
946
- this.queueRetryTimeout = null;
947
- }
948
- subscribeToTerminalData() {
949
- this.terminalDataUnsubscribe?.();
950
- this.terminalDataUnsubscribe = null;
951
- const sessionId = this.options.sessionId;
952
- if (!sessionId) {
953
- return;
954
- }
955
- this.isReplayActive = true;
956
- this.replayCompleteReceived = false;
957
- this.terminalCore?.startHistoryReplay(30000);
958
- this.setLoading('processing_history', 'Restoring terminal...');
959
- const lastSeq = this.terminalCore ? this.lastAppliedSequence : 0;
960
- const unsubscribe = this.options.eventSource.onTerminalData(sessionId, (payload) => {
961
- try {
962
- if (payload.type === 'error') {
963
- throw new Error(payload.error || 'Terminal stream failed');
964
- }
965
- if (payload.type === 'replay-complete') {
966
- const boundary = payload.sequence ?? this.lastAppliedSequence;
967
- this.sequenceBuffer.assertCoveredThrough(boundary);
968
- this.replayCompleteReceived = true;
969
- const ready = this.sequenceBuffer.flushPending();
970
- if (ready.length > 0) {
971
- this.dataQueue.push(...ready);
972
- this.scheduleDataQueueFlush();
973
- }
974
- this.finishReplayIfIdle();
975
- return;
976
- }
977
- const chunk = {
978
- data: payload.data,
979
- sequence: payload.sequence ?? 0,
980
- timestampMs: payload.timestampMs ?? Date.now(),
981
- ...(payload.geometryGeneration !== undefined ? { geometryGeneration: payload.geometryGeneration } : {}),
982
- ...(payload.cols !== undefined ? { cols: payload.cols } : {}),
983
- ...(payload.rows !== undefined ? { rows: payload.rows } : {})
984
- };
985
- this.addChunkToQueue(chunk, payload.liveBatchSize === 1);
986
- }
987
- catch (value) {
988
- const error = value instanceof Error ? value : new Error(String(value));
989
- const terminalError = createTerminalError('connection', error);
990
- this.setConnectionState(ConnectionState.FAILED);
991
- this.setConnectionError(terminalError);
992
- this.setLoading('ready', '');
993
- this.updateState({ error, state: TerminalState.ERROR });
994
- this.options.onError?.(error);
995
- this.scheduleConnectionRetry();
996
- }
997
- }, { lastSeq });
998
- this.terminalDataUnsubscribe = unsubscribe;
999
- }
1000
- subscribeToTerminalGeometry() {
1001
- this.terminalGeometryUnsubscribe?.();
1002
- this.terminalGeometryUnsubscribe = null;
1003
- if (!this.options.config?.responsive?.reportHostDimensionsWithFixedGrid) {
1004
- return;
1005
- }
1006
- const sessionId = this.options.sessionId;
1007
- const subscribe = this.options.eventSource.onTerminalGeometry;
1008
- if (!sessionId || !subscribe) {
1009
- return;
1010
- }
1011
- this.terminalGeometryUnsubscribe = subscribe(sessionId, (event) => {
1012
- if (!Number.isSafeInteger(event.generation) || event.generation <= 0 ||
1013
- !Number.isSafeInteger(event.outputSequenceBoundary) || event.outputSequenceBoundary < 0 ||
1014
- !Number.isSafeInteger(event.cols) || event.cols <= 0 ||
1015
- !Number.isSafeInteger(event.rows) || event.rows <= 0) {
1016
- this.handleError(new Error('terminal live geometry is invalid'));
1017
- return;
1018
- }
1019
- if (event.generation <= this.lastGeometryGeneration) {
1020
- return;
1021
- }
1022
- const duplicate = this.pendingGeometryEvents.find(pending => pending.generation === event.generation);
1023
- if (duplicate) {
1024
- if (duplicate.outputSequenceBoundary !== event.outputSequenceBoundary
1025
- || duplicate.cols !== event.cols
1026
- || duplicate.rows !== event.rows) {
1027
- this.handleError(new Error('terminal live geometry generation is inconsistent'));
1028
- }
1029
- return;
1030
- }
1031
- this.pendingGeometryEvents.push(event);
1032
- this.pendingGeometryEvents.sort((left, right) => left.generation - right.generation);
1033
- this.applyPendingGeometryEvents();
1034
- });
1035
- }
1036
- applyPendingGeometryEvents() {
1037
- if (!this.terminalCore)
1038
- return;
1039
- while (this.pendingGeometryEvents.length > 0) {
1040
- const event = this.pendingGeometryEvents[0];
1041
- if (event.generation <= this.lastGeometryGeneration) {
1042
- this.pendingGeometryEvents.shift();
1043
- continue;
1044
- }
1045
- if (event.outputSequenceBoundary > this.lastAppliedSequence)
1046
- return;
1047
- this.pendingGeometryEvents.shift();
1048
- this.lastGeometryGeneration = event.generation;
1049
- this.terminalCore.setFixedDimensions({ cols: event.cols, rows: event.rows });
1050
- }
1051
- }
1052
- async reinitialize() {
1053
- this.terminalDataUnsubscribe?.();
1054
- this.terminalDataUnsubscribe = null;
1055
- this.queueGeneration += 1;
1056
- this.sequenceBuffer.reset(1);
1057
- this.replayCompleteReceived = false;
1058
- this.isReplayActive = true;
1059
- this.lastAppliedSequence = 0;
1060
- this.lastGeometryGeneration = 0;
1061
- this.pendingGeometryEvents = [];
1062
- this.dataQueue = [];
1063
- this.isProcessing = false;
1064
- this.cancelDataQueueFlush();
1065
- this.clearQueueRetryTimeout();
1066
- this.setLoading('processing_history', 'Restoring terminal...');
1067
- this.cleanupTerminal();
1068
- this.subscribeToTerminalData();
1069
- this.subscribeToTerminalGeometry();
1070
- await this.initializeTerminal();
1071
- }
1072
- }
1073
- export const createTerminalInstance = (options) => {
1074
- return new FrameworkNeutralTerminalInstanceController(options);
1075
- };
1076
- //# sourceMappingURL=TerminalInstanceController.js.map