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.
Files changed (84) hide show
  1. package/bin/cli.js +6036 -3047
  2. package/package.json +1 -1
  3. package/packages/contracts/src/index.ts +6 -0
  4. package/packages/contracts/src/pane-stream.ts +77 -0
  5. package/packages/contracts/src/terminal-delivery-binary-chunk.ts +55 -0
  6. package/packages/contracts/src/terminal-delivery.ts +19 -3
  7. package/packages/contracts/src/terminal-graphics-compressed.ts +66 -0
  8. package/packages/contracts/src/terminal-graphics.ts +73 -0
  9. package/packages/contracts/src/terminal-projection.ts +153 -0
  10. package/packages/contracts/src/terminal-surfaces.ts +137 -0
  11. package/packages/core/src/fixtures/terminal-projection-vectors.json +585 -0
  12. package/packages/core/src/index.ts +2 -0
  13. package/packages/core/src/terminal-delivery-binary.ts +160 -0
  14. package/packages/core/src/terminal-delivery.ts +377 -16
  15. package/packages/core/src/terminal-projection-vectors.mjs +82 -0
  16. package/packages/core/src/terminal-projection.ts +476 -0
  17. package/packages/core/src/terminal-replica.ts +34 -1
  18. package/packages/daemon/dist/command-center/resources/saved-machine-route.js +2 -0
  19. package/packages/daemon/dist/command-center/resources/saved-machine-watch.js +117 -0
  20. package/packages/daemon/dist/command-center/server.js +13 -0
  21. package/packages/daemon/dist/command-center/tmux-servers.js +9 -0
  22. package/packages/daemon/dist/lib/daemon-embed.js +71 -4
  23. package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +1 -0
  24. package/packages/daemon/dist/lib/native-surface-context.js +96 -0
  25. package/packages/daemon/dist/lib/tmux-server-owner.js +10 -0
  26. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  27. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  28. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  29. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  30. package/packages/daemon/dist/server/native-surface-provider-upgrade.js +88 -0
  31. package/packages/daemon/dist/terminal/graphics/compress-chunk.js +40 -0
  32. package/packages/daemon/dist/terminal/graphics/local-image-owner.js +330 -0
  33. package/packages/daemon/dist/terminal/graphics/local-image-store.js +255 -0
  34. package/packages/daemon/dist/terminal/graphics/native-surface-demand.js +99 -0
  35. package/packages/daemon/dist/terminal/graphics/native-surface-provider-channel.js +162 -0
  36. package/packages/daemon/dist/terminal/graphics/native-surface-provider.js +275 -0
  37. package/packages/daemon/dist/terminal/graphics/native-surface-viewer.js +146 -0
  38. package/packages/daemon/dist/terminal/mirror/mirror-service.js +3 -0
  39. package/packages/daemon/dist/terminal/mirror/session-channel.js +32 -9
  40. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +369 -16
  41. package/packages/daemon/dist/terminal/pane-stream/runtime.js +10 -0
  42. package/packages/daemon/dist/terminal/session-runtime/registry.js +2 -0
  43. package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +146 -16
  44. package/packages/daemon/dist/terminal/session-runtime/terminal-projection-delivery.js +32 -0
  45. package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +3 -4
  46. package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +4 -0
  47. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +28 -3
  48. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +13 -1
  49. package/packages/daemon/dist/tui/mirror/runtime/renderer-probe.js +195 -0
  50. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -0
  51. package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +4 -0
  52. package/packages/daemon/src/command-center/resources/saved-machine-route.ts +4 -0
  53. package/packages/daemon/src/command-center/resources/saved-machine-watch.ts +132 -0
  54. package/packages/daemon/src/command-center/server.ts +18 -0
  55. package/packages/daemon/src/command-center/tmux-servers.ts +14 -0
  56. package/packages/daemon/src/lib/daemon-embed.ts +81 -3
  57. package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +3 -0
  58. package/packages/daemon/src/lib/native-surface-context.ts +123 -0
  59. package/packages/daemon/src/lib/tmux-server-owner.ts +12 -0
  60. package/packages/daemon/src/server/native-surface-provider-upgrade.ts +103 -0
  61. package/packages/daemon/src/terminal/graphics/compress-chunk.ts +49 -0
  62. package/packages/daemon/src/terminal/graphics/local-image-owner.ts +346 -0
  63. package/packages/daemon/src/terminal/graphics/local-image-store.ts +286 -0
  64. package/packages/daemon/src/terminal/graphics/native-surface-demand.ts +88 -0
  65. package/packages/daemon/src/terminal/graphics/native-surface-provider-channel.ts +161 -0
  66. package/packages/daemon/src/terminal/graphics/native-surface-provider.ts +308 -0
  67. package/packages/daemon/src/terminal/graphics/native-surface-viewer.ts +154 -0
  68. package/packages/daemon/src/terminal/mirror/mirror-service.ts +5 -0
  69. package/packages/daemon/src/terminal/mirror/session-channel.ts +31 -10
  70. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +446 -15
  71. package/packages/daemon/src/terminal/pane-stream/runtime.ts +14 -1
  72. package/packages/daemon/src/terminal/session-runtime/registry.ts +2 -0
  73. package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +1 -1
  74. package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +216 -22
  75. package/packages/daemon/src/terminal/session-runtime/terminal-delivery-observation-identity.ts +7 -4
  76. package/packages/daemon/src/terminal/session-runtime/terminal-projection-delivery.ts +59 -0
  77. package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +3 -4
  78. package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +4 -0
  79. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +26 -4
  80. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +20 -1
  81. package/packages/daemon/src/tui/mirror/runtime/renderer-probe.ts +257 -0
  82. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +15 -0
  83. package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +4 -0
  84. package/packages/daemon-client/src/pane-stream-client.ts +75 -1
@@ -1,8 +1,12 @@
1
- import { PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION, } from "@tmux-ide/contracts";
1
+ import { TERMINAL_SURFACE_MAX_CONTROL_BYTES, TERMINAL_SURFACE_CAPABILITY_VERSION, } from "@tmux-ide/contracts";
2
+ import { NativeSurfaceViewer } from "../graphics/native-surface-viewer.js";
3
+ import { compressGraphicsChunk } from "../graphics/compress-chunk.js";
4
+ import { TERMINAL_GRAPHICS_COMPRESSED_CAPABILITY_VERSION, TERMINAL_GRAPHICS_COMPRESSED_LOCAL_CAPABILITY_VERSION, TERMINAL_PROJECTION_CAPABILITY_VERSION, TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION, } from "@tmux-ide/contracts";
5
+ import { PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION, PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION, } from "@tmux-ide/contracts";
2
6
  import { inflateSync } from "node:zlib";
3
7
  import { PaneStreamLayoutSnapshotFrameSchemaZ } from "@tmux-ide/contracts";
4
8
  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";
9
+ 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
10
  import { canonicalOriginOrNull, digestSecret, digestsEqual, rawDataByteLength, rawDataToBuffer, safeCloseSocket, strictJsonParse, } from "../attachments/admission-util.js";
7
11
  import { SessionRuntimeControllerLeaseError } from "../session-runtime/registry.js";
8
12
  import { terminalDeliveryObservationOrdinal } from "../session-runtime/terminal-delivery-observation-identity.js";
@@ -115,6 +119,8 @@ function errorFrameCode(error) {
115
119
  * ticket digests; a daemon restart makes every prior descriptor inert.
116
120
  */
117
121
  export class PaneStreamAdmissionCoordinator {
122
+ #nativeSurfaces;
123
+ #localImages;
118
124
  #instanceId;
119
125
  #webSocketUrl;
120
126
  #leaseManager;
@@ -145,6 +151,10 @@ export class PaneStreamAdmissionCoordinator {
145
151
  #shuttingDown = false;
146
152
  #shutdownPromise = null;
147
153
  constructor(options) {
154
+ this.#nativeSurfaces = options.nativeSurfaces;
155
+ if (this.#nativeSurfaces && this.#nativeSurfaces.generation !== options.daemonInstanceId)
156
+ throw new Error("Native surface provider generation mismatch");
157
+ this.#localImages = options.localImages;
148
158
  this.#instanceId = BindingIdSchemaZ.parse(options.daemonInstanceId);
149
159
  this.#webSocketUrl = PaneStreamLoopbackWebSocketUrlSchemaZ.parse(options.webSocketUrl);
150
160
  this.#leaseManager = options.leaseManager;
@@ -407,6 +417,8 @@ export class PaneStreamAdmissionCoordinator {
407
417
  let live;
408
418
  try {
409
419
  live = new PaneStreamLiveConnection({
420
+ nativeSurfaces: this.#nativeSurfaces,
421
+ localImages: this.#localImages,
410
422
  clientId: `psc-${this.#clientCounter}`,
411
423
  socket,
412
424
  descriptor: structuredClone(descriptor),
@@ -614,6 +626,15 @@ export function rawGraphicsState(state) {
614
626
  }
615
627
  const GRAPHICS_SEND_WINDOW_BYTES = 2 * 1024 * 1024;
616
628
  export class PaneStreamLiveConnection {
629
+ // One bounded native negotiation per redeemed socket; never reuse on reconnect.
630
+ #nativeSurfaces;
631
+ #surfaceViewer = null;
632
+ #surfaceDemands = new Map();
633
+ #surfaceNative = false;
634
+ #surfaceSession = null;
635
+ #surfaceReceiver = null;
636
+ #localImages;
637
+ #localLeases = new Map();
617
638
  #clientId;
618
639
  #socket;
619
640
  #descriptor;
@@ -701,6 +722,8 @@ export class PaneStreamLiveConnection {
701
722
  return elapsed;
702
723
  }
703
724
  constructor(options) {
725
+ this.#nativeSurfaces = options.nativeSurfaces;
726
+ this.#localImages = options.localImages;
704
727
  this.#clientId = options.clientId;
705
728
  this.#socket = options.socket;
706
729
  this.#descriptor = options.descriptor;
@@ -770,6 +793,10 @@ export class PaneStreamLiveConnection {
770
793
  this.#descriptor.terminalDelivery?.protocolVersions.includes(PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION)
771
794
  ? { inputLifetime: true }
772
795
  : {}),
796
+ ...(this.#sessionRuntimeBinding &&
797
+ this.#descriptor.terminalDelivery?.protocolVersions.includes(PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION)
798
+ ? { inputBytes: true }
799
+ : {}),
773
800
  protocolVersion: PANE_STREAM_PROTOCOL_VERSION,
774
801
  daemonInstanceId: this.#binding.daemonInstanceId,
775
802
  requestId: this.#binding.requestId,
@@ -790,6 +817,8 @@ export class PaneStreamLiveConnection {
790
817
  }
791
818
  this.#stopAuthoritySnapshots =
792
819
  this.#sessionRuntimeBinding?.onAuthoritySnapshot?.((snapshot) => this.#usesExplicitAuthority ? this.#sendAuthoritySnapshot(snapshot) : undefined) ?? null;
820
+ if (!this.#descriptor.terminalDelivery)
821
+ this.#observeSurfaceDemand(this.#descriptor.panes);
793
822
  void this.#subscribeAll();
794
823
  }
795
824
  close(code = 1000, reason = "stream-closed") {
@@ -797,6 +826,13 @@ export class PaneStreamLiveConnection {
797
826
  return;
798
827
  this.#recordDiagnosticLifecycle("pane-stream-terminal", code, reason);
799
828
  this.#closed = true;
829
+ for (const demand of this.#surfaceDemands.values())
830
+ demand.close();
831
+ this.#surfaceDemands.clear();
832
+ this.#surfaceViewer?.close();
833
+ this.#surfaceViewer = null;
834
+ this.#surfaceSession?.close();
835
+ this.#surfaceSession = null;
800
836
  this.#cancelDrainTick?.();
801
837
  this.#cancelDrainTick = null;
802
838
  // Departure force-returns every ticket within this same tick.
@@ -812,6 +848,12 @@ export class PaneStreamLiveConnection {
812
848
  this.#stopAuthoritySnapshots?.();
813
849
  this.#stopAuthoritySnapshots = null;
814
850
  const closures = [];
851
+ this.#localLeases.clear();
852
+ if (this.#localImages)
853
+ closures.push(this.#localImages.releaseClient({
854
+ generation: this.#localImages.identity.generation,
855
+ client: this.#descriptor.leaseId,
856
+ }));
815
857
  const layoutSubscription = this.#layoutSubscription;
816
858
  this.#layoutSubscription = null;
817
859
  if (layoutSubscription)
@@ -822,6 +864,8 @@ export class PaneStreamLiveConnection {
822
864
  const delivery = channel.delivery;
823
865
  channel.delivery = null;
824
866
  channel.closed = true;
867
+ this.#surfaceDemands.get(channel.semanticPaneId)?.close();
868
+ this.#surfaceDemands.delete(channel.semanticPaneId);
825
869
  if (sub)
826
870
  closures.push(sub.close().catch(() => undefined));
827
871
  if (delivery)
@@ -1024,7 +1068,7 @@ export class PaneStreamLiveConnection {
1024
1068
  return true;
1025
1069
  let incarnation = null;
1026
1070
  for (const message of pending) {
1027
- if (message.type !== "terminal.delivery")
1071
+ if (message.type !== "terminal.delivery" && message.type !== "terminal.projection")
1028
1072
  continue;
1029
1073
  if (message.workspaceName !== this.#descriptor.workspaceName ||
1030
1074
  message.semanticPaneId !== channel.semanticPaneId ||
@@ -1132,7 +1176,15 @@ export class PaneStreamLiveConnection {
1132
1176
  this.#closeIfAllPanesGone();
1133
1177
  }
1134
1178
  async #sendTerminalDelivery(pane, message) {
1135
- if (message.type === "terminal.delivery") {
1179
+ if (message.type === "terminal.projection") {
1180
+ const channel = this.#panes.get(pane);
1181
+ if (channel?.deliveryAddress)
1182
+ channel.deliveryAddress.incarnation = message.incarnation;
1183
+ if (channel)
1184
+ channel.binaryCells = true;
1185
+ this.#sendFrame(pane, { type: "terminal-projection-envelope", pane, envelope: message });
1186
+ }
1187
+ else if (message.type === "terminal.delivery") {
1136
1188
  if (!this.#diagnosticFirstSeedObserved) {
1137
1189
  this.#diagnosticFirstSeedObserved = true;
1138
1190
  this.#recordDiagnosticLifecycle("pane-stream-first-seed");
@@ -1140,6 +1192,8 @@ export class PaneStreamLiveConnection {
1140
1192
  const channel = this.#panes.get(pane);
1141
1193
  if (channel?.deliveryAddress)
1142
1194
  channel.deliveryAddress.incarnation = message.incarnation;
1195
+ if (channel)
1196
+ channel.binaryCells = message.encoding === "semantic-binary-v1";
1143
1197
  // The trusted lease address was fixed at delivery admission; preserve the
1144
1198
  // exact envelope and its observation identity through transmission.
1145
1199
  const detailedDeliveryObservation = this.#observability?.enabled === true;
@@ -1215,13 +1269,18 @@ export class PaneStreamLiveConnection {
1215
1269
  }
1216
1270
  }
1217
1271
  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
- });
1272
+ if (this.#panes.get(pane)?.binaryCells) {
1273
+ this.#sendEncoded(pane, Buffer.from(encodeTerminalDeliveryBinaryChunk({ pane, transactionId: message.transactionId, index: message.index }, message.bytes)), true);
1274
+ }
1275
+ else {
1276
+ this.#sendFrame(pane, {
1277
+ type: "terminal-delivery-chunk",
1278
+ pane,
1279
+ transactionId: message.transactionId,
1280
+ index: message.index,
1281
+ data: Buffer.from(message.bytes).toString("base64"),
1282
+ });
1283
+ }
1225
1284
  }
1226
1285
  else {
1227
1286
  this.#sendFrame(pane, { type: "terminal-delivery-fault", pane, fault: message });
@@ -1232,15 +1291,53 @@ export class PaneStreamLiveConnection {
1232
1291
  }
1233
1292
  await this.#awaitSemanticCredit(pane);
1234
1293
  }
1294
+ #observeSurfaceDemand(panes) {
1295
+ for (const pane of panes) {
1296
+ if (this.#surfaceDemands.has(pane))
1297
+ continue;
1298
+ const demand = this.#nativeSurfaces?.demand.viewer(this.#descriptor.sessionName, [pane]);
1299
+ if (!demand)
1300
+ return;
1301
+ demand.native(this.#surfaceNative);
1302
+ this.#surfaceDemands.set(pane, demand);
1303
+ }
1304
+ }
1305
+ #surfaceControl(type) {
1306
+ if (type === "terminal-surface-offer")
1307
+ return;
1308
+ this.#surfaceNative = type === "terminal-surface-peer";
1309
+ for (const demand of this.#surfaceDemands.values())
1310
+ demand.native(this.#surfaceNative);
1311
+ }
1235
1312
  /** Announce the pane's kitty graphics state; the client pulls bytes it lacks. */
1236
1313
  #startGraphics(channel) {
1237
1314
  const graphics = channel.delivery?.graphics;
1238
1315
  if (!graphics)
1239
1316
  return;
1317
+ this.#observeSurfaceDemand([channel.semanticPaneId]);
1240
1318
  // Renderers that cannot inflate see raw formats; their bytes are inflated
1241
1319
  // when served.
1242
1320
  const zlib = this.#descriptor.terminalDelivery?.protocolVersions.includes(TERMINAL_GRAPHICS_ZLIB_CAPABILITY_VERSION) === true;
1243
1321
  channel.graphicsZlib = zlib;
1322
+ channel.graphicsLocal =
1323
+ !!this.#localImages &&
1324
+ this.#descriptor.terminalDelivery?.protocolVersions.some((version) => version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
1325
+ version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
1326
+ version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION ||
1327
+ version === TERMINAL_GRAPHICS_COMPRESSED_LOCAL_CAPABILITY_VERSION) === true;
1328
+ channel.graphicsCompressed =
1329
+ this.#descriptor.terminalDelivery?.protocolVersions.some((version) => version === TERMINAL_GRAPHICS_COMPRESSED_CAPABILITY_VERSION ||
1330
+ version === TERMINAL_GRAPHICS_COMPRESSED_LOCAL_CAPABILITY_VERSION) === true;
1331
+ channel.graphicsInline = new Set();
1332
+ channel.graphicsBinary =
1333
+ this.#descriptor.terminalDelivery?.protocolVersions.some((version) => version === TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION ||
1334
+ version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
1335
+ version === TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION ||
1336
+ version === TERMINAL_PROJECTION_CAPABILITY_VERSION ||
1337
+ version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
1338
+ version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION ||
1339
+ version === TERMINAL_GRAPHICS_COMPRESSED_CAPABILITY_VERSION ||
1340
+ version === TERMINAL_GRAPHICS_COMPRESSED_LOCAL_CAPABILITY_VERSION) === true;
1244
1341
  // Renderers that report cell pixels learn up front that this daemon
1245
1342
  // accepts them, before any image exists.
1246
1343
  const cellPixels = this.#descriptor.terminalDelivery?.protocolVersions.includes(TERMINAL_GRAPHICS_CELL_PIXELS_CAPABILITY_VERSION) === true;
@@ -1268,6 +1365,9 @@ export class PaneStreamLiveConnection {
1268
1365
  ...(zlib ? state : rawGraphicsState(state)),
1269
1366
  ...(cellPixels ? { cellPixels: true } : {}),
1270
1367
  ...(preciseCellPixels ? { preciseCellPixels: true } : {}),
1368
+ ...(channel.graphicsBinary ? { binaryBlobs: true } : {}),
1369
+ ...(channel.graphicsCompressed ? { compressedBlobs: true } : {}),
1370
+ ...(channel.graphicsLocal ? { localFiles: true } : {}),
1271
1371
  },
1272
1372
  });
1273
1373
  if (pushed.length > 0)
@@ -1284,15 +1384,24 @@ export class PaneStreamLiveConnection {
1284
1384
  placements: [],
1285
1385
  cellPixels: true,
1286
1386
  ...(preciseCellPixels ? { preciseCellPixels: true } : {}),
1387
+ ...(channel.graphicsBinary ? { binaryBlobs: true } : {}),
1388
+ ...(channel.graphicsCompressed ? { compressedBlobs: true } : {}),
1389
+ ...(channel.graphicsLocal ? { localFiles: true } : {}),
1287
1390
  },
1288
1391
  });
1289
1392
  }
1290
1393
  }
1291
- #requestGraphics(pane, digests) {
1394
+ #requestGraphics(pane, digests, preferInline = false) {
1292
1395
  const channel = this.#panes.get(pane);
1293
1396
  // Requests are only meaningful after an announcement; ignore stray ones.
1294
1397
  if (!channel || channel.closed || !channel.graphicsAnnounced)
1295
1398
  return;
1399
+ if (preferInline && channel.graphicsLocal) {
1400
+ for (const digest of digests) {
1401
+ if ((channel.graphicsInline?.size ?? 0) < GRAPHICS_MAX_QUEUED_DIGESTS)
1402
+ channel.graphicsInline?.add(digest);
1403
+ }
1404
+ }
1296
1405
  this.#queueGraphics(channel, digests);
1297
1406
  }
1298
1407
  #queueGraphics(channel, digests) {
@@ -1313,6 +1422,7 @@ export class PaneStreamLiveConnection {
1313
1422
  try {
1314
1423
  while (!this.#closed && !channel.closed && channel.graphicsQueue.length > 0) {
1315
1424
  const digest = channel.graphicsQueue.shift();
1425
+ const inline = channel.graphicsInline?.delete(digest) ?? false;
1316
1426
  const graphics = channel.delivery?.graphics;
1317
1427
  const image = graphics?.image(digest);
1318
1428
  if (!graphics || !image)
@@ -1339,6 +1449,49 @@ export class PaneStreamLiveConnection {
1339
1449
  continue;
1340
1450
  const format = raw ? (image.format === "rgba-zlib" ? "rgba" : "rgb") : image.format;
1341
1451
  const total = data.byteLength;
1452
+ if (!inline && channel.graphicsLocal && !raw && this.#localImages) {
1453
+ for (const [id, lease] of this.#localLeases)
1454
+ if (lease.expiresAt <= Date.now())
1455
+ this.#localLeases.delete(id);
1456
+ if (this.#localLeases.size < 64) {
1457
+ try {
1458
+ const lease = await this.#localImages.acquire({
1459
+ generation: this.#localImages.identity.generation,
1460
+ client: this.#descriptor.leaseId,
1461
+ digest,
1462
+ bytes: data,
1463
+ });
1464
+ if (this.#closed || channel.closed || graphics.image(digest) !== image) {
1465
+ await this.#localImages.release(lease);
1466
+ continue;
1467
+ }
1468
+ this.#localLeases.set(lease.id, {
1469
+ pane: channel.semanticPaneId,
1470
+ expiresAt: lease.expiresAt,
1471
+ });
1472
+ this.#sendFrame(null, {
1473
+ type: "terminal-graphics-local",
1474
+ pane: channel.semanticPaneId,
1475
+ generation: lease.generation,
1476
+ leaseId: lease.id,
1477
+ path: lease.path,
1478
+ expiresAt: lease.expiresAt,
1479
+ blob: {
1480
+ digest,
1481
+ format,
1482
+ width: image.width,
1483
+ height: image.height,
1484
+ total,
1485
+ offset: 0,
1486
+ },
1487
+ });
1488
+ continue;
1489
+ }
1490
+ catch {
1491
+ /* Optional transport: retain the negotiated inline fallback. */
1492
+ }
1493
+ }
1494
+ }
1342
1495
  for (let offset = 0; offset < total; offset += TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES) {
1343
1496
  while ((this.#socket.bufferedAmount ?? 0) > GRAPHICS_SEND_WINDOW_BYTES) {
1344
1497
  if (this.#closed || channel.closed)
@@ -1352,6 +1505,43 @@ export class PaneStreamLiveConnection {
1352
1505
  if (offset > 0 && graphics.image(digest) !== image)
1353
1506
  break;
1354
1507
  const end = Math.min(total, offset + TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES);
1508
+ if (channel.graphicsBinary) {
1509
+ const header = {
1510
+ pane: channel.semanticPaneId,
1511
+ blob: {
1512
+ digest,
1513
+ format,
1514
+ width: image.width,
1515
+ height: image.height,
1516
+ total,
1517
+ offset,
1518
+ },
1519
+ };
1520
+ const compressed = channel.graphicsCompressed
1521
+ ? await compressGraphicsChunk(header, data.subarray(offset, end))
1522
+ : null;
1523
+ // Compression yields: a pane may close or replace this image meanwhile.
1524
+ if (this.#closed || channel.closed)
1525
+ return;
1526
+ if (graphics.image(digest) !== image)
1527
+ break;
1528
+ if (compressed) {
1529
+ this.#sendEncoded(null, Buffer.from(compressed), true);
1530
+ continue;
1531
+ }
1532
+ this.#sendEncoded(null, Buffer.from(encodeTerminalGraphicsBinary({
1533
+ pane: channel.semanticPaneId,
1534
+ blob: {
1535
+ digest,
1536
+ format,
1537
+ width: image.width,
1538
+ height: image.height,
1539
+ total,
1540
+ offset,
1541
+ },
1542
+ }, data.subarray(offset, end))), true);
1543
+ continue;
1544
+ }
1355
1545
  this.#sendFrame(null, {
1356
1546
  type: "terminal-graphics-blob",
1357
1547
  pane: channel.semanticPaneId,
@@ -1508,6 +1698,8 @@ export class PaneStreamLiveConnection {
1508
1698
  if (channel.closed)
1509
1699
  return;
1510
1700
  channel.closed = true;
1701
+ this.#surfaceDemands.get(channel.semanticPaneId)?.close();
1702
+ this.#surfaceDemands.delete(channel.semanticPaneId);
1511
1703
  const sub = channel.sub;
1512
1704
  channel.sub = null;
1513
1705
  if (sub)
@@ -1671,7 +1863,12 @@ export class PaneStreamLiveConnection {
1671
1863
  if (this.#closed || this.#socket.readyState !== WS_OPEN)
1672
1864
  return;
1673
1865
  const encoded = JSON.stringify(frame);
1674
- const bytes = Buffer.byteLength(encoded, "utf8");
1866
+ this.#sendEncoded(pane, encoded, false);
1867
+ }
1868
+ #sendEncoded(pane, encoded, binary) {
1869
+ if (this.#closed || this.#socket.readyState !== WS_OPEN)
1870
+ return;
1871
+ const bytes = Buffer.byteLength(encoded);
1675
1872
  if (pane !== null) {
1676
1873
  this.#ledger.take(this.#clientId, pane, "ws-send-buffer", bytes);
1677
1874
  if (this.#deliveryAcks)
@@ -1680,7 +1877,7 @@ export class PaneStreamLiveConnection {
1680
1877
  this.#sendQueue.push({ pane, remaining: bytes });
1681
1878
  this.#sentBytesTotal += bytes;
1682
1879
  try {
1683
- this.#socket.send(encoded, { binary: false });
1880
+ this.#socket.send(encoded, { binary });
1684
1881
  }
1685
1882
  catch {
1686
1883
  this.close(1011, "stream-unavailable");
@@ -1914,6 +2111,24 @@ export class PaneStreamLiveConnection {
1914
2111
  });
1915
2112
  return;
1916
2113
  }
2114
+ if (frame.type === "terminal-projection-receipt") {
2115
+ const channel = this.#deliveryChannel(frame.receipt);
2116
+ if (!channel)
2117
+ return;
2118
+ if (!channel.delivery?.projectionReceipt)
2119
+ return this.#failProtocol("protocol-error");
2120
+ channel.delivery.projectionReceipt(frame.receipt);
2121
+ return;
2122
+ }
2123
+ if (frame.type === "terminal-projection-materialize") {
2124
+ const channel = this.#deliveryChannel(frame.request);
2125
+ if (!channel)
2126
+ return;
2127
+ if (!channel.delivery?.materialize)
2128
+ return this.#failProtocol("protocol-error");
2129
+ channel.delivery.materialize(frame.request);
2130
+ return;
2131
+ }
1917
2132
  if (frame.type === "terminal-delivery-ack") {
1918
2133
  const channel = this.#deliveryChannel(frame.ack);
1919
2134
  if (!channel)
@@ -1945,8 +2160,138 @@ export class PaneStreamLiveConnection {
1945
2160
  this.#acceptSemanticIntent(frame.operationId, frame.intent);
1946
2161
  return;
1947
2162
  }
2163
+ if (frame.type === "terminal-surface-watch") {
2164
+ if (byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES ||
2165
+ frame.generation !== this.#sessionRuntimeBinding?.generation ||
2166
+ !this.#descriptor.terminalDelivery?.protocolVersions.includes(TERMINAL_SURFACE_CAPABILITY_VERSION) ||
2167
+ !this.#nativeSurfaces ||
2168
+ this.#surfaceReceiver)
2169
+ return this.#failProtocol("protocol-error");
2170
+ if (this.#surfaceViewer)
2171
+ return;
2172
+ this.#surfaceViewer = new NativeSurfaceViewer(this.#nativeSurfaces, this.#descriptor.panes, (control) => {
2173
+ if (this.#closed)
2174
+ return false;
2175
+ this.#surfaceControl(control.type);
2176
+ this.#sendFrame(null, control);
2177
+ return !this.#closed;
2178
+ }, (pane, generation) => {
2179
+ const channel = this.#panes.get(pane);
2180
+ const address = channel?.deliveryAddress;
2181
+ return !this.#closed &&
2182
+ channel &&
2183
+ !channel.closed &&
2184
+ channel.delivery &&
2185
+ address?.generation === generation
2186
+ ? address.deliveryNonce
2187
+ : null;
2188
+ });
2189
+ this.#surfaceViewer.start();
2190
+ return;
2191
+ }
2192
+ if (frame.type === "terminal-surface-receiver") {
2193
+ if (this.#surfaceViewer) {
2194
+ if (byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES || !this.#surfaceViewer.receive(frame))
2195
+ this.#failProtocol("protocol-error");
2196
+ return;
2197
+ }
2198
+ if (byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES ||
2199
+ !this.#descriptor.terminalDelivery ||
2200
+ frame.generation !== this.#sessionRuntimeBinding?.generation) {
2201
+ return this.#failProtocol("protocol-error");
2202
+ }
2203
+ if (this.#surfaceReceiver) {
2204
+ // Parsed schema order is deterministic. A duplicate cannot change the
2205
+ // challenge, kernel identity or endpoint, nor amplify fallback replies.
2206
+ if (JSON.stringify(frame) !== JSON.stringify(this.#surfaceReceiver)) {
2207
+ return this.#failProtocol("protocol-error");
2208
+ }
2209
+ return;
2210
+ }
2211
+ this.#surfaceReceiver = frame;
2212
+ if (this.#descriptor.terminalDelivery.protocolVersions.includes(TERMINAL_SURFACE_CAPABILITY_VERSION)) {
2213
+ const native = this.#nativeSurfaces?.open(frame, this.#descriptor.panes, (control) => {
2214
+ if (this.#closed)
2215
+ return false;
2216
+ this.#surfaceControl(control.type);
2217
+ this.#sendFrame(null, control);
2218
+ return !this.#closed;
2219
+ }, (pane, generation) => {
2220
+ const channel = this.#panes.get(pane);
2221
+ const address = channel?.deliveryAddress;
2222
+ if (this.#closed ||
2223
+ !channel ||
2224
+ channel.closed ||
2225
+ !channel.delivery ||
2226
+ !address ||
2227
+ address.generation !== generation)
2228
+ return null;
2229
+ return address.deliveryNonce;
2230
+ }) ?? null;
2231
+ if (this.#closed) {
2232
+ native?.close();
2233
+ return;
2234
+ }
2235
+ this.#surfaceSession = native;
2236
+ if (native)
2237
+ return;
2238
+ }
2239
+ // No provider/capability means no PID/slot lookup and no inferred locality.
2240
+ this.#sendFrame(null, {
2241
+ type: "terminal-surface-fallback",
2242
+ version: 1,
2243
+ generation: frame.generation,
2244
+ connectionNonce: frame.connectionNonce,
2245
+ reason: "unsupported",
2246
+ });
2247
+ return;
2248
+ }
2249
+ if (frame.type === "terminal-surface-fallback") {
2250
+ if (this.#surfaceViewer) {
2251
+ if (byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES || !this.#surfaceViewer.fallback(frame))
2252
+ this.#failProtocol("protocol-error");
2253
+ return;
2254
+ }
2255
+ if (byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES ||
2256
+ frame.generation !== this.#surfaceReceiver?.generation ||
2257
+ frame.connectionNonce !== this.#surfaceReceiver.connectionNonce) {
2258
+ return this.#failProtocol("protocol-error");
2259
+ }
2260
+ this.#surfaceSession?.fallback(frame.reason);
2261
+ return;
2262
+ }
2263
+ if (frame.type === "terminal-surface-retired") {
2264
+ if (this.#surfaceViewer) {
2265
+ if (byteLength > TERMINAL_SURFACE_MAX_CONTROL_BYTES || !this.#surfaceViewer.retire(frame))
2266
+ this.#failProtocol("protocol-error");
2267
+ return;
2268
+ }
2269
+ if (byteLength <= TERMINAL_SURFACE_MAX_CONTROL_BYTES &&
2270
+ frame.generation === this.#surfaceReceiver?.generation &&
2271
+ frame.connectionNonce === this.#surfaceReceiver.connectionNonce &&
2272
+ this.#surfaceSession?.retire(frame.sequence))
2273
+ return;
2274
+ // Unissued/replayed ACKs must not retire another socket's native storage.
2275
+ return this.#failProtocol("protocol-error");
2276
+ }
1948
2277
  if (frame.type === "terminal-graphics-request") {
1949
- this.#requestGraphics(frame.pane, frame.digests);
2278
+ this.#requestGraphics(frame.pane, frame.digests, frame.preferInline === true);
2279
+ return;
2280
+ }
2281
+ if (frame.type === "terminal-graphics-local-release") {
2282
+ const lease = this.#localLeases.get(frame.leaseId);
2283
+ if (lease &&
2284
+ lease.pane === frame.pane &&
2285
+ frame.generation === this.#localImages?.identity.generation) {
2286
+ this.#localLeases.delete(frame.leaseId);
2287
+ void this.#localImages
2288
+ ?.release({
2289
+ generation: frame.generation,
2290
+ client: this.#descriptor.leaseId,
2291
+ id: frame.leaseId,
2292
+ })
2293
+ .catch(() => { });
2294
+ }
1950
2295
  return;
1951
2296
  }
1952
2297
  if (frame.type === "terminal-cell-pixels") {
@@ -2127,6 +2472,12 @@ export class PaneStreamLiveConnection {
2127
2472
  this.#evaluateResume(pane);
2128
2473
  }
2129
2474
  #acceptInput(pane, seq, kind, data, frameBytes, performanceTraceId, causalProbe, ingressAtMicros = null, callbackAtSharedMicros = null, ingressAtSharedMicros = null, expectedLifetime) {
2475
+ if (kind === "bytes" &&
2476
+ (!this.#sessionRuntimeBinding ||
2477
+ !this.#descriptor.terminalDelivery?.protocolVersions.includes(PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION))) {
2478
+ this.#failProtocol("input-rejected");
2479
+ return;
2480
+ }
2130
2481
  if (expectedLifetime &&
2131
2482
  (!this.#sessionRuntimeBinding ||
2132
2483
  !this.#descriptor.terminalDelivery?.protocolVersions.includes(PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION))) {
@@ -2227,8 +2578,10 @@ export class PaneStreamLiveConnection {
2227
2578
  : undefined, expectedLifetime);
2228
2579
  else if (kind === "text")
2229
2580
  channel.sub.sendText(data);
2230
- else
2581
+ else if (kind === "key")
2231
2582
  channel.sub.sendKey(data);
2583
+ else
2584
+ throw new Error("Byte input requires semantic runtime");
2232
2585
  let ackStartedAtMicros = null;
2233
2586
  let ackStartedAtSharedMicros = null;
2234
2587
  if (trace) {
@@ -1,3 +1,4 @@
1
+ import { NativeSurfaceProviderRegistry } from "../graphics/native-surface-provider.js";
1
2
  import { SessionRuntimeTransportBinder } from "../session-runtime/transport-binding.js";
2
3
  import { PaneStreamLeaseManager } from "./lease-manager.js";
3
4
  import { PaneStreamAdmissionCoordinator, } from "./pane-stream-websocket.js";
@@ -8,14 +9,22 @@ import { PaneStreamAdmissionCoordinator, } from "./pane-stream-websocket.js";
8
9
  */
9
10
  export class PaneStreamRuntime {
10
11
  coordinator;
12
+ nativeSurfaces;
11
13
  #disposePromise = null;
12
14
  constructor(options) {
15
+ this.nativeSurfaces =
16
+ options.nativeSurfaces ??
17
+ (process.platform === "darwin"
18
+ ? new NativeSurfaceProviderRegistry(options.daemonInstanceId)
19
+ : undefined);
13
20
  const leaseManager = new PaneStreamLeaseManager({
14
21
  daemonInstanceId: options.daemonInstanceId,
15
22
  });
16
23
  const transportBinder = new SessionRuntimeTransportBinder(options.sessionRuntimeRegistry);
17
24
  this.coordinator = new PaneStreamAdmissionCoordinator({
18
25
  ...options.admission,
26
+ localImages: options.localImages,
27
+ nativeSurfaces: this.nativeSurfaces,
19
28
  daemonInstanceId: options.daemonInstanceId,
20
29
  webSocketUrl: options.webSocketUrl,
21
30
  leaseManager,
@@ -42,6 +51,7 @@ export class PaneStreamRuntime {
42
51
  dispose() {
43
52
  if (!this.#disposePromise) {
44
53
  this.#disposePromise = (async () => {
54
+ this.nativeSurfaces?.close();
45
55
  await this.coordinator.shutdown();
46
56
  })();
47
57
  }
@@ -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 () => {