tmux-ide 2.9.11 → 2.9.13
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/bin/cli.js +6036 -3047
- package/package.json +1 -1
- package/packages/contracts/src/index.ts +6 -0
- package/packages/contracts/src/pane-stream.ts +77 -0
- package/packages/contracts/src/terminal-delivery-binary-chunk.ts +55 -0
- package/packages/contracts/src/terminal-delivery.ts +19 -3
- package/packages/contracts/src/terminal-graphics-compressed.ts +66 -0
- package/packages/contracts/src/terminal-graphics.ts +73 -0
- package/packages/contracts/src/terminal-projection.ts +153 -0
- package/packages/contracts/src/terminal-surfaces.ts +137 -0
- package/packages/core/src/fixtures/terminal-projection-vectors.json +585 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/terminal-delivery-binary.ts +160 -0
- package/packages/core/src/terminal-delivery.ts +377 -16
- package/packages/core/src/terminal-projection-vectors.mjs +82 -0
- package/packages/core/src/terminal-projection.ts +476 -0
- package/packages/core/src/terminal-replica.ts +34 -1
- package/packages/daemon/dist/command-center/resources/saved-machine-route.js +2 -0
- package/packages/daemon/dist/command-center/resources/saved-machine-watch.js +117 -0
- package/packages/daemon/dist/command-center/server.js +13 -0
- package/packages/daemon/dist/command-center/tmux-servers.js +9 -0
- package/packages/daemon/dist/lib/daemon-embed.js +71 -4
- package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +1 -0
- package/packages/daemon/dist/lib/native-surface-context.js +96 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +10 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
- package/packages/daemon/dist/server/native-surface-provider-upgrade.js +88 -0
- package/packages/daemon/dist/terminal/graphics/compress-chunk.js +40 -0
- package/packages/daemon/dist/terminal/graphics/local-image-owner.js +330 -0
- package/packages/daemon/dist/terminal/graphics/local-image-store.js +255 -0
- package/packages/daemon/dist/terminal/graphics/native-surface-demand.js +99 -0
- package/packages/daemon/dist/terminal/graphics/native-surface-provider-channel.js +162 -0
- package/packages/daemon/dist/terminal/graphics/native-surface-provider.js +275 -0
- package/packages/daemon/dist/terminal/graphics/native-surface-viewer.js +146 -0
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +3 -0
- package/packages/daemon/dist/terminal/mirror/session-channel.js +32 -9
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +369 -16
- package/packages/daemon/dist/terminal/pane-stream/runtime.js +10 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +2 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +146 -16
- package/packages/daemon/dist/terminal/session-runtime/terminal-projection-delivery.js +32 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +3 -4
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +4 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +28 -3
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +13 -1
- package/packages/daemon/dist/tui/mirror/runtime/renderer-probe.js +195 -0
- package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -0
- package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +4 -0
- package/packages/daemon/src/command-center/resources/saved-machine-route.ts +4 -0
- package/packages/daemon/src/command-center/resources/saved-machine-watch.ts +132 -0
- package/packages/daemon/src/command-center/server.ts +18 -0
- package/packages/daemon/src/command-center/tmux-servers.ts +14 -0
- package/packages/daemon/src/lib/daemon-embed.ts +81 -3
- package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +3 -0
- package/packages/daemon/src/lib/native-surface-context.ts +123 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +12 -0
- package/packages/daemon/src/server/native-surface-provider-upgrade.ts +103 -0
- package/packages/daemon/src/terminal/graphics/compress-chunk.ts +49 -0
- package/packages/daemon/src/terminal/graphics/local-image-owner.ts +346 -0
- package/packages/daemon/src/terminal/graphics/local-image-store.ts +286 -0
- package/packages/daemon/src/terminal/graphics/native-surface-demand.ts +88 -0
- package/packages/daemon/src/terminal/graphics/native-surface-provider-channel.ts +161 -0
- package/packages/daemon/src/terminal/graphics/native-surface-provider.ts +308 -0
- package/packages/daemon/src/terminal/graphics/native-surface-viewer.ts +154 -0
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +5 -0
- package/packages/daemon/src/terminal/mirror/session-channel.ts +31 -10
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +446 -15
- package/packages/daemon/src/terminal/pane-stream/runtime.ts +14 -1
- package/packages/daemon/src/terminal/session-runtime/registry.ts +2 -0
- package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +1 -1
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +216 -22
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-observation-identity.ts +7 -4
- package/packages/daemon/src/terminal/session-runtime/terminal-projection-delivery.ts +59 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +3 -4
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +26 -4
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +20 -1
- package/packages/daemon/src/tui/mirror/runtime/renderer-probe.ts +257 -0
- package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +15 -0
- package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +4 -0
- package/packages/daemon-client/src/pane-stream-client.ts +75 -1
|
@@ -1,5 +1,27 @@
|
|
|
1
|
+
import type { SurfaceViewerDemand } from "../graphics/native-surface-demand.ts";
|
|
2
|
+
import {
|
|
3
|
+
TERMINAL_SURFACE_MAX_CONTROL_BYTES,
|
|
4
|
+
TERMINAL_SURFACE_CAPABILITY_VERSION,
|
|
5
|
+
type TerminalSurfaceReceiver,
|
|
6
|
+
} from "@tmux-ide/contracts";
|
|
7
|
+
import type {
|
|
8
|
+
NativeSurfaceProviderRegistry,
|
|
9
|
+
NativeSurfaceSession,
|
|
10
|
+
} from "../graphics/native-surface-provider.ts";
|
|
11
|
+
import { NativeSurfaceViewer } from "../graphics/native-surface-viewer.ts";
|
|
12
|
+
import { compressGraphicsChunk } from "../graphics/compress-chunk.ts";
|
|
13
|
+
import {
|
|
14
|
+
TERMINAL_GRAPHICS_COMPRESSED_CAPABILITY_VERSION,
|
|
15
|
+
TERMINAL_GRAPHICS_COMPRESSED_LOCAL_CAPABILITY_VERSION,
|
|
16
|
+
TERMINAL_PROJECTION_CAPABILITY_VERSION,
|
|
17
|
+
TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION,
|
|
18
|
+
type TerminalProjectionReceipt,
|
|
19
|
+
type TerminalProjectionMaterialize,
|
|
20
|
+
} from "@tmux-ide/contracts";
|
|
21
|
+
import type { LocalImageOwner } from "../graphics/local-image-owner.ts";
|
|
1
22
|
import {
|
|
2
23
|
PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION,
|
|
24
|
+
PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION,
|
|
3
25
|
type PaneStreamInputLifetime,
|
|
4
26
|
} from "@tmux-ide/contracts";
|
|
5
27
|
import { inflateSync } from "node:zlib";
|
|
@@ -15,6 +37,12 @@ import {
|
|
|
15
37
|
PANE_STREAM_REDEEM_PATH,
|
|
16
38
|
PANE_STREAM_WEBSOCKET_SUBPROTOCOL,
|
|
17
39
|
TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES,
|
|
40
|
+
TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION,
|
|
41
|
+
TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION,
|
|
42
|
+
TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION,
|
|
43
|
+
TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION,
|
|
44
|
+
encodeTerminalGraphicsBinary,
|
|
45
|
+
encodeTerminalDeliveryBinaryChunk,
|
|
18
46
|
TERMINAL_GRAPHICS_CELL_PIXELS_CAPABILITY_VERSION,
|
|
19
47
|
TERMINAL_GRAPHICS_PRECISE_PIXELS_CAPABILITY_VERSION,
|
|
20
48
|
TERMINAL_GRAPHICS_LEGACY_MAX_IMAGE_BYTES,
|
|
@@ -245,6 +273,8 @@ export interface PaneStreamAdmissionSnapshot {
|
|
|
245
273
|
}
|
|
246
274
|
|
|
247
275
|
export interface PaneStreamAdmissionCoordinatorOptions {
|
|
276
|
+
readonly nativeSurfaces?: NativeSurfaceProviderRegistry;
|
|
277
|
+
readonly localImages?: LocalImageOwner;
|
|
248
278
|
readonly daemonInstanceId: string;
|
|
249
279
|
readonly webSocketUrl: string;
|
|
250
280
|
readonly leaseManager: PaneStreamLeaseAuthority;
|
|
@@ -326,6 +356,8 @@ export interface SessionRuntimeTerminalDeliveryConnection {
|
|
|
326
356
|
readonly closed?: Promise<"closed" | "sink-failed">;
|
|
327
357
|
readonly negotiation: TerminalDeliveryNegotiationResult;
|
|
328
358
|
ack(ack: TerminalDeliveryAck): void;
|
|
359
|
+
projectionReceipt?(receipt: TerminalProjectionReceipt): void;
|
|
360
|
+
materialize?(request: TerminalProjectionMaterialize): void;
|
|
329
361
|
nack(nack: TerminalDeliveryNack): void;
|
|
330
362
|
setVisibility(visibility: TerminalDeliveryVisibility): void;
|
|
331
363
|
close(): Promise<void>;
|
|
@@ -388,6 +420,8 @@ function errorFrameCode(error: unknown): PaneStreamErrorFrameCode {
|
|
|
388
420
|
* ticket digests; a daemon restart makes every prior descriptor inert.
|
|
389
421
|
*/
|
|
390
422
|
export class PaneStreamAdmissionCoordinator {
|
|
423
|
+
readonly #nativeSurfaces: NativeSurfaceProviderRegistry | undefined;
|
|
424
|
+
readonly #localImages: LocalImageOwner | undefined;
|
|
391
425
|
readonly #instanceId: string;
|
|
392
426
|
readonly #webSocketUrl: string;
|
|
393
427
|
readonly #leaseManager: PaneStreamLeaseAuthority;
|
|
@@ -421,6 +455,10 @@ export class PaneStreamAdmissionCoordinator {
|
|
|
421
455
|
#shutdownPromise: Promise<void> | null = null;
|
|
422
456
|
|
|
423
457
|
constructor(options: PaneStreamAdmissionCoordinatorOptions) {
|
|
458
|
+
this.#nativeSurfaces = options.nativeSurfaces;
|
|
459
|
+
if (this.#nativeSurfaces && this.#nativeSurfaces.generation !== options.daemonInstanceId)
|
|
460
|
+
throw new Error("Native surface provider generation mismatch");
|
|
461
|
+
this.#localImages = options.localImages;
|
|
424
462
|
this.#instanceId = BindingIdSchemaZ.parse(options.daemonInstanceId);
|
|
425
463
|
this.#webSocketUrl = PaneStreamLoopbackWebSocketUrlSchemaZ.parse(options.webSocketUrl);
|
|
426
464
|
this.#leaseManager = options.leaseManager;
|
|
@@ -792,6 +830,8 @@ export class PaneStreamAdmissionCoordinator {
|
|
|
792
830
|
let live: PaneStreamLiveConnection;
|
|
793
831
|
try {
|
|
794
832
|
live = new PaneStreamLiveConnection({
|
|
833
|
+
nativeSurfaces: this.#nativeSurfaces,
|
|
834
|
+
localImages: this.#localImages,
|
|
795
835
|
clientId: `psc-${this.#clientCounter}`,
|
|
796
836
|
socket,
|
|
797
837
|
descriptor: structuredClone(descriptor),
|
|
@@ -1010,6 +1050,8 @@ class PreAuthAdmission implements PaneStreamPreAuthAdmission {
|
|
|
1010
1050
|
}
|
|
1011
1051
|
|
|
1012
1052
|
interface LiveConnectionOptions {
|
|
1053
|
+
readonly nativeSurfaces?: NativeSurfaceProviderRegistry;
|
|
1054
|
+
readonly localImages?: LocalImageOwner;
|
|
1013
1055
|
readonly clientId: string;
|
|
1014
1056
|
readonly socket: DirectTerminalSocket;
|
|
1015
1057
|
readonly descriptor: PaneStreamLeaseDescriptor;
|
|
@@ -1066,6 +1108,11 @@ interface PaneChannel {
|
|
|
1066
1108
|
graphicsServed: Map<number, string>;
|
|
1067
1109
|
graphicsState: TerminalGraphicsState | null;
|
|
1068
1110
|
graphicsZlib?: boolean;
|
|
1111
|
+
graphicsBinary?: boolean;
|
|
1112
|
+
graphicsCompressed?: boolean;
|
|
1113
|
+
binaryCells?: boolean;
|
|
1114
|
+
graphicsLocal?: boolean;
|
|
1115
|
+
graphicsInline?: Set<string>;
|
|
1069
1116
|
}
|
|
1070
1117
|
|
|
1071
1118
|
interface QueuedSend {
|
|
@@ -1097,6 +1144,15 @@ export function rawGraphicsState(state: TerminalGraphicsState): TerminalGraphics
|
|
|
1097
1144
|
const GRAPHICS_SEND_WINDOW_BYTES = 2 * 1024 * 1024;
|
|
1098
1145
|
|
|
1099
1146
|
export class PaneStreamLiveConnection {
|
|
1147
|
+
// One bounded native negotiation per redeemed socket; never reuse on reconnect.
|
|
1148
|
+
readonly #nativeSurfaces: NativeSurfaceProviderRegistry | undefined;
|
|
1149
|
+
#surfaceViewer: NativeSurfaceViewer | null = null;
|
|
1150
|
+
#surfaceDemands = new Map<string, SurfaceViewerDemand>();
|
|
1151
|
+
#surfaceNative = false;
|
|
1152
|
+
#surfaceSession: NativeSurfaceSession | null = null;
|
|
1153
|
+
#surfaceReceiver: TerminalSurfaceReceiver | null = null;
|
|
1154
|
+
readonly #localImages: LocalImageOwner | undefined;
|
|
1155
|
+
readonly #localLeases = new Map<string, { pane: string; expiresAt: number }>();
|
|
1100
1156
|
readonly #clientId: string;
|
|
1101
1157
|
readonly #socket: DirectTerminalSocket;
|
|
1102
1158
|
readonly #descriptor: PaneStreamLeaseDescriptor;
|
|
@@ -1221,6 +1277,8 @@ export class PaneStreamLiveConnection {
|
|
|
1221
1277
|
}
|
|
1222
1278
|
|
|
1223
1279
|
constructor(options: LiveConnectionOptions) {
|
|
1280
|
+
this.#nativeSurfaces = options.nativeSurfaces;
|
|
1281
|
+
this.#localImages = options.localImages;
|
|
1224
1282
|
this.#clientId = options.clientId;
|
|
1225
1283
|
this.#socket = options.socket;
|
|
1226
1284
|
this.#descriptor = options.descriptor;
|
|
@@ -1294,6 +1352,12 @@ export class PaneStreamLiveConnection {
|
|
|
1294
1352
|
)
|
|
1295
1353
|
? { inputLifetime: true as const }
|
|
1296
1354
|
: {}),
|
|
1355
|
+
...(this.#sessionRuntimeBinding &&
|
|
1356
|
+
this.#descriptor.terminalDelivery?.protocolVersions.includes(
|
|
1357
|
+
PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION,
|
|
1358
|
+
)
|
|
1359
|
+
? { inputBytes: true as const }
|
|
1360
|
+
: {}),
|
|
1297
1361
|
protocolVersion: PANE_STREAM_PROTOCOL_VERSION,
|
|
1298
1362
|
daemonInstanceId: this.#binding.daemonInstanceId,
|
|
1299
1363
|
requestId: this.#binding.requestId,
|
|
@@ -1315,6 +1379,7 @@ export class PaneStreamLiveConnection {
|
|
|
1315
1379
|
this.#sessionRuntimeBinding?.onAuthoritySnapshot?.((snapshot) =>
|
|
1316
1380
|
this.#usesExplicitAuthority ? this.#sendAuthoritySnapshot(snapshot) : undefined,
|
|
1317
1381
|
) ?? null;
|
|
1382
|
+
if (!this.#descriptor.terminalDelivery) this.#observeSurfaceDemand(this.#descriptor.panes);
|
|
1318
1383
|
void this.#subscribeAll();
|
|
1319
1384
|
}
|
|
1320
1385
|
|
|
@@ -1322,6 +1387,12 @@ export class PaneStreamLiveConnection {
|
|
|
1322
1387
|
if (this.#closed) return;
|
|
1323
1388
|
this.#recordDiagnosticLifecycle("pane-stream-terminal", code, reason);
|
|
1324
1389
|
this.#closed = true;
|
|
1390
|
+
for (const demand of this.#surfaceDemands.values()) demand.close();
|
|
1391
|
+
this.#surfaceDemands.clear();
|
|
1392
|
+
this.#surfaceViewer?.close();
|
|
1393
|
+
this.#surfaceViewer = null;
|
|
1394
|
+
this.#surfaceSession?.close();
|
|
1395
|
+
this.#surfaceSession = null;
|
|
1325
1396
|
this.#cancelDrainTick?.();
|
|
1326
1397
|
this.#cancelDrainTick = null;
|
|
1327
1398
|
// Departure force-returns every ticket within this same tick.
|
|
@@ -1336,6 +1407,14 @@ export class PaneStreamLiveConnection {
|
|
|
1336
1407
|
this.#stopAuthoritySnapshots?.();
|
|
1337
1408
|
this.#stopAuthoritySnapshots = null;
|
|
1338
1409
|
const closures: Promise<unknown>[] = [];
|
|
1410
|
+
this.#localLeases.clear();
|
|
1411
|
+
if (this.#localImages)
|
|
1412
|
+
closures.push(
|
|
1413
|
+
this.#localImages.releaseClient({
|
|
1414
|
+
generation: this.#localImages.identity.generation,
|
|
1415
|
+
client: this.#descriptor.leaseId,
|
|
1416
|
+
}),
|
|
1417
|
+
);
|
|
1339
1418
|
const layoutSubscription = this.#layoutSubscription;
|
|
1340
1419
|
this.#layoutSubscription = null;
|
|
1341
1420
|
if (layoutSubscription) closures.push(layoutSubscription.close().catch(() => undefined));
|
|
@@ -1345,6 +1424,8 @@ export class PaneStreamLiveConnection {
|
|
|
1345
1424
|
const delivery = channel.delivery;
|
|
1346
1425
|
channel.delivery = null;
|
|
1347
1426
|
channel.closed = true;
|
|
1427
|
+
this.#surfaceDemands.get(channel.semanticPaneId)?.close();
|
|
1428
|
+
this.#surfaceDemands.delete(channel.semanticPaneId);
|
|
1348
1429
|
if (sub) closures.push(sub.close().catch(() => undefined));
|
|
1349
1430
|
if (delivery) closures.push(delivery.close().catch(() => undefined));
|
|
1350
1431
|
}
|
|
@@ -1564,7 +1645,8 @@ export class PaneStreamLiveConnection {
|
|
|
1564
1645
|
if (negotiated.generation !== this.#binding.daemonInstanceId) return true;
|
|
1565
1646
|
let incarnation: string | null = null;
|
|
1566
1647
|
for (const message of pending) {
|
|
1567
|
-
if (message.type !== "terminal.delivery")
|
|
1648
|
+
if (message.type !== "terminal.delivery" && message.type !== "terminal.projection")
|
|
1649
|
+
continue;
|
|
1568
1650
|
if (
|
|
1569
1651
|
message.workspaceName !== this.#descriptor.workspaceName ||
|
|
1570
1652
|
message.semanticPaneId !== channel.semanticPaneId ||
|
|
@@ -1686,13 +1768,19 @@ export class PaneStreamLiveConnection {
|
|
|
1686
1768
|
}
|
|
1687
1769
|
|
|
1688
1770
|
async #sendTerminalDelivery(pane: string, message: TerminalDeliveryServerMessage): Promise<void> {
|
|
1689
|
-
if (message.type === "terminal.
|
|
1771
|
+
if (message.type === "terminal.projection") {
|
|
1772
|
+
const channel = this.#panes.get(pane);
|
|
1773
|
+
if (channel?.deliveryAddress) channel.deliveryAddress.incarnation = message.incarnation;
|
|
1774
|
+
if (channel) channel.binaryCells = true;
|
|
1775
|
+
this.#sendFrame(pane, { type: "terminal-projection-envelope", pane, envelope: message });
|
|
1776
|
+
} else if (message.type === "terminal.delivery") {
|
|
1690
1777
|
if (!this.#diagnosticFirstSeedObserved) {
|
|
1691
1778
|
this.#diagnosticFirstSeedObserved = true;
|
|
1692
1779
|
this.#recordDiagnosticLifecycle("pane-stream-first-seed");
|
|
1693
1780
|
}
|
|
1694
1781
|
const channel = this.#panes.get(pane);
|
|
1695
1782
|
if (channel?.deliveryAddress) channel.deliveryAddress.incarnation = message.incarnation;
|
|
1783
|
+
if (channel) channel.binaryCells = message.encoding === "semantic-binary-v1";
|
|
1696
1784
|
// The trusted lease address was fixed at delivery admission; preserve the
|
|
1697
1785
|
// exact envelope and its observation identity through transmission.
|
|
1698
1786
|
const detailedDeliveryObservation = this.#observability?.enabled === true;
|
|
@@ -1775,13 +1863,26 @@ export class PaneStreamLiveConnection {
|
|
|
1775
1863
|
}
|
|
1776
1864
|
}
|
|
1777
1865
|
} else if (message.type === "terminal.delivery.chunk") {
|
|
1778
|
-
this.#
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1866
|
+
if (this.#panes.get(pane)?.binaryCells) {
|
|
1867
|
+
this.#sendEncoded(
|
|
1868
|
+
pane,
|
|
1869
|
+
Buffer.from(
|
|
1870
|
+
encodeTerminalDeliveryBinaryChunk(
|
|
1871
|
+
{ pane, transactionId: message.transactionId, index: message.index },
|
|
1872
|
+
message.bytes,
|
|
1873
|
+
),
|
|
1874
|
+
),
|
|
1875
|
+
true,
|
|
1876
|
+
);
|
|
1877
|
+
} else {
|
|
1878
|
+
this.#sendFrame(pane, {
|
|
1879
|
+
type: "terminal-delivery-chunk",
|
|
1880
|
+
pane,
|
|
1881
|
+
transactionId: message.transactionId,
|
|
1882
|
+
index: message.index,
|
|
1883
|
+
data: Buffer.from(message.bytes).toString("base64"),
|
|
1884
|
+
});
|
|
1885
|
+
}
|
|
1785
1886
|
} else {
|
|
1786
1887
|
this.#sendFrame(pane, { type: "terminal-delivery-fault", pane, fault: message });
|
|
1787
1888
|
// Lifecycle/control truth must never queue behind bulk terminal bytes.
|
|
@@ -1792,10 +1893,27 @@ export class PaneStreamLiveConnection {
|
|
|
1792
1893
|
await this.#awaitSemanticCredit(pane);
|
|
1793
1894
|
}
|
|
1794
1895
|
|
|
1896
|
+
#observeSurfaceDemand(panes: readonly string[]): void {
|
|
1897
|
+
for (const pane of panes) {
|
|
1898
|
+
if (this.#surfaceDemands.has(pane)) continue;
|
|
1899
|
+
const demand = this.#nativeSurfaces?.demand.viewer(this.#descriptor.sessionName, [pane]);
|
|
1900
|
+
if (!demand) return;
|
|
1901
|
+
demand.native(this.#surfaceNative);
|
|
1902
|
+
this.#surfaceDemands.set(pane, demand);
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
|
|
1906
|
+
#surfaceControl(type: string): void {
|
|
1907
|
+
if (type === "terminal-surface-offer") return;
|
|
1908
|
+
this.#surfaceNative = type === "terminal-surface-peer";
|
|
1909
|
+
for (const demand of this.#surfaceDemands.values()) demand.native(this.#surfaceNative);
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1795
1912
|
/** Announce the pane's kitty graphics state; the client pulls bytes it lacks. */
|
|
1796
1913
|
#startGraphics(channel: PaneChannel): void {
|
|
1797
1914
|
const graphics = channel.delivery?.graphics;
|
|
1798
1915
|
if (!graphics) return;
|
|
1916
|
+
this.#observeSurfaceDemand([channel.semanticPaneId]);
|
|
1799
1917
|
// Renderers that cannot inflate see raw formats; their bytes are inflated
|
|
1800
1918
|
// when served.
|
|
1801
1919
|
const zlib =
|
|
@@ -1803,6 +1921,34 @@ export class PaneStreamLiveConnection {
|
|
|
1803
1921
|
TERMINAL_GRAPHICS_ZLIB_CAPABILITY_VERSION,
|
|
1804
1922
|
) === true;
|
|
1805
1923
|
channel.graphicsZlib = zlib;
|
|
1924
|
+
channel.graphicsLocal =
|
|
1925
|
+
!!this.#localImages &&
|
|
1926
|
+
this.#descriptor.terminalDelivery?.protocolVersions.some(
|
|
1927
|
+
(version) =>
|
|
1928
|
+
version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
|
|
1929
|
+
version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
|
|
1930
|
+
version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION ||
|
|
1931
|
+
version === TERMINAL_GRAPHICS_COMPRESSED_LOCAL_CAPABILITY_VERSION,
|
|
1932
|
+
) === true;
|
|
1933
|
+
channel.graphicsCompressed =
|
|
1934
|
+
this.#descriptor.terminalDelivery?.protocolVersions.some(
|
|
1935
|
+
(version) =>
|
|
1936
|
+
version === TERMINAL_GRAPHICS_COMPRESSED_CAPABILITY_VERSION ||
|
|
1937
|
+
version === TERMINAL_GRAPHICS_COMPRESSED_LOCAL_CAPABILITY_VERSION,
|
|
1938
|
+
) === true;
|
|
1939
|
+
channel.graphicsInline = new Set();
|
|
1940
|
+
channel.graphicsBinary =
|
|
1941
|
+
this.#descriptor.terminalDelivery?.protocolVersions.some(
|
|
1942
|
+
(version) =>
|
|
1943
|
+
version === TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION ||
|
|
1944
|
+
version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
|
|
1945
|
+
version === TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION ||
|
|
1946
|
+
version === TERMINAL_PROJECTION_CAPABILITY_VERSION ||
|
|
1947
|
+
version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
|
|
1948
|
+
version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION ||
|
|
1949
|
+
version === TERMINAL_GRAPHICS_COMPRESSED_CAPABILITY_VERSION ||
|
|
1950
|
+
version === TERMINAL_GRAPHICS_COMPRESSED_LOCAL_CAPABILITY_VERSION,
|
|
1951
|
+
) === true;
|
|
1806
1952
|
// Renderers that report cell pixels learn up front that this daemon
|
|
1807
1953
|
// accepts them, before any image exists.
|
|
1808
1954
|
const cellPixels =
|
|
@@ -1834,6 +1980,9 @@ export class PaneStreamLiveConnection {
|
|
|
1834
1980
|
...(zlib ? state : rawGraphicsState(state)),
|
|
1835
1981
|
...(cellPixels ? { cellPixels: true as const } : {}),
|
|
1836
1982
|
...(preciseCellPixels ? { preciseCellPixels: true as const } : {}),
|
|
1983
|
+
...(channel.graphicsBinary ? { binaryBlobs: true as const } : {}),
|
|
1984
|
+
...(channel.graphicsCompressed ? { compressedBlobs: true as const } : {}),
|
|
1985
|
+
...(channel.graphicsLocal ? { localFiles: true as const } : {}),
|
|
1837
1986
|
},
|
|
1838
1987
|
});
|
|
1839
1988
|
if (pushed.length > 0) this.#queueGraphics(channel, pushed);
|
|
@@ -1849,15 +1998,24 @@ export class PaneStreamLiveConnection {
|
|
|
1849
1998
|
placements: [],
|
|
1850
1999
|
cellPixels: true,
|
|
1851
2000
|
...(preciseCellPixels ? { preciseCellPixels: true as const } : {}),
|
|
2001
|
+
...(channel.graphicsBinary ? { binaryBlobs: true as const } : {}),
|
|
2002
|
+
...(channel.graphicsCompressed ? { compressedBlobs: true as const } : {}),
|
|
2003
|
+
...(channel.graphicsLocal ? { localFiles: true as const } : {}),
|
|
1852
2004
|
},
|
|
1853
2005
|
});
|
|
1854
2006
|
}
|
|
1855
2007
|
}
|
|
1856
2008
|
|
|
1857
|
-
#requestGraphics(pane: string, digests: readonly string[]): void {
|
|
2009
|
+
#requestGraphics(pane: string, digests: readonly string[], preferInline = false): void {
|
|
1858
2010
|
const channel = this.#panes.get(pane);
|
|
1859
2011
|
// Requests are only meaningful after an announcement; ignore stray ones.
|
|
1860
2012
|
if (!channel || channel.closed || !channel.graphicsAnnounced) return;
|
|
2013
|
+
if (preferInline && channel.graphicsLocal) {
|
|
2014
|
+
for (const digest of digests) {
|
|
2015
|
+
if ((channel.graphicsInline?.size ?? 0) < GRAPHICS_MAX_QUEUED_DIGESTS)
|
|
2016
|
+
channel.graphicsInline?.add(digest);
|
|
2017
|
+
}
|
|
2018
|
+
}
|
|
1861
2019
|
this.#queueGraphics(channel, digests);
|
|
1862
2020
|
}
|
|
1863
2021
|
|
|
@@ -1881,6 +2039,7 @@ export class PaneStreamLiveConnection {
|
|
|
1881
2039
|
try {
|
|
1882
2040
|
while (!this.#closed && !channel.closed && channel.graphicsQueue.length > 0) {
|
|
1883
2041
|
const digest = channel.graphicsQueue.shift()!;
|
|
2042
|
+
const inline = channel.graphicsInline?.delete(digest) ?? false;
|
|
1884
2043
|
const graphics = channel.delivery?.graphics;
|
|
1885
2044
|
const image = graphics?.image(digest);
|
|
1886
2045
|
if (!graphics || !image) continue;
|
|
@@ -1902,6 +2061,47 @@ export class PaneStreamLiveConnection {
|
|
|
1902
2061
|
continue;
|
|
1903
2062
|
const format = raw ? (image.format === "rgba-zlib" ? "rgba" : "rgb") : image.format;
|
|
1904
2063
|
const total = data.byteLength;
|
|
2064
|
+
if (!inline && channel.graphicsLocal && !raw && this.#localImages) {
|
|
2065
|
+
for (const [id, lease] of this.#localLeases)
|
|
2066
|
+
if (lease.expiresAt <= Date.now()) this.#localLeases.delete(id);
|
|
2067
|
+
if (this.#localLeases.size < 64) {
|
|
2068
|
+
try {
|
|
2069
|
+
const lease = await this.#localImages.acquire({
|
|
2070
|
+
generation: this.#localImages.identity.generation,
|
|
2071
|
+
client: this.#descriptor.leaseId,
|
|
2072
|
+
digest,
|
|
2073
|
+
bytes: data,
|
|
2074
|
+
});
|
|
2075
|
+
if (this.#closed || channel.closed || graphics.image(digest) !== image) {
|
|
2076
|
+
await this.#localImages.release(lease);
|
|
2077
|
+
continue;
|
|
2078
|
+
}
|
|
2079
|
+
this.#localLeases.set(lease.id, {
|
|
2080
|
+
pane: channel.semanticPaneId,
|
|
2081
|
+
expiresAt: lease.expiresAt,
|
|
2082
|
+
});
|
|
2083
|
+
this.#sendFrame(null, {
|
|
2084
|
+
type: "terminal-graphics-local",
|
|
2085
|
+
pane: channel.semanticPaneId,
|
|
2086
|
+
generation: lease.generation,
|
|
2087
|
+
leaseId: lease.id,
|
|
2088
|
+
path: lease.path,
|
|
2089
|
+
expiresAt: lease.expiresAt,
|
|
2090
|
+
blob: {
|
|
2091
|
+
digest,
|
|
2092
|
+
format,
|
|
2093
|
+
width: image.width,
|
|
2094
|
+
height: image.height,
|
|
2095
|
+
total,
|
|
2096
|
+
offset: 0,
|
|
2097
|
+
},
|
|
2098
|
+
});
|
|
2099
|
+
continue;
|
|
2100
|
+
} catch {
|
|
2101
|
+
/* Optional transport: retain the negotiated inline fallback. */
|
|
2102
|
+
}
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
1905
2105
|
for (let offset = 0; offset < total; offset += TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES) {
|
|
1906
2106
|
while ((this.#socket.bufferedAmount ?? 0) > GRAPHICS_SEND_WINDOW_BYTES) {
|
|
1907
2107
|
if (this.#closed || channel.closed) return;
|
|
@@ -1912,6 +2112,50 @@ export class PaneStreamLiveConnection {
|
|
|
1912
2112
|
// the renderer drops the partial image and shows the newer one.
|
|
1913
2113
|
if (offset > 0 && graphics.image(digest) !== image) break;
|
|
1914
2114
|
const end = Math.min(total, offset + TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES);
|
|
2115
|
+
if (channel.graphicsBinary) {
|
|
2116
|
+
const header = {
|
|
2117
|
+
pane: channel.semanticPaneId,
|
|
2118
|
+
blob: {
|
|
2119
|
+
digest,
|
|
2120
|
+
format,
|
|
2121
|
+
width: image.width,
|
|
2122
|
+
height: image.height,
|
|
2123
|
+
total,
|
|
2124
|
+
offset,
|
|
2125
|
+
},
|
|
2126
|
+
};
|
|
2127
|
+
const compressed = channel.graphicsCompressed
|
|
2128
|
+
? await compressGraphicsChunk(header, data.subarray(offset, end))
|
|
2129
|
+
: null;
|
|
2130
|
+
// Compression yields: a pane may close or replace this image meanwhile.
|
|
2131
|
+
if (this.#closed || channel.closed) return;
|
|
2132
|
+
if (graphics.image(digest) !== image) break;
|
|
2133
|
+
if (compressed) {
|
|
2134
|
+
this.#sendEncoded(null, Buffer.from(compressed), true);
|
|
2135
|
+
continue;
|
|
2136
|
+
}
|
|
2137
|
+
this.#sendEncoded(
|
|
2138
|
+
null,
|
|
2139
|
+
Buffer.from(
|
|
2140
|
+
encodeTerminalGraphicsBinary(
|
|
2141
|
+
{
|
|
2142
|
+
pane: channel.semanticPaneId,
|
|
2143
|
+
blob: {
|
|
2144
|
+
digest,
|
|
2145
|
+
format,
|
|
2146
|
+
width: image.width,
|
|
2147
|
+
height: image.height,
|
|
2148
|
+
total,
|
|
2149
|
+
offset,
|
|
2150
|
+
},
|
|
2151
|
+
},
|
|
2152
|
+
data.subarray(offset, end),
|
|
2153
|
+
),
|
|
2154
|
+
),
|
|
2155
|
+
true,
|
|
2156
|
+
);
|
|
2157
|
+
continue;
|
|
2158
|
+
}
|
|
1915
2159
|
this.#sendFrame(null, {
|
|
1916
2160
|
type: "terminal-graphics-blob",
|
|
1917
2161
|
pane: channel.semanticPaneId,
|
|
@@ -2067,6 +2311,8 @@ export class PaneStreamLiveConnection {
|
|
|
2067
2311
|
#emitClosed(channel: PaneChannel): void {
|
|
2068
2312
|
if (channel.closed) return;
|
|
2069
2313
|
channel.closed = true;
|
|
2314
|
+
this.#surfaceDemands.get(channel.semanticPaneId)?.close();
|
|
2315
|
+
this.#surfaceDemands.delete(channel.semanticPaneId);
|
|
2070
2316
|
const sub = channel.sub;
|
|
2071
2317
|
channel.sub = null;
|
|
2072
2318
|
if (sub) void sub.close().catch(() => undefined);
|
|
@@ -2245,7 +2491,12 @@ export class PaneStreamLiveConnection {
|
|
|
2245
2491
|
#sendFrame(pane: string | null, frame: Record<string, unknown>): void {
|
|
2246
2492
|
if (this.#closed || this.#socket.readyState !== WS_OPEN) return;
|
|
2247
2493
|
const encoded = JSON.stringify(frame);
|
|
2248
|
-
|
|
2494
|
+
this.#sendEncoded(pane, encoded, false);
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
#sendEncoded(pane: string | null, encoded: string | Buffer, binary: boolean): void {
|
|
2498
|
+
if (this.#closed || this.#socket.readyState !== WS_OPEN) return;
|
|
2499
|
+
const bytes = Buffer.byteLength(encoded);
|
|
2249
2500
|
if (pane !== null) {
|
|
2250
2501
|
this.#ledger.take(this.#clientId, pane, "ws-send-buffer", bytes);
|
|
2251
2502
|
if (this.#deliveryAcks) this.#ledger.take(this.#clientId, pane, "renderer-backlog", 1);
|
|
@@ -2253,7 +2504,7 @@ export class PaneStreamLiveConnection {
|
|
|
2253
2504
|
this.#sendQueue.push({ pane, remaining: bytes });
|
|
2254
2505
|
this.#sentBytesTotal += bytes;
|
|
2255
2506
|
try {
|
|
2256
|
-
this.#socket.send(encoded, { binary
|
|
2507
|
+
this.#socket.send(encoded, { binary });
|
|
2257
2508
|
} catch {
|
|
2258
2509
|
this.close(1011, "stream-unavailable");
|
|
2259
2510
|
return;
|
|
@@ -2475,6 +2726,20 @@ export class PaneStreamLiveConnection {
|
|
|
2475
2726
|
});
|
|
2476
2727
|
return;
|
|
2477
2728
|
}
|
|
2729
|
+
if (frame.type === "terminal-projection-receipt") {
|
|
2730
|
+
const channel = this.#deliveryChannel(frame.receipt);
|
|
2731
|
+
if (!channel) return;
|
|
2732
|
+
if (!channel.delivery?.projectionReceipt) return this.#failProtocol("protocol-error");
|
|
2733
|
+
channel.delivery.projectionReceipt(frame.receipt);
|
|
2734
|
+
return;
|
|
2735
|
+
}
|
|
2736
|
+
if (frame.type === "terminal-projection-materialize") {
|
|
2737
|
+
const channel = this.#deliveryChannel(frame.request);
|
|
2738
|
+
if (!channel) return;
|
|
2739
|
+
if (!channel.delivery?.materialize) return this.#failProtocol("protocol-error");
|
|
2740
|
+
channel.delivery.materialize(frame.request);
|
|
2741
|
+
return;
|
|
2742
|
+
}
|
|
2478
2743
|
if (frame.type === "terminal-delivery-ack") {
|
|
2479
2744
|
const channel = this.#deliveryChannel(frame.ack);
|
|
2480
2745
|
if (!channel) return;
|
|
@@ -2503,8 +2768,163 @@ export class PaneStreamLiveConnection {
|
|
|
2503
2768
|
this.#acceptSemanticIntent(frame.operationId, frame.intent);
|
|
2504
2769
|
return;
|
|
2505
2770
|
}
|
|
2771
|
+
if (frame.type === "terminal-surface-watch") {
|
|
2772
|
+
if (
|
|
2773
|
+
byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES ||
|
|
2774
|
+
frame.generation !== this.#sessionRuntimeBinding?.generation ||
|
|
2775
|
+
!this.#descriptor.terminalDelivery?.protocolVersions.includes(
|
|
2776
|
+
TERMINAL_SURFACE_CAPABILITY_VERSION,
|
|
2777
|
+
) ||
|
|
2778
|
+
!this.#nativeSurfaces ||
|
|
2779
|
+
this.#surfaceReceiver
|
|
2780
|
+
)
|
|
2781
|
+
return this.#failProtocol("protocol-error");
|
|
2782
|
+
if (this.#surfaceViewer) return;
|
|
2783
|
+
this.#surfaceViewer = new NativeSurfaceViewer(
|
|
2784
|
+
this.#nativeSurfaces,
|
|
2785
|
+
this.#descriptor.panes,
|
|
2786
|
+
(control) => {
|
|
2787
|
+
if (this.#closed) return false;
|
|
2788
|
+
this.#surfaceControl(control.type);
|
|
2789
|
+
this.#sendFrame(null, control);
|
|
2790
|
+
return !this.#closed;
|
|
2791
|
+
},
|
|
2792
|
+
(pane, generation) => {
|
|
2793
|
+
const channel = this.#panes.get(pane);
|
|
2794
|
+
const address = channel?.deliveryAddress;
|
|
2795
|
+
return !this.#closed &&
|
|
2796
|
+
channel &&
|
|
2797
|
+
!channel.closed &&
|
|
2798
|
+
channel.delivery &&
|
|
2799
|
+
address?.generation === generation
|
|
2800
|
+
? address.deliveryNonce
|
|
2801
|
+
: null;
|
|
2802
|
+
},
|
|
2803
|
+
);
|
|
2804
|
+
this.#surfaceViewer.start();
|
|
2805
|
+
return;
|
|
2806
|
+
}
|
|
2807
|
+
if (frame.type === "terminal-surface-receiver") {
|
|
2808
|
+
if (this.#surfaceViewer) {
|
|
2809
|
+
if (byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES || !this.#surfaceViewer.receive(frame))
|
|
2810
|
+
this.#failProtocol("protocol-error");
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
if (
|
|
2814
|
+
byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES ||
|
|
2815
|
+
!this.#descriptor.terminalDelivery ||
|
|
2816
|
+
frame.generation !== this.#sessionRuntimeBinding?.generation
|
|
2817
|
+
) {
|
|
2818
|
+
return this.#failProtocol("protocol-error");
|
|
2819
|
+
}
|
|
2820
|
+
if (this.#surfaceReceiver) {
|
|
2821
|
+
// Parsed schema order is deterministic. A duplicate cannot change the
|
|
2822
|
+
// challenge, kernel identity or endpoint, nor amplify fallback replies.
|
|
2823
|
+
if (JSON.stringify(frame) !== JSON.stringify(this.#surfaceReceiver)) {
|
|
2824
|
+
return this.#failProtocol("protocol-error");
|
|
2825
|
+
}
|
|
2826
|
+
return;
|
|
2827
|
+
}
|
|
2828
|
+
this.#surfaceReceiver = frame;
|
|
2829
|
+
if (
|
|
2830
|
+
this.#descriptor.terminalDelivery.protocolVersions.includes(
|
|
2831
|
+
TERMINAL_SURFACE_CAPABILITY_VERSION,
|
|
2832
|
+
)
|
|
2833
|
+
) {
|
|
2834
|
+
const native =
|
|
2835
|
+
this.#nativeSurfaces?.open(
|
|
2836
|
+
frame,
|
|
2837
|
+
this.#descriptor.panes,
|
|
2838
|
+
(control) => {
|
|
2839
|
+
if (this.#closed) return false;
|
|
2840
|
+
this.#surfaceControl(control.type);
|
|
2841
|
+
this.#sendFrame(null, control);
|
|
2842
|
+
return !this.#closed;
|
|
2843
|
+
},
|
|
2844
|
+
(pane, generation) => {
|
|
2845
|
+
const channel = this.#panes.get(pane);
|
|
2846
|
+
const address = channel?.deliveryAddress;
|
|
2847
|
+
if (
|
|
2848
|
+
this.#closed ||
|
|
2849
|
+
!channel ||
|
|
2850
|
+
channel.closed ||
|
|
2851
|
+
!channel.delivery ||
|
|
2852
|
+
!address ||
|
|
2853
|
+
address.generation !== generation
|
|
2854
|
+
)
|
|
2855
|
+
return null;
|
|
2856
|
+
return address.deliveryNonce;
|
|
2857
|
+
},
|
|
2858
|
+
) ?? null;
|
|
2859
|
+
if (this.#closed) {
|
|
2860
|
+
native?.close();
|
|
2861
|
+
return;
|
|
2862
|
+
}
|
|
2863
|
+
this.#surfaceSession = native;
|
|
2864
|
+
if (native) return;
|
|
2865
|
+
}
|
|
2866
|
+
// No provider/capability means no PID/slot lookup and no inferred locality.
|
|
2867
|
+
this.#sendFrame(null, {
|
|
2868
|
+
type: "terminal-surface-fallback",
|
|
2869
|
+
version: 1,
|
|
2870
|
+
generation: frame.generation,
|
|
2871
|
+
connectionNonce: frame.connectionNonce,
|
|
2872
|
+
reason: "unsupported",
|
|
2873
|
+
});
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
if (frame.type === "terminal-surface-fallback") {
|
|
2877
|
+
if (this.#surfaceViewer) {
|
|
2878
|
+
if (byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES || !this.#surfaceViewer.fallback(frame))
|
|
2879
|
+
this.#failProtocol("protocol-error");
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
if (
|
|
2883
|
+
byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES ||
|
|
2884
|
+
frame.generation !== this.#surfaceReceiver?.generation ||
|
|
2885
|
+
frame.connectionNonce !== this.#surfaceReceiver.connectionNonce
|
|
2886
|
+
) {
|
|
2887
|
+
return this.#failProtocol("protocol-error");
|
|
2888
|
+
}
|
|
2889
|
+
this.#surfaceSession?.fallback(frame.reason);
|
|
2890
|
+
return;
|
|
2891
|
+
}
|
|
2892
|
+
if (frame.type === "terminal-surface-retired") {
|
|
2893
|
+
if (this.#surfaceViewer) {
|
|
2894
|
+
if (byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES || !this.#surfaceViewer.retire(frame))
|
|
2895
|
+
this.#failProtocol("protocol-error");
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
if (
|
|
2899
|
+
byteLength <= TERMINAL_SURFACE_MAX_CONTROL_BYTES &&
|
|
2900
|
+
frame.generation === this.#surfaceReceiver?.generation &&
|
|
2901
|
+
frame.connectionNonce === this.#surfaceReceiver.connectionNonce &&
|
|
2902
|
+
this.#surfaceSession?.retire(frame.sequence)
|
|
2903
|
+
)
|
|
2904
|
+
return;
|
|
2905
|
+
// Unissued/replayed ACKs must not retire another socket's native storage.
|
|
2906
|
+
return this.#failProtocol("protocol-error");
|
|
2907
|
+
}
|
|
2506
2908
|
if (frame.type === "terminal-graphics-request") {
|
|
2507
|
-
this.#requestGraphics(frame.pane, frame.digests);
|
|
2909
|
+
this.#requestGraphics(frame.pane, frame.digests, frame.preferInline === true);
|
|
2910
|
+
return;
|
|
2911
|
+
}
|
|
2912
|
+
if (frame.type === "terminal-graphics-local-release") {
|
|
2913
|
+
const lease = this.#localLeases.get(frame.leaseId);
|
|
2914
|
+
if (
|
|
2915
|
+
lease &&
|
|
2916
|
+
lease.pane === frame.pane &&
|
|
2917
|
+
frame.generation === this.#localImages?.identity.generation
|
|
2918
|
+
) {
|
|
2919
|
+
this.#localLeases.delete(frame.leaseId);
|
|
2920
|
+
void this.#localImages
|
|
2921
|
+
?.release({
|
|
2922
|
+
generation: frame.generation,
|
|
2923
|
+
client: this.#descriptor.leaseId,
|
|
2924
|
+
id: frame.leaseId,
|
|
2925
|
+
})
|
|
2926
|
+
.catch(() => {});
|
|
2927
|
+
}
|
|
2508
2928
|
return;
|
|
2509
2929
|
}
|
|
2510
2930
|
if (frame.type === "terminal-cell-pixels") {
|
|
@@ -2720,7 +3140,7 @@ export class PaneStreamLiveConnection {
|
|
|
2720
3140
|
#acceptInput(
|
|
2721
3141
|
pane: string,
|
|
2722
3142
|
seq: number,
|
|
2723
|
-
kind: "text" | "key",
|
|
3143
|
+
kind: "text" | "key" | "bytes",
|
|
2724
3144
|
data: string,
|
|
2725
3145
|
frameBytes: number,
|
|
2726
3146
|
performanceTraceId?: string,
|
|
@@ -2730,6 +3150,16 @@ export class PaneStreamLiveConnection {
|
|
|
2730
3150
|
ingressAtSharedMicros: number | null = null,
|
|
2731
3151
|
expectedLifetime?: PaneStreamInputLifetime,
|
|
2732
3152
|
): void {
|
|
3153
|
+
if (
|
|
3154
|
+
kind === "bytes" &&
|
|
3155
|
+
(!this.#sessionRuntimeBinding ||
|
|
3156
|
+
!this.#descriptor.terminalDelivery?.protocolVersions.includes(
|
|
3157
|
+
PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION,
|
|
3158
|
+
))
|
|
3159
|
+
) {
|
|
3160
|
+
this.#failProtocol("input-rejected");
|
|
3161
|
+
return;
|
|
3162
|
+
}
|
|
2733
3163
|
if (
|
|
2734
3164
|
expectedLifetime &&
|
|
2735
3165
|
(!this.#sessionRuntimeBinding ||
|
|
@@ -2862,7 +3292,8 @@ export class PaneStreamLiveConnection {
|
|
|
2862
3292
|
expectedLifetime,
|
|
2863
3293
|
);
|
|
2864
3294
|
else if (kind === "text") channel.sub!.sendText(data);
|
|
2865
|
-
else channel.sub!.sendKey(data);
|
|
3295
|
+
else if (kind === "key") channel.sub!.sendKey(data);
|
|
3296
|
+
else throw new Error("Byte input requires semantic runtime");
|
|
2866
3297
|
let ackStartedAtMicros: number | null = null;
|
|
2867
3298
|
let ackStartedAtSharedMicros: number | null = null;
|
|
2868
3299
|
if (trace) {
|