tmux-ide 2.9.11 → 2.9.12

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 (56) hide show
  1. package/bin/cli.js +4158 -2545
  2. package/package.json +1 -1
  3. package/packages/contracts/src/index.ts +4 -0
  4. package/packages/contracts/src/pane-stream.ts +60 -0
  5. package/packages/contracts/src/terminal-delivery-binary-chunk.ts +55 -0
  6. package/packages/contracts/src/terminal-delivery.ts +17 -2
  7. package/packages/contracts/src/terminal-graphics.ts +68 -0
  8. package/packages/contracts/src/terminal-projection.ts +153 -0
  9. package/packages/core/src/fixtures/terminal-projection-vectors.json +585 -0
  10. package/packages/core/src/index.ts +2 -0
  11. package/packages/core/src/terminal-delivery-binary.ts +160 -0
  12. package/packages/core/src/terminal-delivery.ts +371 -16
  13. package/packages/core/src/terminal-projection-vectors.mjs +82 -0
  14. package/packages/core/src/terminal-projection.ts +476 -0
  15. package/packages/core/src/terminal-replica.ts +1 -1
  16. package/packages/daemon/dist/lib/daemon-embed.js +39 -3
  17. package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +1 -0
  18. package/packages/daemon/dist/lib/tmux-server-owner.js +1 -0
  19. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  20. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  21. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  22. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  23. package/packages/daemon/dist/terminal/graphics/local-image-owner.js +330 -0
  24. package/packages/daemon/dist/terminal/graphics/local-image-store.js +255 -0
  25. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +176 -16
  26. package/packages/daemon/dist/terminal/pane-stream/runtime.js +1 -0
  27. package/packages/daemon/dist/terminal/session-runtime/registry.js +2 -0
  28. package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +146 -16
  29. package/packages/daemon/dist/terminal/session-runtime/terminal-projection-delivery.js +32 -0
  30. package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +3 -4
  31. package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +4 -0
  32. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +28 -3
  33. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +13 -1
  34. package/packages/daemon/dist/tui/mirror/runtime/renderer-probe.js +195 -0
  35. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -0
  36. package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +4 -0
  37. package/packages/daemon/src/lib/daemon-embed.ts +38 -2
  38. package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +3 -0
  39. package/packages/daemon/src/lib/tmux-server-owner.ts +3 -0
  40. package/packages/daemon/src/terminal/graphics/local-image-owner.ts +346 -0
  41. package/packages/daemon/src/terminal/graphics/local-image-store.ts +286 -0
  42. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +219 -15
  43. package/packages/daemon/src/terminal/pane-stream/runtime.ts +3 -0
  44. package/packages/daemon/src/terminal/session-runtime/registry.ts +2 -0
  45. package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +1 -1
  46. package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +216 -22
  47. package/packages/daemon/src/terminal/session-runtime/terminal-delivery-observation-identity.ts +7 -4
  48. package/packages/daemon/src/terminal/session-runtime/terminal-projection-delivery.ts +59 -0
  49. package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +3 -4
  50. package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +4 -0
  51. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +26 -4
  52. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +20 -1
  53. package/packages/daemon/src/tui/mirror/runtime/renderer-probe.ts +257 -0
  54. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +15 -0
  55. package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +4 -0
  56. package/packages/daemon-client/src/pane-stream-client.ts +75 -1
@@ -1,8 +1,9 @@
1
- import { PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION, } from "@tmux-ide/contracts";
1
+ import { TERMINAL_PROJECTION_CAPABILITY_VERSION, TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION, } from "@tmux-ide/contracts";
2
+ import { PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION, PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION, } from "@tmux-ide/contracts";
2
3
  import { inflateSync } from "node:zlib";
3
4
  import { PaneStreamLayoutSnapshotFrameSchemaZ } from "@tmux-ide/contracts";
4
5
  import { z } from "zod";
5
- import { PANE_STREAM_MAX_HELD_DELTAS, PANE_STREAM_MAX_OUTPUT_BYTES, PANE_STREAM_MAX_PANES, PANE_STREAM_MAX_SEED_BYTES, PANE_STREAM_PROTOCOL_VERSION, PANE_STREAM_REDEEM_PATH, PANE_STREAM_WEBSOCKET_SUBPROTOCOL, TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES, TERMINAL_GRAPHICS_CELL_PIXELS_CAPABILITY_VERSION, TERMINAL_GRAPHICS_PRECISE_PIXELS_CAPABILITY_VERSION, TERMINAL_GRAPHICS_LEGACY_MAX_IMAGE_BYTES, TERMINAL_GRAPHICS_ZLIB_CAPABILITY_VERSION, PaneStreamClientFrameSchemaZ, PaneStreamLoopbackWebSocketUrlSchemaZ, PaneStreamRedeemFrameSchemaZ, PaneStreamServerFrameSchemaZ, sharedMonotonicMicros, } from "@tmux-ide/contracts";
6
+ import { PANE_STREAM_MAX_HELD_DELTAS, PANE_STREAM_MAX_OUTPUT_BYTES, PANE_STREAM_MAX_PANES, PANE_STREAM_MAX_SEED_BYTES, PANE_STREAM_PROTOCOL_VERSION, PANE_STREAM_REDEEM_PATH, PANE_STREAM_WEBSOCKET_SUBPROTOCOL, TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES, TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION, TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION, TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION, TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION, encodeTerminalGraphicsBinary, encodeTerminalDeliveryBinaryChunk, TERMINAL_GRAPHICS_CELL_PIXELS_CAPABILITY_VERSION, TERMINAL_GRAPHICS_PRECISE_PIXELS_CAPABILITY_VERSION, TERMINAL_GRAPHICS_LEGACY_MAX_IMAGE_BYTES, TERMINAL_GRAPHICS_ZLIB_CAPABILITY_VERSION, PaneStreamClientFrameSchemaZ, PaneStreamLoopbackWebSocketUrlSchemaZ, PaneStreamRedeemFrameSchemaZ, PaneStreamServerFrameSchemaZ, sharedMonotonicMicros, } from "@tmux-ide/contracts";
6
7
  import { canonicalOriginOrNull, digestSecret, digestsEqual, rawDataByteLength, rawDataToBuffer, safeCloseSocket, strictJsonParse, } from "../attachments/admission-util.js";
7
8
  import { SessionRuntimeControllerLeaseError } from "../session-runtime/registry.js";
8
9
  import { terminalDeliveryObservationOrdinal } from "../session-runtime/terminal-delivery-observation-identity.js";
@@ -115,6 +116,7 @@ function errorFrameCode(error) {
115
116
  * ticket digests; a daemon restart makes every prior descriptor inert.
116
117
  */
117
118
  export class PaneStreamAdmissionCoordinator {
119
+ #localImages;
118
120
  #instanceId;
119
121
  #webSocketUrl;
120
122
  #leaseManager;
@@ -145,6 +147,7 @@ export class PaneStreamAdmissionCoordinator {
145
147
  #shuttingDown = false;
146
148
  #shutdownPromise = null;
147
149
  constructor(options) {
150
+ this.#localImages = options.localImages;
148
151
  this.#instanceId = BindingIdSchemaZ.parse(options.daemonInstanceId);
149
152
  this.#webSocketUrl = PaneStreamLoopbackWebSocketUrlSchemaZ.parse(options.webSocketUrl);
150
153
  this.#leaseManager = options.leaseManager;
@@ -407,6 +410,7 @@ export class PaneStreamAdmissionCoordinator {
407
410
  let live;
408
411
  try {
409
412
  live = new PaneStreamLiveConnection({
413
+ localImages: this.#localImages,
410
414
  clientId: `psc-${this.#clientCounter}`,
411
415
  socket,
412
416
  descriptor: structuredClone(descriptor),
@@ -614,6 +618,8 @@ export function rawGraphicsState(state) {
614
618
  }
615
619
  const GRAPHICS_SEND_WINDOW_BYTES = 2 * 1024 * 1024;
616
620
  export class PaneStreamLiveConnection {
621
+ #localImages;
622
+ #localLeases = new Map();
617
623
  #clientId;
618
624
  #socket;
619
625
  #descriptor;
@@ -701,6 +707,7 @@ export class PaneStreamLiveConnection {
701
707
  return elapsed;
702
708
  }
703
709
  constructor(options) {
710
+ this.#localImages = options.localImages;
704
711
  this.#clientId = options.clientId;
705
712
  this.#socket = options.socket;
706
713
  this.#descriptor = options.descriptor;
@@ -770,6 +777,10 @@ export class PaneStreamLiveConnection {
770
777
  this.#descriptor.terminalDelivery?.protocolVersions.includes(PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION)
771
778
  ? { inputLifetime: true }
772
779
  : {}),
780
+ ...(this.#sessionRuntimeBinding &&
781
+ this.#descriptor.terminalDelivery?.protocolVersions.includes(PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION)
782
+ ? { inputBytes: true }
783
+ : {}),
773
784
  protocolVersion: PANE_STREAM_PROTOCOL_VERSION,
774
785
  daemonInstanceId: this.#binding.daemonInstanceId,
775
786
  requestId: this.#binding.requestId,
@@ -812,6 +823,12 @@ export class PaneStreamLiveConnection {
812
823
  this.#stopAuthoritySnapshots?.();
813
824
  this.#stopAuthoritySnapshots = null;
814
825
  const closures = [];
826
+ this.#localLeases.clear();
827
+ if (this.#localImages)
828
+ closures.push(this.#localImages.releaseClient({
829
+ generation: this.#localImages.identity.generation,
830
+ client: this.#descriptor.leaseId,
831
+ }));
815
832
  const layoutSubscription = this.#layoutSubscription;
816
833
  this.#layoutSubscription = null;
817
834
  if (layoutSubscription)
@@ -1024,7 +1041,7 @@ export class PaneStreamLiveConnection {
1024
1041
  return true;
1025
1042
  let incarnation = null;
1026
1043
  for (const message of pending) {
1027
- if (message.type !== "terminal.delivery")
1044
+ if (message.type !== "terminal.delivery" && message.type !== "terminal.projection")
1028
1045
  continue;
1029
1046
  if (message.workspaceName !== this.#descriptor.workspaceName ||
1030
1047
  message.semanticPaneId !== channel.semanticPaneId ||
@@ -1132,7 +1149,15 @@ export class PaneStreamLiveConnection {
1132
1149
  this.#closeIfAllPanesGone();
1133
1150
  }
1134
1151
  async #sendTerminalDelivery(pane, message) {
1135
- if (message.type === "terminal.delivery") {
1152
+ if (message.type === "terminal.projection") {
1153
+ const channel = this.#panes.get(pane);
1154
+ if (channel?.deliveryAddress)
1155
+ channel.deliveryAddress.incarnation = message.incarnation;
1156
+ if (channel)
1157
+ channel.binaryCells = true;
1158
+ this.#sendFrame(pane, { type: "terminal-projection-envelope", pane, envelope: message });
1159
+ }
1160
+ else if (message.type === "terminal.delivery") {
1136
1161
  if (!this.#diagnosticFirstSeedObserved) {
1137
1162
  this.#diagnosticFirstSeedObserved = true;
1138
1163
  this.#recordDiagnosticLifecycle("pane-stream-first-seed");
@@ -1140,6 +1165,8 @@ export class PaneStreamLiveConnection {
1140
1165
  const channel = this.#panes.get(pane);
1141
1166
  if (channel?.deliveryAddress)
1142
1167
  channel.deliveryAddress.incarnation = message.incarnation;
1168
+ if (channel)
1169
+ channel.binaryCells = message.encoding === "semantic-binary-v1";
1143
1170
  // The trusted lease address was fixed at delivery admission; preserve the
1144
1171
  // exact envelope and its observation identity through transmission.
1145
1172
  const detailedDeliveryObservation = this.#observability?.enabled === true;
@@ -1215,13 +1242,18 @@ export class PaneStreamLiveConnection {
1215
1242
  }
1216
1243
  }
1217
1244
  else if (message.type === "terminal.delivery.chunk") {
1218
- this.#sendFrame(pane, {
1219
- type: "terminal-delivery-chunk",
1220
- pane,
1221
- transactionId: message.transactionId,
1222
- index: message.index,
1223
- data: Buffer.from(message.bytes).toString("base64"),
1224
- });
1245
+ if (this.#panes.get(pane)?.binaryCells) {
1246
+ this.#sendEncoded(pane, Buffer.from(encodeTerminalDeliveryBinaryChunk({ pane, transactionId: message.transactionId, index: message.index }, message.bytes)), true);
1247
+ }
1248
+ else {
1249
+ this.#sendFrame(pane, {
1250
+ type: "terminal-delivery-chunk",
1251
+ pane,
1252
+ transactionId: message.transactionId,
1253
+ index: message.index,
1254
+ data: Buffer.from(message.bytes).toString("base64"),
1255
+ });
1256
+ }
1225
1257
  }
1226
1258
  else {
1227
1259
  this.#sendFrame(pane, { type: "terminal-delivery-fault", pane, fault: message });
@@ -1241,6 +1273,19 @@ export class PaneStreamLiveConnection {
1241
1273
  // when served.
1242
1274
  const zlib = this.#descriptor.terminalDelivery?.protocolVersions.includes(TERMINAL_GRAPHICS_ZLIB_CAPABILITY_VERSION) === true;
1243
1275
  channel.graphicsZlib = zlib;
1276
+ channel.graphicsLocal =
1277
+ !!this.#localImages &&
1278
+ this.#descriptor.terminalDelivery?.protocolVersions.some((version) => version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
1279
+ version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
1280
+ version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION) === true;
1281
+ channel.graphicsInline = new Set();
1282
+ channel.graphicsBinary =
1283
+ this.#descriptor.terminalDelivery?.protocolVersions.some((version) => version === TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION ||
1284
+ version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
1285
+ version === TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION ||
1286
+ version === TERMINAL_PROJECTION_CAPABILITY_VERSION ||
1287
+ version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
1288
+ version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION) === true;
1244
1289
  // Renderers that report cell pixels learn up front that this daemon
1245
1290
  // accepts them, before any image exists.
1246
1291
  const cellPixels = this.#descriptor.terminalDelivery?.protocolVersions.includes(TERMINAL_GRAPHICS_CELL_PIXELS_CAPABILITY_VERSION) === true;
@@ -1268,6 +1313,8 @@ export class PaneStreamLiveConnection {
1268
1313
  ...(zlib ? state : rawGraphicsState(state)),
1269
1314
  ...(cellPixels ? { cellPixels: true } : {}),
1270
1315
  ...(preciseCellPixels ? { preciseCellPixels: true } : {}),
1316
+ ...(channel.graphicsBinary ? { binaryBlobs: true } : {}),
1317
+ ...(channel.graphicsLocal ? { localFiles: true } : {}),
1271
1318
  },
1272
1319
  });
1273
1320
  if (pushed.length > 0)
@@ -1284,15 +1331,23 @@ export class PaneStreamLiveConnection {
1284
1331
  placements: [],
1285
1332
  cellPixels: true,
1286
1333
  ...(preciseCellPixels ? { preciseCellPixels: true } : {}),
1334
+ ...(channel.graphicsBinary ? { binaryBlobs: true } : {}),
1335
+ ...(channel.graphicsLocal ? { localFiles: true } : {}),
1287
1336
  },
1288
1337
  });
1289
1338
  }
1290
1339
  }
1291
- #requestGraphics(pane, digests) {
1340
+ #requestGraphics(pane, digests, preferInline = false) {
1292
1341
  const channel = this.#panes.get(pane);
1293
1342
  // Requests are only meaningful after an announcement; ignore stray ones.
1294
1343
  if (!channel || channel.closed || !channel.graphicsAnnounced)
1295
1344
  return;
1345
+ if (preferInline && channel.graphicsLocal) {
1346
+ for (const digest of digests) {
1347
+ if ((channel.graphicsInline?.size ?? 0) < GRAPHICS_MAX_QUEUED_DIGESTS)
1348
+ channel.graphicsInline?.add(digest);
1349
+ }
1350
+ }
1296
1351
  this.#queueGraphics(channel, digests);
1297
1352
  }
1298
1353
  #queueGraphics(channel, digests) {
@@ -1313,6 +1368,7 @@ export class PaneStreamLiveConnection {
1313
1368
  try {
1314
1369
  while (!this.#closed && !channel.closed && channel.graphicsQueue.length > 0) {
1315
1370
  const digest = channel.graphicsQueue.shift();
1371
+ const inline = channel.graphicsInline?.delete(digest) ?? false;
1316
1372
  const graphics = channel.delivery?.graphics;
1317
1373
  const image = graphics?.image(digest);
1318
1374
  if (!graphics || !image)
@@ -1339,6 +1395,49 @@ export class PaneStreamLiveConnection {
1339
1395
  continue;
1340
1396
  const format = raw ? (image.format === "rgba-zlib" ? "rgba" : "rgb") : image.format;
1341
1397
  const total = data.byteLength;
1398
+ if (!inline && channel.graphicsLocal && !raw && this.#localImages) {
1399
+ for (const [id, lease] of this.#localLeases)
1400
+ if (lease.expiresAt <= Date.now())
1401
+ this.#localLeases.delete(id);
1402
+ if (this.#localLeases.size < 64) {
1403
+ try {
1404
+ const lease = await this.#localImages.acquire({
1405
+ generation: this.#localImages.identity.generation,
1406
+ client: this.#descriptor.leaseId,
1407
+ digest,
1408
+ bytes: data,
1409
+ });
1410
+ if (this.#closed || channel.closed || graphics.image(digest) !== image) {
1411
+ await this.#localImages.release(lease);
1412
+ continue;
1413
+ }
1414
+ this.#localLeases.set(lease.id, {
1415
+ pane: channel.semanticPaneId,
1416
+ expiresAt: lease.expiresAt,
1417
+ });
1418
+ this.#sendFrame(null, {
1419
+ type: "terminal-graphics-local",
1420
+ pane: channel.semanticPaneId,
1421
+ generation: lease.generation,
1422
+ leaseId: lease.id,
1423
+ path: lease.path,
1424
+ expiresAt: lease.expiresAt,
1425
+ blob: {
1426
+ digest,
1427
+ format,
1428
+ width: image.width,
1429
+ height: image.height,
1430
+ total,
1431
+ offset: 0,
1432
+ },
1433
+ });
1434
+ continue;
1435
+ }
1436
+ catch {
1437
+ /* Optional transport: retain the negotiated inline fallback. */
1438
+ }
1439
+ }
1440
+ }
1342
1441
  for (let offset = 0; offset < total; offset += TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES) {
1343
1442
  while ((this.#socket.bufferedAmount ?? 0) > GRAPHICS_SEND_WINDOW_BYTES) {
1344
1443
  if (this.#closed || channel.closed)
@@ -1352,6 +1451,20 @@ export class PaneStreamLiveConnection {
1352
1451
  if (offset > 0 && graphics.image(digest) !== image)
1353
1452
  break;
1354
1453
  const end = Math.min(total, offset + TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES);
1454
+ if (channel.graphicsBinary) {
1455
+ this.#sendEncoded(null, Buffer.from(encodeTerminalGraphicsBinary({
1456
+ pane: channel.semanticPaneId,
1457
+ blob: {
1458
+ digest,
1459
+ format,
1460
+ width: image.width,
1461
+ height: image.height,
1462
+ total,
1463
+ offset,
1464
+ },
1465
+ }, data.subarray(offset, end))), true);
1466
+ continue;
1467
+ }
1355
1468
  this.#sendFrame(null, {
1356
1469
  type: "terminal-graphics-blob",
1357
1470
  pane: channel.semanticPaneId,
@@ -1671,7 +1784,12 @@ export class PaneStreamLiveConnection {
1671
1784
  if (this.#closed || this.#socket.readyState !== WS_OPEN)
1672
1785
  return;
1673
1786
  const encoded = JSON.stringify(frame);
1674
- const bytes = Buffer.byteLength(encoded, "utf8");
1787
+ this.#sendEncoded(pane, encoded, false);
1788
+ }
1789
+ #sendEncoded(pane, encoded, binary) {
1790
+ if (this.#closed || this.#socket.readyState !== WS_OPEN)
1791
+ return;
1792
+ const bytes = Buffer.byteLength(encoded);
1675
1793
  if (pane !== null) {
1676
1794
  this.#ledger.take(this.#clientId, pane, "ws-send-buffer", bytes);
1677
1795
  if (this.#deliveryAcks)
@@ -1680,7 +1798,7 @@ export class PaneStreamLiveConnection {
1680
1798
  this.#sendQueue.push({ pane, remaining: bytes });
1681
1799
  this.#sentBytesTotal += bytes;
1682
1800
  try {
1683
- this.#socket.send(encoded, { binary: false });
1801
+ this.#socket.send(encoded, { binary });
1684
1802
  }
1685
1803
  catch {
1686
1804
  this.close(1011, "stream-unavailable");
@@ -1914,6 +2032,24 @@ export class PaneStreamLiveConnection {
1914
2032
  });
1915
2033
  return;
1916
2034
  }
2035
+ if (frame.type === "terminal-projection-receipt") {
2036
+ const channel = this.#deliveryChannel(frame.receipt);
2037
+ if (!channel)
2038
+ return;
2039
+ if (!channel.delivery?.projectionReceipt)
2040
+ return this.#failProtocol("protocol-error");
2041
+ channel.delivery.projectionReceipt(frame.receipt);
2042
+ return;
2043
+ }
2044
+ if (frame.type === "terminal-projection-materialize") {
2045
+ const channel = this.#deliveryChannel(frame.request);
2046
+ if (!channel)
2047
+ return;
2048
+ if (!channel.delivery?.materialize)
2049
+ return this.#failProtocol("protocol-error");
2050
+ channel.delivery.materialize(frame.request);
2051
+ return;
2052
+ }
1917
2053
  if (frame.type === "terminal-delivery-ack") {
1918
2054
  const channel = this.#deliveryChannel(frame.ack);
1919
2055
  if (!channel)
@@ -1946,7 +2082,23 @@ export class PaneStreamLiveConnection {
1946
2082
  return;
1947
2083
  }
1948
2084
  if (frame.type === "terminal-graphics-request") {
1949
- this.#requestGraphics(frame.pane, frame.digests);
2085
+ this.#requestGraphics(frame.pane, frame.digests, frame.preferInline === true);
2086
+ return;
2087
+ }
2088
+ if (frame.type === "terminal-graphics-local-release") {
2089
+ const lease = this.#localLeases.get(frame.leaseId);
2090
+ if (lease &&
2091
+ lease.pane === frame.pane &&
2092
+ frame.generation === this.#localImages?.identity.generation) {
2093
+ this.#localLeases.delete(frame.leaseId);
2094
+ void this.#localImages
2095
+ ?.release({
2096
+ generation: frame.generation,
2097
+ client: this.#descriptor.leaseId,
2098
+ id: frame.leaseId,
2099
+ })
2100
+ .catch(() => { });
2101
+ }
1950
2102
  return;
1951
2103
  }
1952
2104
  if (frame.type === "terminal-cell-pixels") {
@@ -2127,6 +2279,12 @@ export class PaneStreamLiveConnection {
2127
2279
  this.#evaluateResume(pane);
2128
2280
  }
2129
2281
  #acceptInput(pane, seq, kind, data, frameBytes, performanceTraceId, causalProbe, ingressAtMicros = null, callbackAtSharedMicros = null, ingressAtSharedMicros = null, expectedLifetime) {
2282
+ if (kind === "bytes" &&
2283
+ (!this.#sessionRuntimeBinding ||
2284
+ !this.#descriptor.terminalDelivery?.protocolVersions.includes(PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION))) {
2285
+ this.#failProtocol("input-rejected");
2286
+ return;
2287
+ }
2130
2288
  if (expectedLifetime &&
2131
2289
  (!this.#sessionRuntimeBinding ||
2132
2290
  !this.#descriptor.terminalDelivery?.protocolVersions.includes(PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION))) {
@@ -2227,8 +2385,10 @@ export class PaneStreamLiveConnection {
2227
2385
  : undefined, expectedLifetime);
2228
2386
  else if (kind === "text")
2229
2387
  channel.sub.sendText(data);
2230
- else
2388
+ else if (kind === "key")
2231
2389
  channel.sub.sendKey(data);
2390
+ else
2391
+ throw new Error("Byte input requires semantic runtime");
2232
2392
  let ackStartedAtMicros = null;
2233
2393
  let ackStartedAtSharedMicros = null;
2234
2394
  if (trace) {
@@ -16,6 +16,7 @@ export class PaneStreamRuntime {
16
16
  const transportBinder = new SessionRuntimeTransportBinder(options.sessionRuntimeRegistry);
17
17
  this.coordinator = new PaneStreamAdmissionCoordinator({
18
18
  ...options.admission,
19
+ localImages: options.localImages,
19
20
  daemonInstanceId: options.daemonInstanceId,
20
21
  webSocketUrl: options.webSocketUrl,
21
22
  leaseManager,
@@ -1653,6 +1653,8 @@ class SessionRuntimeConsumerImpl {
1653
1653
  negotiation: upstream.negotiation,
1654
1654
  closed: upstream.closed,
1655
1655
  ack: (ack) => upstream.ack(ack),
1656
+ ...(upstream.projectionReceipt ? { projectionReceipt: upstream.projectionReceipt } : {}),
1657
+ ...(upstream.materialize ? { materialize: upstream.materialize } : {}),
1656
1658
  nack: (nack) => upstream.nack(nack),
1657
1659
  setVisibility: (visibility) => upstream.setVisibility(visibility),
1658
1660
  close: async () => {