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,5 +1,13 @@
1
+ import {
2
+ TERMINAL_PROJECTION_CAPABILITY_VERSION,
3
+ TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION,
4
+ type TerminalProjectionReceipt,
5
+ type TerminalProjectionMaterialize,
6
+ } from "@tmux-ide/contracts";
7
+ import type { LocalImageOwner } from "../graphics/local-image-owner.ts";
1
8
  import {
2
9
  PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION,
10
+ PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION,
3
11
  type PaneStreamInputLifetime,
4
12
  } from "@tmux-ide/contracts";
5
13
  import { inflateSync } from "node:zlib";
@@ -15,6 +23,12 @@ import {
15
23
  PANE_STREAM_REDEEM_PATH,
16
24
  PANE_STREAM_WEBSOCKET_SUBPROTOCOL,
17
25
  TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES,
26
+ TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION,
27
+ TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION,
28
+ TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION,
29
+ TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION,
30
+ encodeTerminalGraphicsBinary,
31
+ encodeTerminalDeliveryBinaryChunk,
18
32
  TERMINAL_GRAPHICS_CELL_PIXELS_CAPABILITY_VERSION,
19
33
  TERMINAL_GRAPHICS_PRECISE_PIXELS_CAPABILITY_VERSION,
20
34
  TERMINAL_GRAPHICS_LEGACY_MAX_IMAGE_BYTES,
@@ -245,6 +259,7 @@ export interface PaneStreamAdmissionSnapshot {
245
259
  }
246
260
 
247
261
  export interface PaneStreamAdmissionCoordinatorOptions {
262
+ readonly localImages?: LocalImageOwner;
248
263
  readonly daemonInstanceId: string;
249
264
  readonly webSocketUrl: string;
250
265
  readonly leaseManager: PaneStreamLeaseAuthority;
@@ -326,6 +341,8 @@ export interface SessionRuntimeTerminalDeliveryConnection {
326
341
  readonly closed?: Promise<"closed" | "sink-failed">;
327
342
  readonly negotiation: TerminalDeliveryNegotiationResult;
328
343
  ack(ack: TerminalDeliveryAck): void;
344
+ projectionReceipt?(receipt: TerminalProjectionReceipt): void;
345
+ materialize?(request: TerminalProjectionMaterialize): void;
329
346
  nack(nack: TerminalDeliveryNack): void;
330
347
  setVisibility(visibility: TerminalDeliveryVisibility): void;
331
348
  close(): Promise<void>;
@@ -388,6 +405,7 @@ function errorFrameCode(error: unknown): PaneStreamErrorFrameCode {
388
405
  * ticket digests; a daemon restart makes every prior descriptor inert.
389
406
  */
390
407
  export class PaneStreamAdmissionCoordinator {
408
+ readonly #localImages: LocalImageOwner | undefined;
391
409
  readonly #instanceId: string;
392
410
  readonly #webSocketUrl: string;
393
411
  readonly #leaseManager: PaneStreamLeaseAuthority;
@@ -421,6 +439,7 @@ export class PaneStreamAdmissionCoordinator {
421
439
  #shutdownPromise: Promise<void> | null = null;
422
440
 
423
441
  constructor(options: PaneStreamAdmissionCoordinatorOptions) {
442
+ this.#localImages = options.localImages;
424
443
  this.#instanceId = BindingIdSchemaZ.parse(options.daemonInstanceId);
425
444
  this.#webSocketUrl = PaneStreamLoopbackWebSocketUrlSchemaZ.parse(options.webSocketUrl);
426
445
  this.#leaseManager = options.leaseManager;
@@ -792,6 +811,7 @@ export class PaneStreamAdmissionCoordinator {
792
811
  let live: PaneStreamLiveConnection;
793
812
  try {
794
813
  live = new PaneStreamLiveConnection({
814
+ localImages: this.#localImages,
795
815
  clientId: `psc-${this.#clientCounter}`,
796
816
  socket,
797
817
  descriptor: structuredClone(descriptor),
@@ -1010,6 +1030,7 @@ class PreAuthAdmission implements PaneStreamPreAuthAdmission {
1010
1030
  }
1011
1031
 
1012
1032
  interface LiveConnectionOptions {
1033
+ readonly localImages?: LocalImageOwner;
1013
1034
  readonly clientId: string;
1014
1035
  readonly socket: DirectTerminalSocket;
1015
1036
  readonly descriptor: PaneStreamLeaseDescriptor;
@@ -1066,6 +1087,10 @@ interface PaneChannel {
1066
1087
  graphicsServed: Map<number, string>;
1067
1088
  graphicsState: TerminalGraphicsState | null;
1068
1089
  graphicsZlib?: boolean;
1090
+ graphicsBinary?: boolean;
1091
+ binaryCells?: boolean;
1092
+ graphicsLocal?: boolean;
1093
+ graphicsInline?: Set<string>;
1069
1094
  }
1070
1095
 
1071
1096
  interface QueuedSend {
@@ -1097,6 +1122,8 @@ export function rawGraphicsState(state: TerminalGraphicsState): TerminalGraphics
1097
1122
  const GRAPHICS_SEND_WINDOW_BYTES = 2 * 1024 * 1024;
1098
1123
 
1099
1124
  export class PaneStreamLiveConnection {
1125
+ readonly #localImages: LocalImageOwner | undefined;
1126
+ readonly #localLeases = new Map<string, { pane: string; expiresAt: number }>();
1100
1127
  readonly #clientId: string;
1101
1128
  readonly #socket: DirectTerminalSocket;
1102
1129
  readonly #descriptor: PaneStreamLeaseDescriptor;
@@ -1221,6 +1248,7 @@ export class PaneStreamLiveConnection {
1221
1248
  }
1222
1249
 
1223
1250
  constructor(options: LiveConnectionOptions) {
1251
+ this.#localImages = options.localImages;
1224
1252
  this.#clientId = options.clientId;
1225
1253
  this.#socket = options.socket;
1226
1254
  this.#descriptor = options.descriptor;
@@ -1294,6 +1322,12 @@ export class PaneStreamLiveConnection {
1294
1322
  )
1295
1323
  ? { inputLifetime: true as const }
1296
1324
  : {}),
1325
+ ...(this.#sessionRuntimeBinding &&
1326
+ this.#descriptor.terminalDelivery?.protocolVersions.includes(
1327
+ PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION,
1328
+ )
1329
+ ? { inputBytes: true as const }
1330
+ : {}),
1297
1331
  protocolVersion: PANE_STREAM_PROTOCOL_VERSION,
1298
1332
  daemonInstanceId: this.#binding.daemonInstanceId,
1299
1333
  requestId: this.#binding.requestId,
@@ -1336,6 +1370,14 @@ export class PaneStreamLiveConnection {
1336
1370
  this.#stopAuthoritySnapshots?.();
1337
1371
  this.#stopAuthoritySnapshots = null;
1338
1372
  const closures: Promise<unknown>[] = [];
1373
+ this.#localLeases.clear();
1374
+ if (this.#localImages)
1375
+ closures.push(
1376
+ this.#localImages.releaseClient({
1377
+ generation: this.#localImages.identity.generation,
1378
+ client: this.#descriptor.leaseId,
1379
+ }),
1380
+ );
1339
1381
  const layoutSubscription = this.#layoutSubscription;
1340
1382
  this.#layoutSubscription = null;
1341
1383
  if (layoutSubscription) closures.push(layoutSubscription.close().catch(() => undefined));
@@ -1564,7 +1606,8 @@ export class PaneStreamLiveConnection {
1564
1606
  if (negotiated.generation !== this.#binding.daemonInstanceId) return true;
1565
1607
  let incarnation: string | null = null;
1566
1608
  for (const message of pending) {
1567
- if (message.type !== "terminal.delivery") continue;
1609
+ if (message.type !== "terminal.delivery" && message.type !== "terminal.projection")
1610
+ continue;
1568
1611
  if (
1569
1612
  message.workspaceName !== this.#descriptor.workspaceName ||
1570
1613
  message.semanticPaneId !== channel.semanticPaneId ||
@@ -1686,13 +1729,19 @@ export class PaneStreamLiveConnection {
1686
1729
  }
1687
1730
 
1688
1731
  async #sendTerminalDelivery(pane: string, message: TerminalDeliveryServerMessage): Promise<void> {
1689
- if (message.type === "terminal.delivery") {
1732
+ if (message.type === "terminal.projection") {
1733
+ const channel = this.#panes.get(pane);
1734
+ if (channel?.deliveryAddress) channel.deliveryAddress.incarnation = message.incarnation;
1735
+ if (channel) channel.binaryCells = true;
1736
+ this.#sendFrame(pane, { type: "terminal-projection-envelope", pane, envelope: message });
1737
+ } else if (message.type === "terminal.delivery") {
1690
1738
  if (!this.#diagnosticFirstSeedObserved) {
1691
1739
  this.#diagnosticFirstSeedObserved = true;
1692
1740
  this.#recordDiagnosticLifecycle("pane-stream-first-seed");
1693
1741
  }
1694
1742
  const channel = this.#panes.get(pane);
1695
1743
  if (channel?.deliveryAddress) channel.deliveryAddress.incarnation = message.incarnation;
1744
+ if (channel) channel.binaryCells = message.encoding === "semantic-binary-v1";
1696
1745
  // The trusted lease address was fixed at delivery admission; preserve the
1697
1746
  // exact envelope and its observation identity through transmission.
1698
1747
  const detailedDeliveryObservation = this.#observability?.enabled === true;
@@ -1775,13 +1824,26 @@ export class PaneStreamLiveConnection {
1775
1824
  }
1776
1825
  }
1777
1826
  } else if (message.type === "terminal.delivery.chunk") {
1778
- this.#sendFrame(pane, {
1779
- type: "terminal-delivery-chunk",
1780
- pane,
1781
- transactionId: message.transactionId,
1782
- index: message.index,
1783
- data: Buffer.from(message.bytes).toString("base64"),
1784
- });
1827
+ if (this.#panes.get(pane)?.binaryCells) {
1828
+ this.#sendEncoded(
1829
+ pane,
1830
+ Buffer.from(
1831
+ encodeTerminalDeliveryBinaryChunk(
1832
+ { pane, transactionId: message.transactionId, index: message.index },
1833
+ message.bytes,
1834
+ ),
1835
+ ),
1836
+ true,
1837
+ );
1838
+ } else {
1839
+ this.#sendFrame(pane, {
1840
+ type: "terminal-delivery-chunk",
1841
+ pane,
1842
+ transactionId: message.transactionId,
1843
+ index: message.index,
1844
+ data: Buffer.from(message.bytes).toString("base64"),
1845
+ });
1846
+ }
1785
1847
  } else {
1786
1848
  this.#sendFrame(pane, { type: "terminal-delivery-fault", pane, fault: message });
1787
1849
  // Lifecycle/control truth must never queue behind bulk terminal bytes.
@@ -1803,6 +1865,25 @@ export class PaneStreamLiveConnection {
1803
1865
  TERMINAL_GRAPHICS_ZLIB_CAPABILITY_VERSION,
1804
1866
  ) === true;
1805
1867
  channel.graphicsZlib = zlib;
1868
+ channel.graphicsLocal =
1869
+ !!this.#localImages &&
1870
+ this.#descriptor.terminalDelivery?.protocolVersions.some(
1871
+ (version) =>
1872
+ version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
1873
+ version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
1874
+ version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION,
1875
+ ) === true;
1876
+ channel.graphicsInline = new Set();
1877
+ channel.graphicsBinary =
1878
+ this.#descriptor.terminalDelivery?.protocolVersions.some(
1879
+ (version) =>
1880
+ version === TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION ||
1881
+ version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
1882
+ version === TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION ||
1883
+ version === TERMINAL_PROJECTION_CAPABILITY_VERSION ||
1884
+ version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
1885
+ version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION,
1886
+ ) === true;
1806
1887
  // Renderers that report cell pixels learn up front that this daemon
1807
1888
  // accepts them, before any image exists.
1808
1889
  const cellPixels =
@@ -1834,6 +1915,8 @@ export class PaneStreamLiveConnection {
1834
1915
  ...(zlib ? state : rawGraphicsState(state)),
1835
1916
  ...(cellPixels ? { cellPixels: true as const } : {}),
1836
1917
  ...(preciseCellPixels ? { preciseCellPixels: true as const } : {}),
1918
+ ...(channel.graphicsBinary ? { binaryBlobs: true as const } : {}),
1919
+ ...(channel.graphicsLocal ? { localFiles: true as const } : {}),
1837
1920
  },
1838
1921
  });
1839
1922
  if (pushed.length > 0) this.#queueGraphics(channel, pushed);
@@ -1849,15 +1932,23 @@ export class PaneStreamLiveConnection {
1849
1932
  placements: [],
1850
1933
  cellPixels: true,
1851
1934
  ...(preciseCellPixels ? { preciseCellPixels: true as const } : {}),
1935
+ ...(channel.graphicsBinary ? { binaryBlobs: true as const } : {}),
1936
+ ...(channel.graphicsLocal ? { localFiles: true as const } : {}),
1852
1937
  },
1853
1938
  });
1854
1939
  }
1855
1940
  }
1856
1941
 
1857
- #requestGraphics(pane: string, digests: readonly string[]): void {
1942
+ #requestGraphics(pane: string, digests: readonly string[], preferInline = false): void {
1858
1943
  const channel = this.#panes.get(pane);
1859
1944
  // Requests are only meaningful after an announcement; ignore stray ones.
1860
1945
  if (!channel || channel.closed || !channel.graphicsAnnounced) return;
1946
+ if (preferInline && channel.graphicsLocal) {
1947
+ for (const digest of digests) {
1948
+ if ((channel.graphicsInline?.size ?? 0) < GRAPHICS_MAX_QUEUED_DIGESTS)
1949
+ channel.graphicsInline?.add(digest);
1950
+ }
1951
+ }
1861
1952
  this.#queueGraphics(channel, digests);
1862
1953
  }
1863
1954
 
@@ -1881,6 +1972,7 @@ export class PaneStreamLiveConnection {
1881
1972
  try {
1882
1973
  while (!this.#closed && !channel.closed && channel.graphicsQueue.length > 0) {
1883
1974
  const digest = channel.graphicsQueue.shift()!;
1975
+ const inline = channel.graphicsInline?.delete(digest) ?? false;
1884
1976
  const graphics = channel.delivery?.graphics;
1885
1977
  const image = graphics?.image(digest);
1886
1978
  if (!graphics || !image) continue;
@@ -1902,6 +1994,47 @@ export class PaneStreamLiveConnection {
1902
1994
  continue;
1903
1995
  const format = raw ? (image.format === "rgba-zlib" ? "rgba" : "rgb") : image.format;
1904
1996
  const total = data.byteLength;
1997
+ if (!inline && channel.graphicsLocal && !raw && this.#localImages) {
1998
+ for (const [id, lease] of this.#localLeases)
1999
+ if (lease.expiresAt <= Date.now()) this.#localLeases.delete(id);
2000
+ if (this.#localLeases.size < 64) {
2001
+ try {
2002
+ const lease = await this.#localImages.acquire({
2003
+ generation: this.#localImages.identity.generation,
2004
+ client: this.#descriptor.leaseId,
2005
+ digest,
2006
+ bytes: data,
2007
+ });
2008
+ if (this.#closed || channel.closed || graphics.image(digest) !== image) {
2009
+ await this.#localImages.release(lease);
2010
+ continue;
2011
+ }
2012
+ this.#localLeases.set(lease.id, {
2013
+ pane: channel.semanticPaneId,
2014
+ expiresAt: lease.expiresAt,
2015
+ });
2016
+ this.#sendFrame(null, {
2017
+ type: "terminal-graphics-local",
2018
+ pane: channel.semanticPaneId,
2019
+ generation: lease.generation,
2020
+ leaseId: lease.id,
2021
+ path: lease.path,
2022
+ expiresAt: lease.expiresAt,
2023
+ blob: {
2024
+ digest,
2025
+ format,
2026
+ width: image.width,
2027
+ height: image.height,
2028
+ total,
2029
+ offset: 0,
2030
+ },
2031
+ });
2032
+ continue;
2033
+ } catch {
2034
+ /* Optional transport: retain the negotiated inline fallback. */
2035
+ }
2036
+ }
2037
+ }
1905
2038
  for (let offset = 0; offset < total; offset += TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES) {
1906
2039
  while ((this.#socket.bufferedAmount ?? 0) > GRAPHICS_SEND_WINDOW_BYTES) {
1907
2040
  if (this.#closed || channel.closed) return;
@@ -1912,6 +2045,29 @@ export class PaneStreamLiveConnection {
1912
2045
  // the renderer drops the partial image and shows the newer one.
1913
2046
  if (offset > 0 && graphics.image(digest) !== image) break;
1914
2047
  const end = Math.min(total, offset + TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES);
2048
+ if (channel.graphicsBinary) {
2049
+ this.#sendEncoded(
2050
+ null,
2051
+ Buffer.from(
2052
+ encodeTerminalGraphicsBinary(
2053
+ {
2054
+ pane: channel.semanticPaneId,
2055
+ blob: {
2056
+ digest,
2057
+ format,
2058
+ width: image.width,
2059
+ height: image.height,
2060
+ total,
2061
+ offset,
2062
+ },
2063
+ },
2064
+ data.subarray(offset, end),
2065
+ ),
2066
+ ),
2067
+ true,
2068
+ );
2069
+ continue;
2070
+ }
1915
2071
  this.#sendFrame(null, {
1916
2072
  type: "terminal-graphics-blob",
1917
2073
  pane: channel.semanticPaneId,
@@ -2245,7 +2401,12 @@ export class PaneStreamLiveConnection {
2245
2401
  #sendFrame(pane: string | null, frame: Record<string, unknown>): void {
2246
2402
  if (this.#closed || this.#socket.readyState !== WS_OPEN) return;
2247
2403
  const encoded = JSON.stringify(frame);
2248
- const bytes = Buffer.byteLength(encoded, "utf8");
2404
+ this.#sendEncoded(pane, encoded, false);
2405
+ }
2406
+
2407
+ #sendEncoded(pane: string | null, encoded: string | Buffer, binary: boolean): void {
2408
+ if (this.#closed || this.#socket.readyState !== WS_OPEN) return;
2409
+ const bytes = Buffer.byteLength(encoded);
2249
2410
  if (pane !== null) {
2250
2411
  this.#ledger.take(this.#clientId, pane, "ws-send-buffer", bytes);
2251
2412
  if (this.#deliveryAcks) this.#ledger.take(this.#clientId, pane, "renderer-backlog", 1);
@@ -2253,7 +2414,7 @@ export class PaneStreamLiveConnection {
2253
2414
  this.#sendQueue.push({ pane, remaining: bytes });
2254
2415
  this.#sentBytesTotal += bytes;
2255
2416
  try {
2256
- this.#socket.send(encoded, { binary: false });
2417
+ this.#socket.send(encoded, { binary });
2257
2418
  } catch {
2258
2419
  this.close(1011, "stream-unavailable");
2259
2420
  return;
@@ -2475,6 +2636,20 @@ export class PaneStreamLiveConnection {
2475
2636
  });
2476
2637
  return;
2477
2638
  }
2639
+ if (frame.type === "terminal-projection-receipt") {
2640
+ const channel = this.#deliveryChannel(frame.receipt);
2641
+ if (!channel) return;
2642
+ if (!channel.delivery?.projectionReceipt) return this.#failProtocol("protocol-error");
2643
+ channel.delivery.projectionReceipt(frame.receipt);
2644
+ return;
2645
+ }
2646
+ if (frame.type === "terminal-projection-materialize") {
2647
+ const channel = this.#deliveryChannel(frame.request);
2648
+ if (!channel) return;
2649
+ if (!channel.delivery?.materialize) return this.#failProtocol("protocol-error");
2650
+ channel.delivery.materialize(frame.request);
2651
+ return;
2652
+ }
2478
2653
  if (frame.type === "terminal-delivery-ack") {
2479
2654
  const channel = this.#deliveryChannel(frame.ack);
2480
2655
  if (!channel) return;
@@ -2504,7 +2679,25 @@ export class PaneStreamLiveConnection {
2504
2679
  return;
2505
2680
  }
2506
2681
  if (frame.type === "terminal-graphics-request") {
2507
- this.#requestGraphics(frame.pane, frame.digests);
2682
+ this.#requestGraphics(frame.pane, frame.digests, frame.preferInline === true);
2683
+ return;
2684
+ }
2685
+ if (frame.type === "terminal-graphics-local-release") {
2686
+ const lease = this.#localLeases.get(frame.leaseId);
2687
+ if (
2688
+ lease &&
2689
+ lease.pane === frame.pane &&
2690
+ frame.generation === this.#localImages?.identity.generation
2691
+ ) {
2692
+ this.#localLeases.delete(frame.leaseId);
2693
+ void this.#localImages
2694
+ ?.release({
2695
+ generation: frame.generation,
2696
+ client: this.#descriptor.leaseId,
2697
+ id: frame.leaseId,
2698
+ })
2699
+ .catch(() => {});
2700
+ }
2508
2701
  return;
2509
2702
  }
2510
2703
  if (frame.type === "terminal-cell-pixels") {
@@ -2720,7 +2913,7 @@ export class PaneStreamLiveConnection {
2720
2913
  #acceptInput(
2721
2914
  pane: string,
2722
2915
  seq: number,
2723
- kind: "text" | "key",
2916
+ kind: "text" | "key" | "bytes",
2724
2917
  data: string,
2725
2918
  frameBytes: number,
2726
2919
  performanceTraceId?: string,
@@ -2730,6 +2923,16 @@ export class PaneStreamLiveConnection {
2730
2923
  ingressAtSharedMicros: number | null = null,
2731
2924
  expectedLifetime?: PaneStreamInputLifetime,
2732
2925
  ): void {
2926
+ if (
2927
+ kind === "bytes" &&
2928
+ (!this.#sessionRuntimeBinding ||
2929
+ !this.#descriptor.terminalDelivery?.protocolVersions.includes(
2930
+ PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION,
2931
+ ))
2932
+ ) {
2933
+ this.#failProtocol("input-rejected");
2934
+ return;
2935
+ }
2733
2936
  if (
2734
2937
  expectedLifetime &&
2735
2938
  (!this.#sessionRuntimeBinding ||
@@ -2862,7 +3065,8 @@ export class PaneStreamLiveConnection {
2862
3065
  expectedLifetime,
2863
3066
  );
2864
3067
  else if (kind === "text") channel.sub!.sendText(data);
2865
- else channel.sub!.sendKey(data);
3068
+ else if (kind === "key") channel.sub!.sendKey(data);
3069
+ else throw new Error("Byte input requires semantic runtime");
2866
3070
  let ackStartedAtMicros: number | null = null;
2867
3071
  let ackStartedAtSharedMicros: number | null = null;
2868
3072
  if (trace) {
@@ -1,3 +1,4 @@
1
+ import type { LocalImageOwner } from "../graphics/local-image-owner.ts";
1
2
  import type { SemanticPaneCatalog } from "../attachments/semantic-pane-catalog.ts";
2
3
  import type { SessionRuntimeRegistry } from "../session-runtime/registry.ts";
3
4
  import { SessionRuntimeTransportBinder } from "../session-runtime/transport-binding.ts";
@@ -9,6 +10,7 @@ import {
9
10
  } from "./pane-stream-websocket.ts";
10
11
 
11
12
  export interface PaneStreamRuntimeOptions {
13
+ readonly localImages?: LocalImageOwner;
12
14
  readonly daemonInstanceId: string;
13
15
  readonly webSocketUrl: string;
14
16
  /** Canonical daemon-generation session/control owner. */
@@ -38,6 +40,7 @@ export class PaneStreamRuntime {
38
40
  const transportBinder = new SessionRuntimeTransportBinder(options.sessionRuntimeRegistry);
39
41
  this.coordinator = new PaneStreamAdmissionCoordinator({
40
42
  ...options.admission,
43
+ localImages: options.localImages,
41
44
  daemonInstanceId: options.daemonInstanceId,
42
45
  webSocketUrl: options.webSocketUrl,
43
46
  leaseManager,
@@ -2472,6 +2472,8 @@ class SessionRuntimeConsumerImpl implements SessionRuntimeConsumer {
2472
2472
  negotiation: upstream.negotiation,
2473
2473
  closed: upstream.closed,
2474
2474
  ack: (ack: TerminalDeliveryAck) => upstream.ack(ack),
2475
+ ...(upstream.projectionReceipt ? { projectionReceipt: upstream.projectionReceipt } : {}),
2476
+ ...(upstream.materialize ? { materialize: upstream.materialize } : {}),
2475
2477
  nack: (nack: TerminalDeliveryNack) => upstream.nack(nack),
2476
2478
  setVisibility: (visibility: TerminalDeliveryVisibility) => upstream.setVisibility(visibility),
2477
2479
  close: async () => {
@@ -81,7 +81,7 @@ export interface SessionRuntimeStageSpan {
81
81
  readonly attemptedLegacySeedSizeCapped?: boolean;
82
82
  readonly attemptedCompactPatchBytes?: number | null;
83
83
  readonly attemptedCompactSeedBytes?: number | null;
84
- readonly selectedEncoding?: "semantic-v1" | "semantic-compact-v1";
84
+ readonly selectedEncoding?: "semantic-v1" | "semantic-compact-v1" | "semantic-binary-v1";
85
85
  readonly selectionStatus?:
86
86
  | "patch-preferred"
87
87
  | "seed-preferred"