tmux-ide 2.9.10 → 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.
- package/bin/cli.js +4321 -2596
- package/package.json +1 -1
- package/packages/contracts/src/index.ts +4 -0
- package/packages/contracts/src/pane-stream.ts +72 -0
- package/packages/contracts/src/terminal-delivery-binary-chunk.ts +55 -0
- package/packages/contracts/src/terminal-delivery.ts +17 -2
- package/packages/contracts/src/terminal-graphics.ts +68 -0
- package/packages/contracts/src/terminal-projection.ts +153 -0
- package/packages/core/src/fixtures/terminal-projection-vectors.json +585 -0
- package/packages/core/src/index.ts +2 -0
- package/packages/core/src/terminal-delivery-binary.ts +160 -0
- package/packages/core/src/terminal-delivery.ts +371 -16
- package/packages/core/src/terminal-projection-vectors.mjs +82 -0
- package/packages/core/src/terminal-projection.ts +476 -0
- package/packages/core/src/terminal-replica-hash-cache.ts +8 -1
- package/packages/core/src/terminal-replica.ts +1 -1
- package/packages/daemon/dist/lib/daemon-embed.js +39 -3
- package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +1 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +1 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
- package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
- package/packages/daemon/dist/terminal/graphics/local-image-owner.js +330 -0
- package/packages/daemon/dist/terminal/graphics/local-image-store.js +255 -0
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +12 -2
- package/packages/daemon/dist/terminal/mirror/session-channel.js +46 -23
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +190 -19
- package/packages/daemon/dist/terminal/pane-stream/runtime.js +1 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +19 -6
- package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +146 -16
- package/packages/daemon/dist/terminal/session-runtime/terminal-projection-delivery.js +32 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-interpreter.js +95 -36
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +5 -0
- package/packages/daemon/dist/terminal/session-runtime/transport-binding.js +2 -2
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +4 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-generation-starter.js +16 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-agent-navigation.js +13 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +12 -4
- package/packages/daemon/dist/tui/mirror/runtime/application-retained-agent.js +18 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +90 -5
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +13 -1
- package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +2 -2
- package/packages/daemon/dist/tui/mirror/runtime/renderer-probe.js +195 -0
- package/packages/daemon/dist/tui/mirror/runtime/runtime-layout-presentation.js +6 -3
- package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +7 -2
- package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -0
- package/packages/daemon/dist/tui/mirror/runtime/terminal-selection-waiter.js +36 -0
- package/packages/daemon/dist/tui/mirror/semantic-pane-render-source.js +4 -0
- package/packages/daemon/src/lib/daemon-embed.ts +38 -2
- package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +3 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +3 -0
- package/packages/daemon/src/terminal/graphics/local-image-owner.ts +346 -0
- package/packages/daemon/src/terminal/graphics/local-image-store.ts +286 -0
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +19 -2
- package/packages/daemon/src/terminal/mirror/session-channel.ts +57 -9
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +248 -16
- package/packages/daemon/src/terminal/pane-stream/runtime.ts +3 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +25 -3
- package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +1 -1
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +216 -22
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-observation-identity.ts +7 -4
- package/packages/daemon/src/terminal/session-runtime/terminal-projection-delivery.ts +59 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-interpreter.ts +102 -44
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +6 -0
- package/packages/daemon/src/terminal/session-runtime/transport-binding.ts +3 -0
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +6 -0
- package/packages/daemon/src/tui/mirror/runtime/application-generation-starter.ts +17 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-agent-navigation.ts +15 -0
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +18 -4
- package/packages/daemon/src/tui/mirror/runtime/application-retained-agent.ts +29 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +89 -6
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +20 -1
- package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +2 -2
- package/packages/daemon/src/tui/mirror/runtime/renderer-probe.ts +257 -0
- package/packages/daemon/src/tui/mirror/runtime/runtime-layout-presentation.ts +7 -4
- package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +9 -1
- package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +15 -0
- package/packages/daemon/src/tui/mirror/runtime/terminal-selection-waiter.ts +36 -0
- package/packages/daemon/src/tui/mirror/semantic-pane-render-source.ts +4 -0
- package/packages/daemon-client/src/pane-stream-client.ts +92 -1
package/package.json
CHANGED
|
@@ -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";
|
|
@@ -230,6 +236,18 @@ export type PaneStreamRedeemFrame = z.infer<typeof PaneStreamRedeemFrameSchemaZ>
|
|
|
230
236
|
/** @deprecated Import SessionRuntimeTerminalKeyNameSchemaZ for new code. */
|
|
231
237
|
export const PaneStreamKeyNameSchemaZ = SessionRuntimeTerminalKeyNameSchemaZ;
|
|
232
238
|
|
|
239
|
+
/** Negotiated only with callers offering this additive delivery capability. */
|
|
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;
|
|
243
|
+
export const PaneStreamInputLifetimeSchemaZ = z
|
|
244
|
+
.object({
|
|
245
|
+
generation: z.string().min(1).max(256),
|
|
246
|
+
incarnation: z.string().min(1).max(256),
|
|
247
|
+
})
|
|
248
|
+
.strict();
|
|
249
|
+
export type PaneStreamInputLifetime = z.infer<typeof PaneStreamInputLifetimeSchemaZ>;
|
|
250
|
+
|
|
233
251
|
const PaneStreamInputFrameMetadataShape = {
|
|
234
252
|
type: z.literal("input"),
|
|
235
253
|
pane: PaneStreamSemanticPaneIdSchemaZ,
|
|
@@ -237,12 +255,14 @@ const PaneStreamInputFrameMetadataShape = {
|
|
|
237
255
|
/** Opt-in controlled next-output probe; not a general causal assertion. */
|
|
238
256
|
performanceTraceId: z.uuid().optional(),
|
|
239
257
|
causalProbe: CausalCellProbeV1SchemaZ.optional(),
|
|
258
|
+
expectedLifetime: PaneStreamInputLifetimeSchemaZ.optional(),
|
|
240
259
|
} as const;
|
|
241
260
|
|
|
242
261
|
export const PaneStreamInputFrameSchemaZ = z
|
|
243
262
|
.discriminatedUnion("kind", [
|
|
244
263
|
SessionRuntimeTerminalTextInputSchemaZ.extend(PaneStreamInputFrameMetadataShape),
|
|
245
264
|
SessionRuntimeTerminalKeyInputSchemaZ.extend(PaneStreamInputFrameMetadataShape),
|
|
265
|
+
SessionRuntimeTerminalBytesInputSchemaZ.extend(PaneStreamInputFrameMetadataShape),
|
|
246
266
|
])
|
|
247
267
|
.superRefine((value, context) => {
|
|
248
268
|
if (!value.causalProbe) return;
|
|
@@ -268,6 +288,18 @@ export type PaneStreamConsumedFrame = z.infer<typeof PaneStreamConsumedFrameSche
|
|
|
268
288
|
export const PaneStreamTerminalDeliveryAckFrameSchemaZ = z
|
|
269
289
|
.object({ type: z.literal("terminal-delivery-ack"), ack: TerminalDeliveryAckSchemaZ })
|
|
270
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();
|
|
271
303
|
export const PaneStreamTerminalDeliveryNackFrameSchemaZ = z
|
|
272
304
|
.object({ type: z.literal("terminal-delivery-nack"), nack: TerminalDeliveryNackSchemaZ })
|
|
273
305
|
.strict();
|
|
@@ -352,6 +384,7 @@ export const PaneStreamClockProbeFrameSchemaZ = z
|
|
|
352
384
|
export const PaneStreamTerminalGraphicsRequestFrameSchemaZ = z
|
|
353
385
|
.object({
|
|
354
386
|
type: z.literal("terminal-graphics-request"),
|
|
387
|
+
preferInline: z.literal(true).optional(),
|
|
355
388
|
pane: PaneStreamSemanticPaneIdSchemaZ,
|
|
356
389
|
digests: z
|
|
357
390
|
.array(TerminalGraphicsDigestSchemaZ)
|
|
@@ -360,6 +393,15 @@ export const PaneStreamTerminalGraphicsRequestFrameSchemaZ = z
|
|
|
360
393
|
})
|
|
361
394
|
.strict();
|
|
362
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
|
+
|
|
363
405
|
/** The renderer's terminal cell size in device pixels (see capability 103). */
|
|
364
406
|
export const PaneStreamTerminalCellPixelsFrameSchemaZ = z
|
|
365
407
|
.object({
|
|
@@ -380,6 +422,8 @@ export const PaneStreamClientFrameSchemaZ = z.union([
|
|
|
380
422
|
PaneStreamInputFrameSchemaZ,
|
|
381
423
|
PaneStreamConsumedFrameSchemaZ,
|
|
382
424
|
PaneStreamTerminalDeliveryAckFrameSchemaZ,
|
|
425
|
+
PaneStreamTerminalProjectionReceiptFrameSchemaZ,
|
|
426
|
+
PaneStreamTerminalProjectionMaterializeFrameSchemaZ,
|
|
383
427
|
PaneStreamTerminalDeliveryNackFrameSchemaZ,
|
|
384
428
|
PaneStreamTerminalDeliveryVisibilityFrameSchemaZ,
|
|
385
429
|
PaneStreamSemanticIntentFrameSchemaZ,
|
|
@@ -390,6 +434,7 @@ export const PaneStreamClientFrameSchemaZ = z.union([
|
|
|
390
434
|
PaneStreamAuthorityReleaseFrameSchemaZ,
|
|
391
435
|
PaneStreamClockProbeFrameSchemaZ,
|
|
392
436
|
PaneStreamTerminalGraphicsRequestFrameSchemaZ,
|
|
437
|
+
PaneStreamTerminalGraphicsLocalReleaseFrameSchemaZ,
|
|
393
438
|
PaneStreamTerminalCellPixelsFrameSchemaZ,
|
|
394
439
|
]);
|
|
395
440
|
export type PaneStreamClientFrame = z.infer<typeof PaneStreamClientFrameSchemaZ>;
|
|
@@ -409,6 +454,8 @@ const CellCoordinateSchemaZ = z.number().int().min(0).max(PANE_STREAM_MAX_GRID_C
|
|
|
409
454
|
export const PaneStreamReadyFrameSchemaZ = z
|
|
410
455
|
.object({
|
|
411
456
|
type: z.literal("ready"),
|
|
457
|
+
inputLifetime: z.literal(true).optional(),
|
|
458
|
+
inputBytes: z.literal(true).optional(),
|
|
412
459
|
protocolVersion: z.literal(PANE_STREAM_PROTOCOL_VERSION),
|
|
413
460
|
daemonInstanceId: BoundedIdentitySchemaZ,
|
|
414
461
|
requestId: z.uuid(),
|
|
@@ -666,6 +713,13 @@ export const PaneStreamTerminalDeliveryEnvelopeFrameSchemaZ = z
|
|
|
666
713
|
envelope: TerminalDeliveryEnvelopeSchemaZ,
|
|
667
714
|
})
|
|
668
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();
|
|
669
723
|
export const PaneStreamTerminalDeliveryChunkFrameSchemaZ = z
|
|
670
724
|
.object({
|
|
671
725
|
type: z.literal("terminal-delivery-chunk"),
|
|
@@ -697,6 +751,22 @@ export const PaneStreamTerminalGraphicsBlobFrameSchemaZ = z
|
|
|
697
751
|
data: Base64SchemaZ(Math.ceil(TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES / 3) * 4).min(4),
|
|
698
752
|
})
|
|
699
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
|
+
|
|
700
770
|
export const PaneStreamSemanticIntentAckFrameSchemaZ = z
|
|
701
771
|
.object({
|
|
702
772
|
type: z.literal("semantic-intent-ack"),
|
|
@@ -799,10 +869,12 @@ export const PaneStreamServerFrameSchemaZ = z.discriminatedUnion("type", [
|
|
|
799
869
|
PaneStreamCausalCellFailureFrameSchemaZ,
|
|
800
870
|
PaneStreamTerminalDeliveryReadyFrameSchemaZ,
|
|
801
871
|
PaneStreamTerminalDeliveryEnvelopeFrameSchemaZ,
|
|
872
|
+
PaneStreamTerminalProjectionEnvelopeFrameSchemaZ,
|
|
802
873
|
PaneStreamTerminalDeliveryChunkFrameSchemaZ,
|
|
803
874
|
PaneStreamTerminalDeliveryFaultFrameSchemaZ,
|
|
804
875
|
PaneStreamTerminalGraphicsFrameSchemaZ,
|
|
805
876
|
PaneStreamTerminalGraphicsBlobFrameSchemaZ,
|
|
877
|
+
PaneStreamTerminalGraphicsLocalFrameSchemaZ,
|
|
806
878
|
PaneStreamSemanticIntentAckFrameSchemaZ,
|
|
807
879
|
PaneStreamViewportAckFrameSchemaZ,
|
|
808
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 =
|
|
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>;
|