@floegence/floeterm-terminal-web 0.13.6 → 0.14.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.md +3 -3
- package/dist/checkpoint/GhosttyCheckpoint.worker.d.ts +2 -0
- package/dist/checkpoint/GhosttyCheckpoint.worker.d.ts.map +1 -0
- package/dist/checkpoint/GhosttyCheckpoint.worker.js +9 -0
- package/dist/checkpoint/GhosttyCheckpoint.worker.js.map +1 -0
- package/dist/checkpoint/GhosttyCheckpointActor.d.ts +44 -0
- package/dist/checkpoint/GhosttyCheckpointActor.d.ts.map +1 -0
- package/dist/checkpoint/GhosttyCheckpointActor.js +284 -0
- package/dist/checkpoint/GhosttyCheckpointActor.js.map +1 -0
- package/dist/checkpoint/GhosttyCheckpointEngine.d.ts +62 -0
- package/dist/checkpoint/GhosttyCheckpointEngine.d.ts.map +1 -0
- package/dist/checkpoint/GhosttyCheckpointEngine.js +175 -0
- package/dist/checkpoint/GhosttyCheckpointEngine.js.map +1 -0
- package/dist/checkpoint/GhosttyCheckpointWorkerRuntime.d.ts +31 -0
- package/dist/checkpoint/GhosttyCheckpointWorkerRuntime.d.ts.map +1 -0
- package/dist/checkpoint/GhosttyCheckpointWorkerRuntime.js +112 -0
- package/dist/checkpoint/GhosttyCheckpointWorkerRuntime.js.map +1 -0
- package/dist/core/PagedTerminalOutputCoordinator.d.ts +5 -1
- package/dist/core/PagedTerminalOutputCoordinator.d.ts.map +1 -1
- package/dist/core/PagedTerminalOutputCoordinator.js +83 -5
- package/dist/core/PagedTerminalOutputCoordinator.js.map +1 -1
- package/dist/core/TerminalCore.d.ts +7 -0
- package/dist/core/TerminalCore.d.ts.map +1 -1
- package/dist/core/TerminalCore.js +93 -13
- package/dist/core/TerminalCore.js.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/internal/GhosttyCompatibilityVersion.d.ts +1 -1
- package/dist/internal/GhosttyCompatibilityVersion.d.ts.map +1 -1
- package/dist/internal/GhosttyCompatibilityVersion.js +1 -1
- package/dist/internal/GhosttyCompatibilityVersion.js.map +1 -1
- package/dist/internal/TerminalResourceLoader.d.ts +14 -14
- package/dist/internal/TerminalResourceLoader.d.ts.map +1 -1
- package/dist/internal/TerminalResourceLoader.js +11 -11
- package/dist/internal/TerminalResourceLoader.js.map +1 -1
- package/dist/live/transport.d.ts +2 -1
- package/dist/live/transport.d.ts.map +1 -1
- package/dist/live/transport.js +1 -0
- package/dist/live/transport.js.map +1 -1
- package/dist/manager/TerminalInstanceController.d.ts +8 -0
- package/dist/manager/TerminalInstanceController.d.ts.map +1 -1
- package/dist/manager/TerminalInstanceController.js +158 -1
- package/dist/manager/TerminalInstanceController.js.map +1 -1
- package/dist/types.d.ts +39 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -164,10 +164,10 @@ the foreground revision that was active at that exact byte boundary.
|
|
|
164
164
|
## Notes
|
|
165
165
|
|
|
166
166
|
- You must provide a `TerminalTransport` and `TerminalEventSource` for the managed controller.
|
|
167
|
-
- Each `TerminalCore` owns an isolated
|
|
168
|
-
- `scrollback` is measured in terminal buffer rows, including separately wrapped rows, and accepts integers from 1 through 10,000. Within the supported geometry, Floeterm preserves at least the requested number of buffer rows; it does not trim to an exact physical row count. Floeterm maps that contract to a bounded byte budget for the exact pinned
|
|
167
|
+
- Each `TerminalCore` owns an isolated `@floegence/ghostty-web` WASM runtime; consumers must not provide or share a Ghostty runtime.
|
|
168
|
+
- `scrollback` is measured in terminal buffer rows, including separately wrapped rows, and accepts integers from 1 through 10,000. Within the supported geometry, Floeterm preserves at least the requested number of buffer rows; it does not trim to an exact physical row count. Floeterm maps that contract to a bounded byte budget for the exact pinned `@floegence/ghostty-web@0.5.0-rc.0` release, which currently interprets the value as bytes. The 81,920,000-byte cap limits Ghostty's scrollback arena, not the total memory of a WASM runtime. The mapping is temporary and must be reviewed and removed or updated with any Ghostty upgrade.
|
|
169
169
|
- Supported terminal geometry is limited to 500 columns. Explicit dimensions above that limit fail, while automatic fitting reports at most 500 columns.
|
|
170
|
-
- Consumer dependency overrides for
|
|
170
|
+
- Consumer dependency overrides for `@floegence/ghostty-web` are unsupported. Floeterm's compatibility guard requires the exact package pin so an upstream behavior change cannot silently apply the row-to-byte mapping twice or reactivate the native canvas scrollbar beneath the renderer-neutral DOM control.
|
|
171
171
|
- `TerminalCore` bridges the hidden textarea used by `ghostty-web`, so soft-keyboard and composition input continue to work on touch devices.
|
|
172
172
|
- The hidden terminal input is mounted in the document viewport plane and anchored from rendered cursor geometry, so native IME candidate windows stay aligned without expanding transformed terminal hosts.
|
|
173
173
|
- Programmatic terminal focus uses no-scroll focus by default, keeping embedded terminals stable inside scaled, projected, or otherwise transformed host surfaces.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GhosttyCheckpoint.worker.d.ts","sourceRoot":"","sources":["../../src/checkpoint/GhosttyCheckpoint.worker.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { GhosttyCheckpointWorkerRuntime } from './GhosttyCheckpointWorkerRuntime';
|
|
2
|
+
const workerScope = globalThis;
|
|
3
|
+
const runtime = new GhosttyCheckpointWorkerRuntime({
|
|
4
|
+
postMessage: (message, transfer) => workerScope.postMessage(message, transfer),
|
|
5
|
+
});
|
|
6
|
+
workerScope.addEventListener('message', event => {
|
|
7
|
+
void runtime.handle(event.data);
|
|
8
|
+
});
|
|
9
|
+
//# sourceMappingURL=GhosttyCheckpoint.worker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GhosttyCheckpoint.worker.js","sourceRoot":"","sources":["../../src/checkpoint/GhosttyCheckpoint.worker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,kCAAkC,CAAC;AAElF,MAAM,WAAW,GAAG,UAGnB,CAAC;AAEF,MAAM,OAAO,GAAG,IAAI,8BAA8B,CAAC;IACjD,WAAW,EAAE,CAAC,OAAO,EAAE,QAAQ,EAAE,EAAE,CAAC,WAAW,CAAC,WAAW,CAAC,OAAO,EAAE,QAAQ,CAAC;CAC/E,CAAC,CAAC;AAEH,WAAW,CAAC,gBAAgB,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE;IAC9C,KAAK,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AAClC,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { TerminalHistoryCheckpoint } from '../types.js';
|
|
2
|
+
export type GhosttyCheckpointActorState = 'idle' | 'starting' | 'ready' | 'failed' | 'disposed';
|
|
3
|
+
export interface GhosttyCheckpointChunk {
|
|
4
|
+
sequence: number;
|
|
5
|
+
data: Uint8Array;
|
|
6
|
+
geometryGeneration: number;
|
|
7
|
+
cols: number;
|
|
8
|
+
rows: number;
|
|
9
|
+
}
|
|
10
|
+
export type GhosttyAuthoritativeCheckpoint = TerminalHistoryCheckpoint;
|
|
11
|
+
export interface GhosttyCheckpointWorkerLike {
|
|
12
|
+
postMessage(message: unknown, transfer?: Transferable[]): void;
|
|
13
|
+
addEventListener(type: 'message' | 'error', listener: EventListener): void;
|
|
14
|
+
removeEventListener(type: 'message' | 'error', listener: EventListener): void;
|
|
15
|
+
terminate(): void;
|
|
16
|
+
}
|
|
17
|
+
export interface GhosttyCheckpointActorOptions {
|
|
18
|
+
createWorker?: () => GhosttyCheckpointWorkerLike;
|
|
19
|
+
maxQueuedBytes?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface GhosttyCheckpointActorStartOptions {
|
|
22
|
+
cols: number;
|
|
23
|
+
rows: number;
|
|
24
|
+
parserEpoch: number;
|
|
25
|
+
initialSequence?: number;
|
|
26
|
+
checkpoint?: GhosttyAuthoritativeCheckpoint;
|
|
27
|
+
}
|
|
28
|
+
export interface GhosttyCheckpointActorSnapshot {
|
|
29
|
+
state: GhosttyCheckpointActorState;
|
|
30
|
+
queuedThroughSequence: number;
|
|
31
|
+
appliedThroughSequence: number;
|
|
32
|
+
queuedBytes: number;
|
|
33
|
+
parserEpoch: number;
|
|
34
|
+
failure: Error | null;
|
|
35
|
+
}
|
|
36
|
+
export interface GhosttyCheckpointActor {
|
|
37
|
+
start(options: GhosttyCheckpointActorStartOptions): Promise<void>;
|
|
38
|
+
append(chunks: readonly GhosttyCheckpointChunk[]): void;
|
|
39
|
+
capture(targetSequence: number): Promise<GhosttyAuthoritativeCheckpoint>;
|
|
40
|
+
getSnapshot(): GhosttyCheckpointActorSnapshot;
|
|
41
|
+
dispose(): void;
|
|
42
|
+
}
|
|
43
|
+
export declare const createGhosttyCheckpointActor: (options?: GhosttyCheckpointActorOptions) => GhosttyCheckpointActor;
|
|
44
|
+
//# sourceMappingURL=GhosttyCheckpointActor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GhosttyCheckpointActor.d.ts","sourceRoot":"","sources":["../../src/checkpoint/GhosttyCheckpointActor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE7D,MAAM,MAAM,2BAA2B,GAAG,MAAM,GAAG,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEhG,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,MAAM,8BAA8B,GAAG,yBAAyB,CAAC;AAEvE,MAAM,WAAW,2BAA2B;IAC1C,WAAW,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,YAAY,EAAE,GAAG,IAAI,CAAC;IAC/D,gBAAgB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC3E,mBAAmB,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI,CAAC;IAC9E,SAAS,IAAI,IAAI,CAAC;CACnB;AAED,MAAM,WAAW,6BAA6B;IAC5C,YAAY,CAAC,EAAE,MAAM,2BAA2B,CAAC;IACjD,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,MAAM,WAAW,kCAAkC;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,8BAA8B,CAAC;CAC7C;AAED,MAAM,WAAW,8BAA8B;IAC7C,KAAK,EAAE,2BAA2B,CAAC;IACnC,qBAAqB,EAAE,MAAM,CAAC;IAC9B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,KAAK,GAAG,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,OAAO,EAAE,kCAAkC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAClE,MAAM,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,GAAG,IAAI,CAAC;IACxD,OAAO,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,8BAA8B,CAAC,CAAC;IACzE,WAAW,IAAI,8BAA8B,CAAC;IAC9C,OAAO,IAAI,IAAI,CAAC;CACjB;AAgTD,eAAO,MAAM,4BAA4B,GACvC,UAAS,6BAAkC,KAC1C,sBAAiE,CAAC"}
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
const DEFAULT_MAX_QUEUED_BYTES = 16 * 1024 * 1024;
|
|
2
|
+
const SHA256_HEX = /^[0-9a-f]{64}$/;
|
|
3
|
+
const assertPositiveSafeInteger = (value, field) => {
|
|
4
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
5
|
+
throw new Error(`${field} must be a positive safe integer`);
|
|
6
|
+
}
|
|
7
|
+
};
|
|
8
|
+
const assertNonNegativeSafeInteger = (value, field) => {
|
|
9
|
+
if (!Number.isSafeInteger(value) || value < 0) {
|
|
10
|
+
throw new Error(`${field} must be a non-negative safe integer`);
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
const toError = (value, fallback) => {
|
|
14
|
+
if (value instanceof Error)
|
|
15
|
+
return value;
|
|
16
|
+
if (typeof value === 'string' && value.trim() !== '')
|
|
17
|
+
return new Error(value);
|
|
18
|
+
return new Error(fallback);
|
|
19
|
+
};
|
|
20
|
+
const defaultCreateWorker = () => new Worker(new URL('./GhosttyCheckpoint.worker.js', import.meta.url), { type: 'module', name: 'floeterm-ghostty-checkpoint' });
|
|
21
|
+
class GhosttyCheckpointActorImpl {
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.state = 'idle';
|
|
24
|
+
this.parserEpoch = 0;
|
|
25
|
+
this.queuedThroughSequence = 0;
|
|
26
|
+
this.appliedThroughSequence = 0;
|
|
27
|
+
this.queuedBytes = 0;
|
|
28
|
+
this.geometryGeneration = 0;
|
|
29
|
+
this.cols = 0;
|
|
30
|
+
this.rows = 0;
|
|
31
|
+
this.nextRequestId = 1;
|
|
32
|
+
this.failure = null;
|
|
33
|
+
this.startResolve = null;
|
|
34
|
+
this.startReject = null;
|
|
35
|
+
this.captures = new Map();
|
|
36
|
+
this.onMessage = (event) => {
|
|
37
|
+
const data = event.data;
|
|
38
|
+
try {
|
|
39
|
+
this.handleMessage(data);
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
this.fail(toError(error, 'invalid Ghostty checkpoint worker response'));
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
this.onError = (event) => {
|
|
46
|
+
const message = event.message;
|
|
47
|
+
this.fail(new Error(message || 'Ghostty checkpoint worker crashed'));
|
|
48
|
+
};
|
|
49
|
+
const maxQueuedBytes = options.maxQueuedBytes ?? DEFAULT_MAX_QUEUED_BYTES;
|
|
50
|
+
assertPositiveSafeInteger(maxQueuedBytes, 'maxQueuedBytes');
|
|
51
|
+
this.maxQueuedBytes = maxQueuedBytes;
|
|
52
|
+
this.worker = (options.createWorker ?? defaultCreateWorker)();
|
|
53
|
+
this.worker.addEventListener('message', this.onMessage);
|
|
54
|
+
this.worker.addEventListener('error', this.onError);
|
|
55
|
+
}
|
|
56
|
+
start(options) {
|
|
57
|
+
if (this.state !== 'idle')
|
|
58
|
+
return Promise.reject(new Error('Ghostty checkpoint actor has already started'));
|
|
59
|
+
assertPositiveSafeInteger(options.cols, 'cols');
|
|
60
|
+
assertPositiveSafeInteger(options.rows, 'rows');
|
|
61
|
+
assertPositiveSafeInteger(options.parserEpoch, 'parserEpoch');
|
|
62
|
+
assertNonNegativeSafeInteger(options.initialSequence ?? 0, 'initialSequence');
|
|
63
|
+
if (options.checkpoint && options.initialSequence !== undefined) {
|
|
64
|
+
return Promise.reject(new Error('checkpoint and initialSequence are mutually exclusive'));
|
|
65
|
+
}
|
|
66
|
+
if (options.checkpoint && options.checkpoint.parserEpoch !== options.parserEpoch) {
|
|
67
|
+
return Promise.reject(new Error('checkpoint parser epoch does not match actor parser epoch'));
|
|
68
|
+
}
|
|
69
|
+
this.state = 'starting';
|
|
70
|
+
this.parserEpoch = options.parserEpoch;
|
|
71
|
+
this.cols = options.checkpoint?.cols ?? options.cols;
|
|
72
|
+
this.rows = options.checkpoint?.rows ?? options.rows;
|
|
73
|
+
this.geometryGeneration = options.checkpoint?.geometryGeneration ?? 0;
|
|
74
|
+
this.queuedThroughSequence = options.checkpoint?.coveredThroughSequence ?? options.initialSequence ?? 0;
|
|
75
|
+
this.appliedThroughSequence = this.queuedThroughSequence;
|
|
76
|
+
const checkpointBytes = options.checkpoint?.bytes.slice();
|
|
77
|
+
const transfer = checkpointBytes ? [checkpointBytes.buffer] : [];
|
|
78
|
+
const started = new Promise((resolve, reject) => {
|
|
79
|
+
this.startResolve = resolve;
|
|
80
|
+
this.startReject = reject;
|
|
81
|
+
});
|
|
82
|
+
this.worker.postMessage({
|
|
83
|
+
type: 'initialize',
|
|
84
|
+
cols: this.cols,
|
|
85
|
+
rows: this.rows,
|
|
86
|
+
parserEpoch: this.parserEpoch,
|
|
87
|
+
initialSequence: options.initialSequence,
|
|
88
|
+
checkpoint: options.checkpoint ? {
|
|
89
|
+
...options.checkpoint,
|
|
90
|
+
bytes: checkpointBytes,
|
|
91
|
+
} : undefined,
|
|
92
|
+
}, transfer);
|
|
93
|
+
return started;
|
|
94
|
+
}
|
|
95
|
+
append(chunks) {
|
|
96
|
+
this.assertReady();
|
|
97
|
+
if (chunks.length === 0)
|
|
98
|
+
return;
|
|
99
|
+
let sequence = this.queuedThroughSequence;
|
|
100
|
+
let geometryGeneration = this.geometryGeneration;
|
|
101
|
+
let cols = this.cols;
|
|
102
|
+
let rows = this.rows;
|
|
103
|
+
let addedBytes = 0;
|
|
104
|
+
const ownedChunks = chunks.map(chunk => {
|
|
105
|
+
assertPositiveSafeInteger(chunk.sequence, 'chunk.sequence');
|
|
106
|
+
assertPositiveSafeInteger(chunk.geometryGeneration, 'chunk.geometryGeneration');
|
|
107
|
+
assertPositiveSafeInteger(chunk.cols, 'chunk.cols');
|
|
108
|
+
assertPositiveSafeInteger(chunk.rows, 'chunk.rows');
|
|
109
|
+
if (chunk.sequence !== sequence + 1) {
|
|
110
|
+
throw new Error(`Ghostty checkpoint actor sequence gap: got ${chunk.sequence}, want ${sequence + 1}`);
|
|
111
|
+
}
|
|
112
|
+
if (!(chunk.data instanceof Uint8Array) || chunk.data.byteLength === 0) {
|
|
113
|
+
throw new Error('Ghostty checkpoint actor chunk data must be non-empty bytes');
|
|
114
|
+
}
|
|
115
|
+
if (chunk.geometryGeneration < geometryGeneration) {
|
|
116
|
+
throw new Error('Ghostty checkpoint actor geometry generation moved backwards');
|
|
117
|
+
}
|
|
118
|
+
if (chunk.geometryGeneration === geometryGeneration && geometryGeneration > 0 && (chunk.cols !== cols || chunk.rows !== rows)) {
|
|
119
|
+
throw new Error('Ghostty checkpoint actor geometry generation has conflicting dimensions');
|
|
120
|
+
}
|
|
121
|
+
sequence = chunk.sequence;
|
|
122
|
+
if (chunk.geometryGeneration > geometryGeneration) {
|
|
123
|
+
geometryGeneration = chunk.geometryGeneration;
|
|
124
|
+
cols = chunk.cols;
|
|
125
|
+
rows = chunk.rows;
|
|
126
|
+
}
|
|
127
|
+
addedBytes += chunk.data.byteLength;
|
|
128
|
+
return { ...chunk, data: chunk.data.slice() };
|
|
129
|
+
});
|
|
130
|
+
if (this.queuedBytes + addedBytes > this.maxQueuedBytes) {
|
|
131
|
+
throw new Error(`Ghostty checkpoint actor queue exceeds ${this.maxQueuedBytes} bytes`);
|
|
132
|
+
}
|
|
133
|
+
this.queuedThroughSequence = sequence;
|
|
134
|
+
this.geometryGeneration = geometryGeneration;
|
|
135
|
+
this.cols = cols;
|
|
136
|
+
this.rows = rows;
|
|
137
|
+
this.queuedBytes += addedBytes;
|
|
138
|
+
this.worker.postMessage({ type: 'append', chunks: ownedChunks }, ownedChunks.map(chunk => chunk.data.buffer));
|
|
139
|
+
}
|
|
140
|
+
capture(targetSequence) {
|
|
141
|
+
this.assertReady();
|
|
142
|
+
assertPositiveSafeInteger(targetSequence, 'targetSequence');
|
|
143
|
+
if (targetSequence !== this.queuedThroughSequence) {
|
|
144
|
+
return Promise.reject(new Error(`checkpoint target must equal queued sequence ${this.queuedThroughSequence}`));
|
|
145
|
+
}
|
|
146
|
+
const requestId = this.nextRequestId;
|
|
147
|
+
this.nextRequestId += 1;
|
|
148
|
+
const result = new Promise((resolve, reject) => {
|
|
149
|
+
this.captures.set(requestId, {
|
|
150
|
+
targetSequence,
|
|
151
|
+
geometryGeneration: this.geometryGeneration,
|
|
152
|
+
cols: this.cols,
|
|
153
|
+
rows: this.rows,
|
|
154
|
+
resolve,
|
|
155
|
+
reject,
|
|
156
|
+
});
|
|
157
|
+
});
|
|
158
|
+
this.worker.postMessage({ type: 'capture', requestId, targetSequence });
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
getSnapshot() {
|
|
162
|
+
return {
|
|
163
|
+
state: this.state,
|
|
164
|
+
queuedThroughSequence: this.queuedThroughSequence,
|
|
165
|
+
appliedThroughSequence: this.appliedThroughSequence,
|
|
166
|
+
queuedBytes: this.queuedBytes,
|
|
167
|
+
parserEpoch: this.parserEpoch,
|
|
168
|
+
failure: this.failure,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
dispose() {
|
|
172
|
+
if (this.state === 'disposed')
|
|
173
|
+
return;
|
|
174
|
+
const error = new Error('Ghostty checkpoint actor is disposed');
|
|
175
|
+
this.startReject?.(error);
|
|
176
|
+
this.startResolve = null;
|
|
177
|
+
this.startReject = null;
|
|
178
|
+
for (const pending of this.captures.values())
|
|
179
|
+
pending.reject(error);
|
|
180
|
+
this.captures.clear();
|
|
181
|
+
this.worker.removeEventListener('message', this.onMessage);
|
|
182
|
+
this.worker.removeEventListener('error', this.onError);
|
|
183
|
+
this.worker.terminate();
|
|
184
|
+
this.state = 'disposed';
|
|
185
|
+
}
|
|
186
|
+
handleMessage(value) {
|
|
187
|
+
if (!value || typeof value !== 'object')
|
|
188
|
+
throw new Error('checkpoint worker message must be an object');
|
|
189
|
+
const message = value;
|
|
190
|
+
if (message.type === 'ready') {
|
|
191
|
+
if (this.state !== 'starting')
|
|
192
|
+
throw new Error('unexpected checkpoint worker ready message');
|
|
193
|
+
if (message.parserEpoch !== this.parserEpoch || message.historySequence !== this.queuedThroughSequence) {
|
|
194
|
+
throw new Error('checkpoint worker initialized at stale coordinates');
|
|
195
|
+
}
|
|
196
|
+
this.state = 'ready';
|
|
197
|
+
this.startResolve?.();
|
|
198
|
+
this.startResolve = null;
|
|
199
|
+
this.startReject = null;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (message.type === 'applied') {
|
|
203
|
+
this.assertReady();
|
|
204
|
+
const throughSequence = message.throughSequence;
|
|
205
|
+
const releasedBytes = message.releasedBytes;
|
|
206
|
+
assertNonNegativeSafeInteger(throughSequence, 'applied.throughSequence');
|
|
207
|
+
assertNonNegativeSafeInteger(releasedBytes, 'applied.releasedBytes');
|
|
208
|
+
if (throughSequence < this.appliedThroughSequence || throughSequence > this.queuedThroughSequence) {
|
|
209
|
+
throw new Error('checkpoint worker applied sequence is stale or beyond queued output');
|
|
210
|
+
}
|
|
211
|
+
if (releasedBytes > this.queuedBytes) {
|
|
212
|
+
throw new Error('checkpoint worker released more bytes than queued');
|
|
213
|
+
}
|
|
214
|
+
this.appliedThroughSequence = throughSequence;
|
|
215
|
+
this.queuedBytes -= releasedBytes;
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
if (message.type === 'checkpoint') {
|
|
219
|
+
const requestId = message.requestId;
|
|
220
|
+
assertPositiveSafeInteger(requestId, 'checkpoint.requestId');
|
|
221
|
+
const pending = this.captures.get(requestId);
|
|
222
|
+
if (!pending)
|
|
223
|
+
throw new Error('checkpoint worker returned an unknown request');
|
|
224
|
+
const historySequence = message.historySequence;
|
|
225
|
+
if (historySequence !== pending.targetSequence) {
|
|
226
|
+
throw new Error(`stale checkpoint sequence: got ${historySequence}, want ${pending.targetSequence}`);
|
|
227
|
+
}
|
|
228
|
+
if (message.parserEpoch !== this.parserEpoch
|
|
229
|
+
|| message.geometryGeneration !== pending.geometryGeneration
|
|
230
|
+
|| message.cols !== pending.cols
|
|
231
|
+
|| message.rows !== pending.rows) {
|
|
232
|
+
throw new Error('stale checkpoint geometry or parser epoch');
|
|
233
|
+
}
|
|
234
|
+
if (message.formatVersion !== 1)
|
|
235
|
+
throw new Error('unsupported checkpoint format version');
|
|
236
|
+
if (typeof message.checksumSha256 !== 'string' || !SHA256_HEX.test(message.checksumSha256)) {
|
|
237
|
+
throw new Error('checkpoint checksum must be lowercase SHA-256');
|
|
238
|
+
}
|
|
239
|
+
if (typeof message.stateDigestSha256 !== 'string' || !SHA256_HEX.test(message.stateDigestSha256)) {
|
|
240
|
+
throw new Error('checkpoint state digest must be lowercase SHA-256');
|
|
241
|
+
}
|
|
242
|
+
if (!(message.bytes instanceof Uint8Array) || message.bytes.byteLength === 0) {
|
|
243
|
+
throw new Error('checkpoint worker returned empty checkpoint bytes');
|
|
244
|
+
}
|
|
245
|
+
this.captures.delete(requestId);
|
|
246
|
+
pending.resolve({
|
|
247
|
+
formatVersion: 1,
|
|
248
|
+
engineId: 'floegence-ghostty-web',
|
|
249
|
+
coveredThroughSequence: historySequence,
|
|
250
|
+
geometryGeneration: pending.geometryGeneration,
|
|
251
|
+
parserEpoch: this.parserEpoch,
|
|
252
|
+
cols: pending.cols,
|
|
253
|
+
rows: pending.rows,
|
|
254
|
+
checksumSha256: message.checksumSha256,
|
|
255
|
+
stateDigestSha256: message.stateDigestSha256,
|
|
256
|
+
bytes: message.bytes.slice(),
|
|
257
|
+
});
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
if (message.type === 'error') {
|
|
261
|
+
throw new Error(typeof message.message === 'string' ? message.message : 'Ghostty checkpoint worker failed');
|
|
262
|
+
}
|
|
263
|
+
throw new Error('unknown Ghostty checkpoint worker message');
|
|
264
|
+
}
|
|
265
|
+
assertReady() {
|
|
266
|
+
if (this.state !== 'ready') {
|
|
267
|
+
throw this.failure ?? new Error(`Ghostty checkpoint actor is not ready (${this.state})`);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
fail(error) {
|
|
271
|
+
if (this.state === 'disposed' || this.state === 'failed')
|
|
272
|
+
return;
|
|
273
|
+
this.failure = error;
|
|
274
|
+
this.state = 'failed';
|
|
275
|
+
this.startReject?.(error);
|
|
276
|
+
this.startResolve = null;
|
|
277
|
+
this.startReject = null;
|
|
278
|
+
for (const pending of this.captures.values())
|
|
279
|
+
pending.reject(error);
|
|
280
|
+
this.captures.clear();
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
export const createGhosttyCheckpointActor = (options = {}) => new GhosttyCheckpointActorImpl(options);
|
|
284
|
+
//# sourceMappingURL=GhosttyCheckpointActor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GhosttyCheckpointActor.js","sourceRoot":"","sources":["../../src/checkpoint/GhosttyCheckpointActor.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import type { GhosttyAuthoritativeCheckpoint, GhosttyCheckpointChunk } from './GhosttyCheckpointActor';
|
|
2
|
+
interface GhosttyCheckpointMetadata {
|
|
3
|
+
formatVersion: number;
|
|
4
|
+
cols: number;
|
|
5
|
+
rows: number;
|
|
6
|
+
historySequence: bigint;
|
|
7
|
+
geometryGeneration: bigint;
|
|
8
|
+
parserEpoch: bigint;
|
|
9
|
+
}
|
|
10
|
+
interface GhosttyCheckpointTerminal {
|
|
11
|
+
readonly cols: number;
|
|
12
|
+
readonly rows: number;
|
|
13
|
+
write(data: Uint8Array): void;
|
|
14
|
+
resize(cols: number, rows: number): void;
|
|
15
|
+
captureCheckpoint(coordinates?: {
|
|
16
|
+
historySequence: bigint;
|
|
17
|
+
geometryGeneration: bigint;
|
|
18
|
+
parserEpoch: bigint;
|
|
19
|
+
}): {
|
|
20
|
+
bytes: Uint8Array;
|
|
21
|
+
metadata: GhosttyCheckpointMetadata;
|
|
22
|
+
};
|
|
23
|
+
validateCheckpoint(bytes: Uint8Array): GhosttyCheckpointMetadata;
|
|
24
|
+
restoreCheckpoint(bytes: Uint8Array, expectedCoordinates?: {
|
|
25
|
+
historySequence: bigint;
|
|
26
|
+
geometryGeneration: bigint;
|
|
27
|
+
parserEpoch: bigint;
|
|
28
|
+
}): void;
|
|
29
|
+
getStateDigest(): string;
|
|
30
|
+
free(): void;
|
|
31
|
+
}
|
|
32
|
+
export interface GhosttyCheckpointRuntimeLike {
|
|
33
|
+
createTerminal(cols: number, rows: number): GhosttyCheckpointTerminal;
|
|
34
|
+
}
|
|
35
|
+
export interface GhosttyCheckpointEngineOptions {
|
|
36
|
+
cols: number;
|
|
37
|
+
rows: number;
|
|
38
|
+
parserEpoch: number;
|
|
39
|
+
initialSequence?: number;
|
|
40
|
+
checkpoint?: GhosttyAuthoritativeCheckpoint;
|
|
41
|
+
}
|
|
42
|
+
export interface GhosttyCheckpointEngineAppendResult {
|
|
43
|
+
throughSequence: number;
|
|
44
|
+
releasedBytes: number;
|
|
45
|
+
}
|
|
46
|
+
export declare class GhosttyCheckpointEngine {
|
|
47
|
+
private readonly runtime;
|
|
48
|
+
private readonly parserEpoch;
|
|
49
|
+
private terminal;
|
|
50
|
+
private queuedThroughSequence;
|
|
51
|
+
private geometryGeneration;
|
|
52
|
+
private cols;
|
|
53
|
+
private rows;
|
|
54
|
+
private disposed;
|
|
55
|
+
constructor(runtime: GhosttyCheckpointRuntimeLike, options: GhosttyCheckpointEngineOptions);
|
|
56
|
+
append(chunks: readonly GhosttyCheckpointChunk[]): GhosttyCheckpointEngineAppendResult;
|
|
57
|
+
capture(targetSequence: number): Promise<GhosttyAuthoritativeCheckpoint>;
|
|
58
|
+
dispose(): void;
|
|
59
|
+
private assertLive;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
62
|
+
//# sourceMappingURL=GhosttyCheckpointEngine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GhosttyCheckpointEngine.d.ts","sourceRoot":"","sources":["../../src/checkpoint/GhosttyCheckpointEngine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,8BAA8B,EAC9B,sBAAsB,EACvB,MAAM,0BAA0B,CAAC;AAElC,UAAU,yBAAyB;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,UAAU,yBAAyB;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACzC,iBAAiB,CAAC,WAAW,CAAC,EAAE;QAC9B,eAAe,EAAE,MAAM,CAAC;QACxB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG;QAAE,KAAK,EAAE,UAAU,CAAC;QAAC,QAAQ,EAAE,yBAAyB,CAAA;KAAE,CAAC;IAC/D,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,yBAAyB,CAAC;IACjE,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,mBAAmB,CAAC,EAAE;QACzD,eAAe,EAAE,MAAM,CAAC;QACxB,kBAAkB,EAAE,MAAM,CAAC;QAC3B,WAAW,EAAE,MAAM,CAAC;KACrB,GAAG,IAAI,CAAC;IACT,cAAc,IAAI,MAAM,CAAC;IACzB,IAAI,IAAI,IAAI,CAAC;CACd;AAED,MAAM,WAAW,4BAA4B;IAC3C,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,yBAAyB,CAAC;CACvE;AAED,MAAM,WAAW,8BAA8B;IAC7C,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,EAAE,8BAA8B,CAAC;CAC7C;AAED,MAAM,WAAW,mCAAmC;IAClD,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;CACvB;AAgCD,qBAAa,uBAAuB;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAA4B;IAC5C,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,kBAAkB,CAAS;IACnC,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,IAAI,CAAS;IACrB,OAAO,CAAC,QAAQ,CAAS;gBAEb,OAAO,EAAE,4BAA4B,EAAE,OAAO,EAAE,8BAA8B;IAoC1F,MAAM,CAAC,MAAM,EAAE,SAAS,sBAAsB,EAAE,GAAG,mCAAmC;IAyChF,OAAO,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,8BAA8B,CAAC;IA8D9E,OAAO,IAAI,IAAI;IAMf,OAAO,CAAC,UAAU;CAGnB"}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
const toBigInt = (value, field) => {
|
|
2
|
+
if (!Number.isSafeInteger(value) || value < 0)
|
|
3
|
+
throw new Error(`${field} must be a non-negative safe integer`);
|
|
4
|
+
return BigInt(value);
|
|
5
|
+
};
|
|
6
|
+
const assertDigest = (value, field) => {
|
|
7
|
+
if (!/^[0-9a-f]{64}$/.test(value))
|
|
8
|
+
throw new Error(`${field} must be lowercase SHA-256`);
|
|
9
|
+
};
|
|
10
|
+
const sha256Hex = async (bytes) => {
|
|
11
|
+
const digest = await crypto.subtle.digest('SHA-256', bytes.slice().buffer);
|
|
12
|
+
return Array.from(new Uint8Array(digest), byte => byte.toString(16).padStart(2, '0')).join('');
|
|
13
|
+
};
|
|
14
|
+
const assertMetadataMatches = (metadata, checkpoint) => {
|
|
15
|
+
if (metadata.formatVersion !== checkpoint.formatVersion
|
|
16
|
+
|| metadata.cols !== checkpoint.cols
|
|
17
|
+
|| metadata.rows !== checkpoint.rows
|
|
18
|
+
|| metadata.historySequence !== BigInt(checkpoint.coveredThroughSequence)
|
|
19
|
+
|| metadata.geometryGeneration !== BigInt(checkpoint.geometryGeneration)
|
|
20
|
+
|| metadata.parserEpoch !== BigInt(checkpoint.parserEpoch)) {
|
|
21
|
+
throw new Error('checkpoint metadata does not match its requested coordinates');
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
export class GhosttyCheckpointEngine {
|
|
25
|
+
constructor(runtime, options) {
|
|
26
|
+
this.disposed = false;
|
|
27
|
+
if (!Number.isSafeInteger(options.parserEpoch) || options.parserEpoch <= 0) {
|
|
28
|
+
throw new Error('checkpoint parser epoch must be positive');
|
|
29
|
+
}
|
|
30
|
+
if (!Number.isSafeInteger(options.cols) || options.cols <= 0 || !Number.isSafeInteger(options.rows) || options.rows <= 0) {
|
|
31
|
+
throw new Error('checkpoint engine dimensions must be positive safe integers');
|
|
32
|
+
}
|
|
33
|
+
if (!Number.isSafeInteger(options.initialSequence ?? 0) || (options.initialSequence ?? 0) < 0) {
|
|
34
|
+
throw new Error('checkpoint engine initial sequence must be a non-negative safe integer');
|
|
35
|
+
}
|
|
36
|
+
if (options.checkpoint && options.initialSequence !== undefined) {
|
|
37
|
+
throw new Error('checkpoint and initial sequence are mutually exclusive');
|
|
38
|
+
}
|
|
39
|
+
this.runtime = runtime;
|
|
40
|
+
this.parserEpoch = options.parserEpoch;
|
|
41
|
+
this.terminal = runtime.createTerminal(options.checkpoint?.cols ?? options.cols, options.checkpoint?.rows ?? options.rows);
|
|
42
|
+
this.queuedThroughSequence = options.checkpoint?.coveredThroughSequence ?? options.initialSequence ?? 0;
|
|
43
|
+
this.geometryGeneration = options.checkpoint?.geometryGeneration ?? 0;
|
|
44
|
+
this.cols = options.checkpoint?.cols ?? options.cols;
|
|
45
|
+
this.rows = options.checkpoint?.rows ?? options.rows;
|
|
46
|
+
if (options.checkpoint) {
|
|
47
|
+
assertDigest(options.checkpoint.stateDigestSha256, 'checkpoint state digest');
|
|
48
|
+
const coordinates = {
|
|
49
|
+
historySequence: toBigInt(options.checkpoint.coveredThroughSequence, 'checkpoint history sequence'),
|
|
50
|
+
geometryGeneration: toBigInt(options.checkpoint.geometryGeneration, 'checkpoint geometry generation'),
|
|
51
|
+
parserEpoch: toBigInt(options.checkpoint.parserEpoch, 'checkpoint parser epoch'),
|
|
52
|
+
};
|
|
53
|
+
const metadata = this.terminal.validateCheckpoint(options.checkpoint.bytes);
|
|
54
|
+
assertMetadataMatches(metadata, options.checkpoint);
|
|
55
|
+
this.terminal.restoreCheckpoint(options.checkpoint.bytes.slice(), coordinates);
|
|
56
|
+
if (this.terminal.getStateDigest() !== options.checkpoint.stateDigestSha256) {
|
|
57
|
+
throw new Error('checkpoint restore state digest mismatch');
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
append(chunks) {
|
|
62
|
+
this.assertLive();
|
|
63
|
+
let sequence = this.queuedThroughSequence;
|
|
64
|
+
let geometryGeneration = this.geometryGeneration;
|
|
65
|
+
let cols = this.cols;
|
|
66
|
+
let rows = this.rows;
|
|
67
|
+
let releasedBytes = 0;
|
|
68
|
+
for (const chunk of chunks) {
|
|
69
|
+
if (!Number.isSafeInteger(chunk.sequence) || chunk.sequence !== sequence + 1) {
|
|
70
|
+
throw new Error(`checkpoint engine sequence gap: got ${chunk.sequence}, want ${sequence + 1}`);
|
|
71
|
+
}
|
|
72
|
+
if (!Number.isSafeInteger(chunk.geometryGeneration) || chunk.geometryGeneration <= 0) {
|
|
73
|
+
throw new Error('checkpoint engine geometry generation is invalid');
|
|
74
|
+
}
|
|
75
|
+
if (chunk.geometryGeneration < geometryGeneration) {
|
|
76
|
+
throw new Error('checkpoint engine geometry generation moved backwards');
|
|
77
|
+
}
|
|
78
|
+
if (chunk.geometryGeneration === geometryGeneration && geometryGeneration > 0 && (chunk.cols !== cols || chunk.rows !== rows)) {
|
|
79
|
+
throw new Error('checkpoint engine geometry generation has conflicting dimensions');
|
|
80
|
+
}
|
|
81
|
+
if (chunk.geometryGeneration > geometryGeneration) {
|
|
82
|
+
if (!Number.isSafeInteger(chunk.cols) || chunk.cols <= 0 || !Number.isSafeInteger(chunk.rows) || chunk.rows <= 0) {
|
|
83
|
+
throw new Error('checkpoint engine geometry dimensions are invalid');
|
|
84
|
+
}
|
|
85
|
+
geometryGeneration = chunk.geometryGeneration;
|
|
86
|
+
cols = chunk.cols;
|
|
87
|
+
rows = chunk.rows;
|
|
88
|
+
this.terminal.resize(cols, rows);
|
|
89
|
+
}
|
|
90
|
+
if (chunk.data.byteLength === 0)
|
|
91
|
+
throw new Error('checkpoint engine output chunk is empty');
|
|
92
|
+
this.terminal.write(chunk.data.slice());
|
|
93
|
+
sequence = chunk.sequence;
|
|
94
|
+
releasedBytes += chunk.data.byteLength;
|
|
95
|
+
}
|
|
96
|
+
this.queuedThroughSequence = sequence;
|
|
97
|
+
this.geometryGeneration = geometryGeneration;
|
|
98
|
+
this.cols = cols;
|
|
99
|
+
this.rows = rows;
|
|
100
|
+
return { throughSequence: sequence, releasedBytes };
|
|
101
|
+
}
|
|
102
|
+
async capture(targetSequence) {
|
|
103
|
+
this.assertLive();
|
|
104
|
+
if (!Number.isSafeInteger(targetSequence) || targetSequence <= 0 || targetSequence !== this.queuedThroughSequence) {
|
|
105
|
+
throw new Error(`checkpoint engine target must equal queued sequence ${this.queuedThroughSequence}`);
|
|
106
|
+
}
|
|
107
|
+
const coordinates = {
|
|
108
|
+
historySequence: toBigInt(targetSequence, 'checkpoint history sequence'),
|
|
109
|
+
geometryGeneration: toBigInt(this.geometryGeneration, 'checkpoint geometry generation'),
|
|
110
|
+
parserEpoch: toBigInt(this.parserEpoch, 'checkpoint parser epoch'),
|
|
111
|
+
};
|
|
112
|
+
const captured = this.terminal.captureCheckpoint(coordinates);
|
|
113
|
+
assertMetadataMatches(captured.metadata, {
|
|
114
|
+
formatVersion: 1,
|
|
115
|
+
engineId: 'floegence-ghostty-web',
|
|
116
|
+
coveredThroughSequence: targetSequence,
|
|
117
|
+
geometryGeneration: this.geometryGeneration,
|
|
118
|
+
parserEpoch: this.parserEpoch,
|
|
119
|
+
cols: this.cols,
|
|
120
|
+
rows: this.rows,
|
|
121
|
+
checksumSha256: '0'.repeat(64),
|
|
122
|
+
stateDigestSha256: this.terminal.getStateDigest(),
|
|
123
|
+
bytes: captured.bytes,
|
|
124
|
+
});
|
|
125
|
+
const metadata = this.terminal.validateCheckpoint(captured.bytes);
|
|
126
|
+
assertMetadataMatches(metadata, {
|
|
127
|
+
formatVersion: 1,
|
|
128
|
+
engineId: 'floegence-ghostty-web',
|
|
129
|
+
coveredThroughSequence: targetSequence,
|
|
130
|
+
geometryGeneration: this.geometryGeneration,
|
|
131
|
+
parserEpoch: this.parserEpoch,
|
|
132
|
+
cols: this.cols,
|
|
133
|
+
rows: this.rows,
|
|
134
|
+
checksumSha256: '0'.repeat(64),
|
|
135
|
+
stateDigestSha256: this.terminal.getStateDigest(),
|
|
136
|
+
bytes: captured.bytes,
|
|
137
|
+
});
|
|
138
|
+
const liveDigest = this.terminal.getStateDigest();
|
|
139
|
+
assertDigest(liveDigest, 'live state digest');
|
|
140
|
+
const verifier = this.runtime.createTerminal(this.cols, this.rows);
|
|
141
|
+
try {
|
|
142
|
+
verifier.restoreCheckpoint(captured.bytes.slice(), coordinates);
|
|
143
|
+
if (verifier.getStateDigest() !== liveDigest) {
|
|
144
|
+
throw new Error('self-restore state digest mismatch');
|
|
145
|
+
}
|
|
146
|
+
const bytes = captured.bytes.slice();
|
|
147
|
+
return {
|
|
148
|
+
formatVersion: 1,
|
|
149
|
+
engineId: 'floegence-ghostty-web',
|
|
150
|
+
coveredThroughSequence: targetSequence,
|
|
151
|
+
geometryGeneration: this.geometryGeneration,
|
|
152
|
+
parserEpoch: this.parserEpoch,
|
|
153
|
+
cols: this.cols,
|
|
154
|
+
rows: this.rows,
|
|
155
|
+
checksumSha256: await sha256Hex(bytes),
|
|
156
|
+
stateDigestSha256: liveDigest,
|
|
157
|
+
bytes,
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
verifier.free();
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
dispose() {
|
|
165
|
+
if (this.disposed)
|
|
166
|
+
return;
|
|
167
|
+
this.disposed = true;
|
|
168
|
+
this.terminal.free();
|
|
169
|
+
}
|
|
170
|
+
assertLive() {
|
|
171
|
+
if (this.disposed)
|
|
172
|
+
throw new Error('checkpoint engine is disposed');
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
//# sourceMappingURL=GhosttyCheckpointEngine.js.map
|