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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-ide",
3
- "version": "2.9.11",
3
+ "version": "2.9.12",
4
4
  "description": "The open-source workspace for coding agents: run Claude Code, Codex and other agents in tmux with live status",
5
5
  "type": "module",
6
6
  "bin": {
@@ -120,3 +120,7 @@ export * from "./tmux-agent-open.ts";
120
120
  export * from "./tmux-server-diagnostics.ts";
121
121
 
122
122
  export * from "./terminal-history-anchor.ts";
123
+
124
+ export * from "./terminal-delivery-binary-chunk.ts";
125
+
126
+ export * from "./terminal-projection.ts";
@@ -1,3 +1,8 @@
1
+ import {
2
+ TerminalProjectionEnvelopeSchemaZ,
3
+ TerminalProjectionReceiptSchemaZ,
4
+ TerminalProjectionMaterializeSchemaZ,
5
+ } from "./terminal-projection.ts";
1
6
  import { z } from "zod";
2
7
  import { isTmuxServerPaneStreamPath } from "./tmux-server-scope.ts";
3
8
  import {
@@ -45,6 +50,7 @@ import {
45
50
  SessionRuntimePresenceStateSchemaZ,
46
51
  SessionRuntimeSemanticIntentSchemaZ,
47
52
  SessionRuntimeTerminalKeyInputSchemaZ,
53
+ SessionRuntimeTerminalBytesInputSchemaZ,
48
54
  SessionRuntimeTerminalKeyNameSchemaZ,
49
55
  SessionRuntimeTerminalTextInputSchemaZ,
50
56
  } from "./session-runtime.ts";
@@ -232,6 +238,8 @@ export const PaneStreamKeyNameSchemaZ = SessionRuntimeTerminalKeyNameSchemaZ;
232
238
 
233
239
  /** Negotiated only with callers offering this additive delivery capability. */
234
240
  export const PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION = 105;
241
+ /** Exact hex bytes require explicit offer + ready acknowledgement; older daemons may omit it. */
242
+ export const PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION = 106;
235
243
  export const PaneStreamInputLifetimeSchemaZ = z
236
244
  .object({
237
245
  generation: z.string().min(1).max(256),
@@ -254,6 +262,7 @@ export const PaneStreamInputFrameSchemaZ = z
254
262
  .discriminatedUnion("kind", [
255
263
  SessionRuntimeTerminalTextInputSchemaZ.extend(PaneStreamInputFrameMetadataShape),
256
264
  SessionRuntimeTerminalKeyInputSchemaZ.extend(PaneStreamInputFrameMetadataShape),
265
+ SessionRuntimeTerminalBytesInputSchemaZ.extend(PaneStreamInputFrameMetadataShape),
257
266
  ])
258
267
  .superRefine((value, context) => {
259
268
  if (!value.causalProbe) return;
@@ -279,6 +288,18 @@ export type PaneStreamConsumedFrame = z.infer<typeof PaneStreamConsumedFrameSche
279
288
  export const PaneStreamTerminalDeliveryAckFrameSchemaZ = z
280
289
  .object({ type: z.literal("terminal-delivery-ack"), ack: TerminalDeliveryAckSchemaZ })
281
290
  .strict();
291
+ export const PaneStreamTerminalProjectionReceiptFrameSchemaZ = z
292
+ .object({
293
+ type: z.literal("terminal-projection-receipt"),
294
+ receipt: TerminalProjectionReceiptSchemaZ,
295
+ })
296
+ .strict();
297
+ export const PaneStreamTerminalProjectionMaterializeFrameSchemaZ = z
298
+ .object({
299
+ type: z.literal("terminal-projection-materialize"),
300
+ request: TerminalProjectionMaterializeSchemaZ,
301
+ })
302
+ .strict();
282
303
  export const PaneStreamTerminalDeliveryNackFrameSchemaZ = z
283
304
  .object({ type: z.literal("terminal-delivery-nack"), nack: TerminalDeliveryNackSchemaZ })
284
305
  .strict();
@@ -363,6 +384,7 @@ export const PaneStreamClockProbeFrameSchemaZ = z
363
384
  export const PaneStreamTerminalGraphicsRequestFrameSchemaZ = z
364
385
  .object({
365
386
  type: z.literal("terminal-graphics-request"),
387
+ preferInline: z.literal(true).optional(),
366
388
  pane: PaneStreamSemanticPaneIdSchemaZ,
367
389
  digests: z
368
390
  .array(TerminalGraphicsDigestSchemaZ)
@@ -371,6 +393,15 @@ export const PaneStreamTerminalGraphicsRequestFrameSchemaZ = z
371
393
  })
372
394
  .strict();
373
395
 
396
+ export const PaneStreamTerminalGraphicsLocalReleaseFrameSchemaZ = z
397
+ .object({
398
+ type: z.literal("terminal-graphics-local-release"),
399
+ pane: PaneStreamSemanticPaneIdSchemaZ,
400
+ generation: z.uuid(),
401
+ leaseId: z.uuid(),
402
+ })
403
+ .strict();
404
+
374
405
  /** The renderer's terminal cell size in device pixels (see capability 103). */
375
406
  export const PaneStreamTerminalCellPixelsFrameSchemaZ = z
376
407
  .object({
@@ -391,6 +422,8 @@ export const PaneStreamClientFrameSchemaZ = z.union([
391
422
  PaneStreamInputFrameSchemaZ,
392
423
  PaneStreamConsumedFrameSchemaZ,
393
424
  PaneStreamTerminalDeliveryAckFrameSchemaZ,
425
+ PaneStreamTerminalProjectionReceiptFrameSchemaZ,
426
+ PaneStreamTerminalProjectionMaterializeFrameSchemaZ,
394
427
  PaneStreamTerminalDeliveryNackFrameSchemaZ,
395
428
  PaneStreamTerminalDeliveryVisibilityFrameSchemaZ,
396
429
  PaneStreamSemanticIntentFrameSchemaZ,
@@ -401,6 +434,7 @@ export const PaneStreamClientFrameSchemaZ = z.union([
401
434
  PaneStreamAuthorityReleaseFrameSchemaZ,
402
435
  PaneStreamClockProbeFrameSchemaZ,
403
436
  PaneStreamTerminalGraphicsRequestFrameSchemaZ,
437
+ PaneStreamTerminalGraphicsLocalReleaseFrameSchemaZ,
404
438
  PaneStreamTerminalCellPixelsFrameSchemaZ,
405
439
  ]);
406
440
  export type PaneStreamClientFrame = z.infer<typeof PaneStreamClientFrameSchemaZ>;
@@ -421,6 +455,7 @@ export const PaneStreamReadyFrameSchemaZ = z
421
455
  .object({
422
456
  type: z.literal("ready"),
423
457
  inputLifetime: z.literal(true).optional(),
458
+ inputBytes: z.literal(true).optional(),
424
459
  protocolVersion: z.literal(PANE_STREAM_PROTOCOL_VERSION),
425
460
  daemonInstanceId: BoundedIdentitySchemaZ,
426
461
  requestId: z.uuid(),
@@ -678,6 +713,13 @@ export const PaneStreamTerminalDeliveryEnvelopeFrameSchemaZ = z
678
713
  envelope: TerminalDeliveryEnvelopeSchemaZ,
679
714
  })
680
715
  .strict();
716
+ export const PaneStreamTerminalProjectionEnvelopeFrameSchemaZ = z
717
+ .object({
718
+ type: z.literal("terminal-projection-envelope"),
719
+ pane: PaneStreamSemanticPaneIdSchemaZ,
720
+ envelope: TerminalProjectionEnvelopeSchemaZ,
721
+ })
722
+ .strict();
681
723
  export const PaneStreamTerminalDeliveryChunkFrameSchemaZ = z
682
724
  .object({
683
725
  type: z.literal("terminal-delivery-chunk"),
@@ -709,6 +751,22 @@ export const PaneStreamTerminalGraphicsBlobFrameSchemaZ = z
709
751
  data: Base64SchemaZ(Math.ceil(TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES / 3) * 4).min(4),
710
752
  })
711
753
  .strict();
754
+ export const PaneStreamTerminalGraphicsLocalFrameSchemaZ = z
755
+ .object({
756
+ type: z.literal("terminal-graphics-local"),
757
+ pane: PaneStreamSemanticPaneIdSchemaZ,
758
+ generation: z.uuid(),
759
+ leaseId: z.uuid(),
760
+ path: z
761
+ .string()
762
+ .min(1)
763
+ .max(4096)
764
+ .refine((path) => path.startsWith("/") && !path.includes("\0")),
765
+ expiresAt: z.number().int().positive().safe(),
766
+ blob: TerminalGraphicsBlobSchemaZ.extend({ offset: z.literal(0) }),
767
+ })
768
+ .strict();
769
+
712
770
  export const PaneStreamSemanticIntentAckFrameSchemaZ = z
713
771
  .object({
714
772
  type: z.literal("semantic-intent-ack"),
@@ -811,10 +869,12 @@ export const PaneStreamServerFrameSchemaZ = z.discriminatedUnion("type", [
811
869
  PaneStreamCausalCellFailureFrameSchemaZ,
812
870
  PaneStreamTerminalDeliveryReadyFrameSchemaZ,
813
871
  PaneStreamTerminalDeliveryEnvelopeFrameSchemaZ,
872
+ PaneStreamTerminalProjectionEnvelopeFrameSchemaZ,
814
873
  PaneStreamTerminalDeliveryChunkFrameSchemaZ,
815
874
  PaneStreamTerminalDeliveryFaultFrameSchemaZ,
816
875
  PaneStreamTerminalGraphicsFrameSchemaZ,
817
876
  PaneStreamTerminalGraphicsBlobFrameSchemaZ,
877
+ PaneStreamTerminalGraphicsLocalFrameSchemaZ,
818
878
  PaneStreamSemanticIntentAckFrameSchemaZ,
819
879
  PaneStreamViewportAckFrameSchemaZ,
820
880
  PaneStreamAuthoritySnapshotFrameSchemaZ,
@@ -0,0 +1,55 @@
1
+ import { z } from "zod";
2
+ import { TerminalAttachmentSemanticPaneIdSchemaZ } from "./semantic-identity.ts";
3
+ import { TERMINAL_DELIVERY_CHUNK_BYTES } from "./terminal-delivery.ts";
4
+ export const TerminalDeliveryBinaryChunkHeaderSchemaZ = z
5
+ .object({
6
+ pane: TerminalAttachmentSemanticPaneIdSchemaZ,
7
+ transactionId: z.uuid(),
8
+ index: z.number().int().min(0).max(255),
9
+ })
10
+ .strict();
11
+ export type TerminalDeliveryBinaryChunkHeader = z.infer<
12
+ typeof TerminalDeliveryBinaryChunkHeaderSchemaZ
13
+ >;
14
+ /** TDC1 + u32be metadata length + strict header + unchanged representation chunk. */
15
+ export function encodeTerminalDeliveryBinaryChunk(
16
+ header: TerminalDeliveryBinaryChunkHeader,
17
+ bytes: Uint8Array,
18
+ ): Uint8Array {
19
+ const parsed = TerminalDeliveryBinaryChunkHeaderSchemaZ.parse(header);
20
+ const json = new TextEncoder().encode(JSON.stringify(parsed));
21
+ if (json.length > 4096 || bytes.length > TERMINAL_DELIVERY_CHUNK_BYTES)
22
+ throw new TypeError("Binary delivery chunk bound");
23
+ const result = new Uint8Array(8 + json.length + bytes.length);
24
+ result.set([84, 68, 67, 49]);
25
+ new DataView(result.buffer).setUint32(4, json.length, false);
26
+ result.set(json, 8);
27
+ result.set(bytes, 8 + json.length);
28
+ return result;
29
+ }
30
+ export function decodeTerminalDeliveryBinaryChunk(data: Uint8Array): {
31
+ header: TerminalDeliveryBinaryChunkHeader;
32
+ bytes: Uint8Array;
33
+ } {
34
+ if (
35
+ data.length < 9 ||
36
+ data.length > 8 + 4096 + TERMINAL_DELIVERY_CHUNK_BYTES ||
37
+ data[0] !== 84 ||
38
+ data[1] !== 68 ||
39
+ data[2] !== 67 ||
40
+ data[3] !== 49
41
+ )
42
+ throw new TypeError("Invalid binary delivery packet");
43
+ const length = new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(4, false);
44
+ if (
45
+ length < 1 ||
46
+ length > 4096 ||
47
+ 8 + length > data.length ||
48
+ data.length - 8 - length > TERMINAL_DELIVERY_CHUNK_BYTES
49
+ )
50
+ throw new TypeError("Invalid binary delivery header bound");
51
+ const header = TerminalDeliveryBinaryChunkHeaderSchemaZ.parse(
52
+ JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(data.subarray(8, 8 + length))),
53
+ );
54
+ return { header, bytes: data.subarray(8 + length) };
55
+ }
@@ -1,3 +1,4 @@
1
+ import type { TerminalProjectionEnvelope } from "./terminal-projection.ts";
1
2
  import { z } from "zod";
2
3
  import { CausalCellProofV1SchemaZ } from "./causal-cell.ts";
3
4
  import { TerminalAttachmentSemanticPaneIdSchemaZ } from "./semantic-identity.ts";
@@ -13,6 +14,11 @@ import {
13
14
  export const STOCK_CAPTURE_TAB_UNAVAILABLE =
14
15
  "This tmux server cannot provide an exact snapshot of saved tab cells. Continue this session in native tmux, or use a new session started with tmux-ide's bundled server.";
15
16
 
17
+ /** Opt-in bundles:109 adds binary cells+107 graphics;110 also adds108 local image leases.
18
+ * Offer compact in encodings for old-daemon compatibility; new daemons respond binary explicitly. */
19
+ export const TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION = 109;
20
+ export const TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION = 110;
21
+
16
22
  export const TERMINAL_DELIVERY_PROTOCOL_VERSION = 1 as const;
17
23
  export const TERMINAL_DELIVERY_CHUNK_BYTES = 256 * 1024;
18
24
  export const TERMINAL_DELIVERY_PATCH_TO_SEED_BYTES = 512 * 1024;
@@ -20,6 +26,7 @@ export const TERMINAL_DELIVERY_MAX_REPRESENTATION_BYTES = 16 * 1024 * 1024;
20
26
 
21
27
  export const TerminalDeliveryEncodingSchemaZ = z.enum([
22
28
  "semantic-compact-v1",
29
+ "semantic-binary-v1",
23
30
  "semantic-v1",
24
31
  "ansi-diff-v1",
25
32
  "ansi-raw-v1",
@@ -46,18 +53,22 @@ export const TerminalDeliveryNegotiatedSchemaZ = z
46
53
  protocolVersion: z.literal(TERMINAL_DELIVERY_PROTOCOL_VERSION),
47
54
  encoding: TerminalDeliveryEncodingSchemaZ,
48
55
  fallbackEncoding: z.literal("semantic-v1").nullable().optional(),
56
+ alternateScreenProjection: z.literal("deferred-history-v1").optional(),
49
57
  richPlacements: z.boolean(),
50
58
  generation: SessionRuntimeGenerationSchemaZ,
51
59
  deliveryNonce: z.uuid(),
52
60
  })
53
61
  .strict()
54
62
  .superRefine((value, context) => {
63
+ if (value.alternateScreenProjection && value.encoding !== "semantic-binary-v1")
64
+ context.addIssue({ code: "custom", message: "projection requires binary semantic delivery" });
55
65
  if (value.fallbackEncoding && value.encoding !== "semantic-compact-v1")
56
66
  context.addIssue({ code: "custom", message: "fallback encoding requires compact semantic" });
57
67
  if (
58
68
  value.richPlacements &&
59
69
  value.encoding !== "semantic-v1" &&
60
- value.encoding !== "semantic-compact-v1"
70
+ value.encoding !== "semantic-compact-v1" &&
71
+ value.encoding !== "semantic-binary-v1"
61
72
  )
62
73
  context.addIssue({ code: "custom", message: "rich placements require semantic delivery" });
63
74
  });
@@ -118,7 +129,10 @@ export const TerminalDeliveryEnvelopeSchemaZ = DeliveryAddressSchemaZ.extend({
118
129
  })
119
130
  .strict()
120
131
  .superRefine((value, context) => {
121
- const semantic = value.encoding === "semantic-v1" || value.encoding === "semantic-compact-v1";
132
+ const semantic =
133
+ value.encoding === "semantic-v1" ||
134
+ value.encoding === "semantic-compact-v1" ||
135
+ value.encoding === "semantic-binary-v1";
122
136
  const expectedChunks = Math.max(
123
137
  1,
124
138
  Math.ceil(value.representationBytes / TERMINAL_DELIVERY_CHUNK_BYTES),
@@ -206,6 +220,7 @@ export type TerminalDeliveryNack = z.infer<typeof TerminalDeliveryNackSchemaZ>;
206
220
 
207
221
  export type TerminalDeliveryServerMessage =
208
222
  | TerminalDeliveryEnvelope
223
+ | TerminalProjectionEnvelope
209
224
  | TerminalDeliveryChunk
210
225
  | TerminalDeliveryFault;
211
226
 
@@ -1,4 +1,5 @@
1
1
  import { z } from "zod";
2
+ import { TerminalAttachmentSemanticPaneIdSchemaZ } from "./semantic-identity.ts";
2
3
 
3
4
  /**
4
5
  * Kitty graphics protocol side channel.
@@ -15,6 +16,12 @@ import { z } from "zod";
15
16
  * version; daemons that know it send graphics frames only to such renderers.
16
17
  */
17
18
  export const TERMINAL_GRAPHICS_CAPABILITY_VERSION = 101;
19
+ /** Raw binary blob frames; requires the graphics side channel (101). */
20
+ export const TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION = 107;
21
+ /** Verified same-host file leases, including binary-blob fallback (107).
22
+ * Offer 108 instead of 107 to retain the existing eight-capability bound.
23
+ * Never infer locality from a loopback URL. */
24
+ export const TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION = 108;
18
25
  /** Renderers that inflate `rgba-zlib`/`rgb-zlib` themselves offer this too. */
19
26
  export const TERMINAL_GRAPHICS_ZLIB_CAPABILITY_VERSION = 102;
20
27
  /**
@@ -92,6 +99,8 @@ export const TerminalGraphicsStateSchemaZ = z
92
99
  /** The daemon accepts `terminal-cell-pixels` (only sent to renderers offering 103). */
93
100
  cellPixels: z.literal(true).optional(),
94
101
  preciseCellPixels: z.literal(true).optional(),
102
+ binaryBlobs: z.literal(true).optional(),
103
+ localFiles: z.literal(true).optional(),
95
104
  })
96
105
  .strict();
97
106
  export type TerminalGraphicsState = z.infer<typeof TerminalGraphicsStateSchemaZ>;
@@ -116,3 +125,62 @@ export type TerminalGraphicsServerMessage =
116
125
  readonly blob: TerminalGraphicsBlob;
117
126
  readonly bytes: Uint8Array;
118
127
  };
128
+
129
+ /** TGB1 + u32be header bytes + strict JSON {pane,blob} + raw payload.
130
+ * No compression is applied here: PNG and negotiated zlib are already encoded.
131
+ */
132
+ export const TERMINAL_GRAPHICS_BINARY_HEADER_BYTES = 4096;
133
+ export const TerminalGraphicsBinaryHeaderSchemaZ = z
134
+ .object({
135
+ pane: TerminalAttachmentSemanticPaneIdSchemaZ,
136
+ blob: TerminalGraphicsBlobSchemaZ,
137
+ })
138
+ .strict();
139
+ export type TerminalGraphicsBinaryHeader = z.infer<typeof TerminalGraphicsBinaryHeaderSchemaZ>;
140
+ export function encodeTerminalGraphicsBinary(
141
+ header: TerminalGraphicsBinaryHeader,
142
+ bytes: Uint8Array,
143
+ ): Uint8Array {
144
+ const parsed = TerminalGraphicsBinaryHeaderSchemaZ.parse(header);
145
+ const json = new TextEncoder().encode(JSON.stringify(parsed));
146
+ if (
147
+ json.length > TERMINAL_GRAPHICS_BINARY_HEADER_BYTES ||
148
+ bytes.length < 1 ||
149
+ bytes.length > TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES ||
150
+ parsed.blob.offset + bytes.length > parsed.blob.total
151
+ )
152
+ throw new Error("Invalid binary graphics bounds");
153
+ const result = new Uint8Array(8 + json.length + bytes.length);
154
+ result.set([84, 71, 66, 49]);
155
+ new DataView(result.buffer).setUint32(4, json.length, false);
156
+ result.set(json, 8);
157
+ result.set(bytes, 8 + json.length);
158
+ return result;
159
+ }
160
+ export function decodeTerminalGraphicsBinary(data: Uint8Array): {
161
+ header: TerminalGraphicsBinaryHeader;
162
+ bytes: Uint8Array;
163
+ } {
164
+ if (
165
+ data.length < 10 ||
166
+ data.length > 8 + TERMINAL_GRAPHICS_BINARY_HEADER_BYTES + TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES ||
167
+ data[0] !== 84 ||
168
+ data[1] !== 71 ||
169
+ data[2] !== 66 ||
170
+ data[3] !== 49
171
+ )
172
+ throw new Error("Invalid binary graphics frame");
173
+ const length = new DataView(data.buffer, data.byteOffset, data.byteLength).getUint32(4, false);
174
+ if (length < 1 || length > TERMINAL_GRAPHICS_BINARY_HEADER_BYTES || 8 + length >= data.length)
175
+ throw new Error("Invalid binary graphics header");
176
+ const header = TerminalGraphicsBinaryHeaderSchemaZ.parse(
177
+ JSON.parse(new TextDecoder("utf-8", { fatal: true }).decode(data.subarray(8, 8 + length))),
178
+ );
179
+ const bytes = data.subarray(8 + length);
180
+ if (
181
+ bytes.length > TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES ||
182
+ header.blob.offset + bytes.length > header.blob.total
183
+ )
184
+ throw new Error("Invalid binary graphics payload");
185
+ return { header, bytes };
186
+ }
@@ -0,0 +1,153 @@
1
+ import { z } from "zod";
2
+ import {
3
+ TerminalReplicaSnapshotSchemaZ,
4
+ TerminalReplicaPatchPayloadSchemaZ,
5
+ TerminalReplicaTombstonePayloadSchemaZ,
6
+ } from "./terminal-replica.ts";
7
+ import { TerminalAttachmentSemanticPaneIdSchemaZ } from "./semantic-identity.ts";
8
+ import { SessionRuntimeGenerationSchemaZ } from "./session-runtime.ts";
9
+ import { WorkspaceIdSchemaZ } from "./workspace-state.ts";
10
+ import {
11
+ TERMINAL_DELIVERY_CHUNK_BYTES,
12
+ TERMINAL_DELIVERY_MAX_REPRESENTATION_BYTES,
13
+ } from "./terminal-delivery.ts";
14
+
15
+ /** Bundles111/112 include109/110 respectively; the old-compatible encoding offer stays compact. */
16
+ export const TERMINAL_PROJECTION_CAPABILITY_VERSION = 111;
17
+ export const TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION = 112;
18
+ const Hash = z.string().regex(/^[0-9a-f]{16}$/u);
19
+ const Revision = z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER);
20
+ export const TerminalDeferredHistorySchemaZ = z
21
+ .object({ rows: z.number().int().min(0).max(10000), digest: Hash })
22
+ .strict();
23
+ export type TerminalDeferredHistory = z.infer<typeof TerminalDeferredHistorySchemaZ>;
24
+ const ProjectionModes = TerminalReplicaSnapshotSchemaZ.shape.modes.extend({
25
+ alternateScreen: z.literal(true),
26
+ });
27
+ /** A viewport projection is not a materialized canonical snapshot. History is deliberately absent. */
28
+ export const TerminalProjectionSnapshotSchemaZ = TerminalReplicaSnapshotSchemaZ.omit({
29
+ history: true,
30
+ })
31
+ .extend({
32
+ cols: z.number().int().min(1).max(4096),
33
+ rows: z.number().int().min(1).max(4096),
34
+ grid: TerminalReplicaSnapshotSchemaZ.shape.grid.max(4096),
35
+ modes: ProjectionModes,
36
+ placements: TerminalReplicaSnapshotSchemaZ.shape.placements.max(4096),
37
+ deferredHistory: TerminalDeferredHistorySchemaZ,
38
+ })
39
+ .strict()
40
+ .superRefine((value, ctx) => {
41
+ if (
42
+ value.grid.length !== value.rows ||
43
+ value.grid.some((row) => row.cells.length !== value.cols) ||
44
+ value.cols * value.rows > 1_000_000
45
+ )
46
+ ctx.addIssue({ code: "custom", message: "Invalid projection grid dimensions" });
47
+ if (
48
+ value.cursor.x >= value.cols ||
49
+ value.cursor.y >= value.rows ||
50
+ value.placements.some((p) => p.row + p.rows > value.rows || p.column + p.columns > value.cols)
51
+ )
52
+ ctx.addIssue({ code: "custom", message: "Projection metadata outside viewport" });
53
+ });
54
+ export type TerminalProjectionSnapshot = z.infer<typeof TerminalProjectionSnapshotSchemaZ>;
55
+ export const TerminalProjectionPatchSchemaZ = TerminalReplicaPatchPayloadSchemaZ.omit({
56
+ history: true,
57
+ historyDelta: true,
58
+ })
59
+ .extend({
60
+ modes: ProjectionModes.optional(),
61
+ dimensions: z
62
+ .object({ cols: z.number().int().min(1).max(4096), rows: z.number().int().min(1).max(4096) })
63
+ .strict()
64
+ .optional(),
65
+ placements: TerminalReplicaSnapshotSchemaZ.shape.placements.max(4096).optional(),
66
+ deferredHistory: TerminalDeferredHistorySchemaZ,
67
+ rows: TerminalReplicaPatchPayloadSchemaZ.shape.rows.max(4096),
68
+ })
69
+ .strict();
70
+ export type TerminalProjectionPatch = z.infer<typeof TerminalProjectionPatchSchemaZ>;
71
+ export const TerminalProjectionPayloadSchemaZ = z
72
+ .discriminatedUnion("frame", [
73
+ z
74
+ .object({
75
+ frame: z.literal("seed"),
76
+ revision: Revision,
77
+ projection: TerminalProjectionSnapshotSchemaZ,
78
+ })
79
+ .strict(),
80
+ z
81
+ .object({
82
+ frame: z.literal("patch"),
83
+ baseRevision: Revision,
84
+ revision: Revision,
85
+ patch: TerminalProjectionPatchSchemaZ,
86
+ })
87
+ .strict(),
88
+ z
89
+ .object({
90
+ frame: z.literal("tombstone"),
91
+ baseRevision: Revision,
92
+ revision: Revision,
93
+ tombstone: TerminalReplicaTombstonePayloadSchemaZ,
94
+ })
95
+ .strict(),
96
+ ])
97
+ .superRefine((value, ctx) => {
98
+ if (value.frame !== "seed" && value.revision <= value.baseRevision)
99
+ ctx.addIssue({ code: "custom", message: "Projection revision must advance" });
100
+ });
101
+ export type TerminalProjectionPayload = z.infer<typeof TerminalProjectionPayloadSchemaZ>;
102
+ const Address = z.object({
103
+ workspaceName: WorkspaceIdSchemaZ,
104
+ semanticPaneId: TerminalAttachmentSemanticPaneIdSchemaZ,
105
+ generation: SessionRuntimeGenerationSchemaZ,
106
+ incarnation: z.string().min(1).max(256),
107
+ deliveryNonce: z.uuid(),
108
+ });
109
+ const Proof = Address.extend({
110
+ transactionId: z.uuid(),
111
+ /** Source canonical root/revision; receipt does not assert history materialization. */
112
+ canonicalRevision: Revision,
113
+ canonicalStateHash: Hash,
114
+ projectionStateHash: Hash,
115
+ representationHash: Hash,
116
+ });
117
+ export const TerminalProjectionEnvelopeSchemaZ = Proof.extend({
118
+ type: z.literal("terminal.projection"),
119
+ performanceTraceId: z.uuid().optional(),
120
+ protocolVersion: z.literal(1),
121
+ encoding: z.literal("alternate-projection-binary-v1"),
122
+ frame: z.enum(["seed", "patch", "tombstone"]),
123
+ baseRevision: Revision.nullable(),
124
+ representationBytes: z.number().int().min(1).max(TERMINAL_DELIVERY_MAX_REPRESENTATION_BYTES),
125
+ chunkCount: z.number().int().min(1).max(256),
126
+ canonicalEquivalent: z.literal(false),
127
+ history: z.literal("deferred"),
128
+ richPlacements: z.boolean(),
129
+ })
130
+ .strict()
131
+ .superRefine((value, ctx) => {
132
+ if (value.chunkCount !== Math.ceil(value.representationBytes / TERMINAL_DELIVERY_CHUNK_BYTES))
133
+ ctx.addIssue({ code: "custom", message: "Projection chunk count mismatch" });
134
+ if (
135
+ value.frame === "seed"
136
+ ? value.baseRevision !== null
137
+ : value.baseRevision === null || value.canonicalRevision <= value.baseRevision
138
+ )
139
+ ctx.addIssue({ code: "custom", message: "Projection base revision mismatch" });
140
+ });
141
+ export type TerminalProjectionEnvelope = z.infer<typeof TerminalProjectionEnvelopeSchemaZ>;
142
+ /** This is never a TerminalDeliveryAck and must not advance a complete baseline. */
143
+ export const TerminalProjectionReceiptSchemaZ = Proof.extend({
144
+ type: z.literal("terminal.projection.receipt"),
145
+ }).strict();
146
+ export type TerminalProjectionReceipt = z.infer<typeof TerminalProjectionReceiptSchemaZ>;
147
+ export const TerminalProjectionMaterializeSchemaZ = Address.extend({
148
+ type: z.literal("terminal.projection.materialize"),
149
+ requestId: z.uuid(),
150
+ canonicalRevision: Revision,
151
+ projectionStateHash: Hash,
152
+ }).strict();
153
+ export type TerminalProjectionMaterialize = z.infer<typeof TerminalProjectionMaterializeSchemaZ>;