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
|
@@ -30,6 +30,7 @@ import {
|
|
|
30
30
|
type CanonicalTerminalReplicaUpdate,
|
|
31
31
|
type CausalCellFailureReasonV1,
|
|
32
32
|
type CausalCellProbeV1,
|
|
33
|
+
type PaneStreamInputLifetime,
|
|
33
34
|
type TerminalDeliveryAck,
|
|
34
35
|
type TerminalDeliveryNack,
|
|
35
36
|
type TerminalDeliveryOffer,
|
|
@@ -244,6 +245,7 @@ export interface SessionRuntimeConsumer {
|
|
|
244
245
|
performanceTraceId?: string,
|
|
245
246
|
causalProbe?: CausalCellProbeV1,
|
|
246
247
|
onCausalResult?: (result: CausalCellLedgerResult) => void,
|
|
248
|
+
expectedLifetime?: PaneStreamInputLifetime,
|
|
247
249
|
): void;
|
|
248
250
|
failCausalCellProbe(
|
|
249
251
|
semanticPaneId: string,
|
|
@@ -896,13 +898,16 @@ export class SessionRuntimeRegistry implements PaneStreamMirror {
|
|
|
896
898
|
return result;
|
|
897
899
|
}
|
|
898
900
|
|
|
899
|
-
async describeSessionAuthority(
|
|
901
|
+
async describeSessionAuthority(
|
|
902
|
+
session: string,
|
|
903
|
+
expectedSemanticPaneIds?: readonly string[],
|
|
904
|
+
): Promise<{
|
|
900
905
|
readonly description: MirrorSessionDescription;
|
|
901
906
|
readonly runtimeSessionId: string;
|
|
902
907
|
}> {
|
|
903
908
|
const runtime = this.#runtime(session);
|
|
904
909
|
await runtime.whenReady();
|
|
905
|
-
const authority = await this.#mirror.describeSessionAuthority(session);
|
|
910
|
+
const authority = await this.#mirror.describeSessionAuthority(session, expectedSemanticPaneIds);
|
|
906
911
|
if (this.#disposed || this.#sessions.get(session) !== runtime) {
|
|
907
912
|
const error = new Error(`session runtime ${session} changed topology`);
|
|
908
913
|
error.name = "MirrorTopologyChangedError";
|
|
@@ -1589,6 +1594,7 @@ class SessionRuntime {
|
|
|
1589
1594
|
performanceTraceId?: string,
|
|
1590
1595
|
rawCausalProbe?: CausalCellProbeV1,
|
|
1591
1596
|
onCausalResult?: (result: CausalCellLedgerResult) => void,
|
|
1597
|
+
expectedLifetime?: PaneStreamInputLifetime,
|
|
1592
1598
|
): void {
|
|
1593
1599
|
this.assertController(lease, clientId);
|
|
1594
1600
|
const inputLease = this.#authority.leaseFor(clientId, "input");
|
|
@@ -1626,7 +1632,10 @@ class SessionRuntime {
|
|
|
1626
1632
|
})
|
|
1627
1633
|
: null;
|
|
1628
1634
|
const started = this.#observability.enabled ? this.#observability.nowMicros() : 0;
|
|
1629
|
-
const replicaOwner =
|
|
1635
|
+
const replicaOwner = expectedLifetime
|
|
1636
|
+
? this.#terminalReplicas.get(semanticPaneId)
|
|
1637
|
+
: this.#terminalReplicaOwner(semanticPaneId);
|
|
1638
|
+
if (!replicaOwner) throw new Error("Input target lifetime unavailable");
|
|
1630
1639
|
if (causalProbe) {
|
|
1631
1640
|
this.#activeCausalCellProbes += 1;
|
|
1632
1641
|
let settled = false;
|
|
@@ -1649,6 +1658,15 @@ class SessionRuntime {
|
|
|
1649
1658
|
this.#outputTraces.arm(semanticPaneId, trace);
|
|
1650
1659
|
}
|
|
1651
1660
|
try {
|
|
1661
|
+
// Synchronous final admission: never create a new owner to satisfy a stale
|
|
1662
|
+
// held-input fence, and never await between this check and mirror dispatch.
|
|
1663
|
+
if (
|
|
1664
|
+
expectedLifetime &&
|
|
1665
|
+
(expectedLifetime.generation !== this.generation ||
|
|
1666
|
+
this.#terminalReplicas.get(semanticPaneId) !== replicaOwner ||
|
|
1667
|
+
replicaOwner.inputAdmissionIncarnation() !== expectedLifetime.incarnation)
|
|
1668
|
+
)
|
|
1669
|
+
throw new Error("Input target lifetime changed");
|
|
1652
1670
|
if (input.kind === "text")
|
|
1653
1671
|
this.#mirror.sendText(
|
|
1654
1672
|
this.session,
|
|
@@ -2323,6 +2341,7 @@ class SessionRuntimeConsumerImpl implements SessionRuntimeConsumer {
|
|
|
2323
2341
|
performanceTraceId?: string,
|
|
2324
2342
|
causalProbe?: CausalCellProbeV1,
|
|
2325
2343
|
onCausalResult?: (result: CausalCellLedgerResult) => void,
|
|
2344
|
+
expectedLifetime?: PaneStreamInputLifetime,
|
|
2326
2345
|
): void {
|
|
2327
2346
|
this.#assertOpen();
|
|
2328
2347
|
this.#runtime.sendInput(
|
|
@@ -2333,6 +2352,7 @@ class SessionRuntimeConsumerImpl implements SessionRuntimeConsumer {
|
|
|
2333
2352
|
performanceTraceId,
|
|
2334
2353
|
causalProbe,
|
|
2335
2354
|
onCausalResult,
|
|
2355
|
+
expectedLifetime,
|
|
2336
2356
|
);
|
|
2337
2357
|
}
|
|
2338
2358
|
|
|
@@ -2452,6 +2472,8 @@ class SessionRuntimeConsumerImpl implements SessionRuntimeConsumer {
|
|
|
2452
2472
|
negotiation: upstream.negotiation,
|
|
2453
2473
|
closed: upstream.closed,
|
|
2454
2474
|
ack: (ack: TerminalDeliveryAck) => upstream.ack(ack),
|
|
2475
|
+
...(upstream.projectionReceipt ? { projectionReceipt: upstream.projectionReceipt } : {}),
|
|
2476
|
+
...(upstream.materialize ? { materialize: upstream.materialize } : {}),
|
|
2455
2477
|
nack: (nack: TerminalDeliveryNack) => upstream.nack(nack),
|
|
2456
2478
|
setVisibility: (visibility: TerminalDeliveryVisibility) => upstream.setVisibility(visibility),
|
|
2457
2479
|
close: async () => {
|
|
@@ -81,7 +81,7 @@ export interface SessionRuntimeStageSpan {
|
|
|
81
81
|
readonly attemptedLegacySeedSizeCapped?: boolean;
|
|
82
82
|
readonly attemptedCompactPatchBytes?: number | null;
|
|
83
83
|
readonly attemptedCompactSeedBytes?: number | null;
|
|
84
|
-
readonly selectedEncoding?: "semantic-v1" | "semantic-compact-v1";
|
|
84
|
+
readonly selectedEncoding?: "semantic-v1" | "semantic-compact-v1" | "semantic-binary-v1";
|
|
85
85
|
readonly selectionStatus?:
|
|
86
86
|
| "patch-preferred"
|
|
87
87
|
| "seed-preferred"
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import { prepareProjection, type ProjectionBaseline } from "./terminal-projection-delivery.ts";
|
|
2
|
+
import {
|
|
3
|
+
TerminalProjectionEnvelopeSchemaZ,
|
|
4
|
+
TerminalProjectionReceiptSchemaZ,
|
|
5
|
+
TerminalProjectionMaterializeSchemaZ,
|
|
6
|
+
type TerminalProjectionEnvelope,
|
|
7
|
+
type TerminalProjectionReceipt,
|
|
8
|
+
type TerminalProjectionMaterialize,
|
|
9
|
+
type TerminalProjectionPayload,
|
|
10
|
+
} from "@tmux-ide/contracts";
|
|
11
|
+
import { encodeTerminalProjectionCooperatively } from "@tmux-ide/core";
|
|
12
|
+
type DeliveryEnvelope = TerminalDeliveryEnvelope | TerminalProjectionEnvelope;
|
|
1
13
|
import type { KittyImage } from "../graphics/kitty-graphics.ts";
|
|
2
14
|
import { StockCaptureTabUnavailableError } from "./stock-capture-fidelity.ts";
|
|
3
15
|
import {
|
|
@@ -37,6 +49,8 @@ import {
|
|
|
37
49
|
applyTerminalReplicaUpdate,
|
|
38
50
|
applyTerminalReplicaUpdateCooperatively,
|
|
39
51
|
terminalReplicaUpdateNeedsCooperativeReduction,
|
|
52
|
+
encodeBinarySemanticTerminalUpdate,
|
|
53
|
+
encodeBinarySemanticTerminalUpdateCooperatively,
|
|
40
54
|
encodeCompactSemanticTerminalUpdate,
|
|
41
55
|
encodeCompactSemanticTerminalUpdateCooperatively,
|
|
42
56
|
terminalSemanticUpdateNeedsCooperativeEncoding,
|
|
@@ -124,6 +138,8 @@ export interface TerminalDeliveryConnection {
|
|
|
124
138
|
readonly closed: Promise<"closed" | "sink-failed">;
|
|
125
139
|
readonly negotiation: TerminalDeliveryNegotiationResult;
|
|
126
140
|
ack(ack: TerminalDeliveryAck): void;
|
|
141
|
+
projectionReceipt?(receipt: TerminalProjectionReceipt): void;
|
|
142
|
+
materialize?(request: TerminalProjectionMaterialize): void;
|
|
127
143
|
nack(nack: TerminalDeliveryNack): void;
|
|
128
144
|
setVisibility(visibility: TerminalDeliveryVisibility): void;
|
|
129
145
|
close(): Promise<void>;
|
|
@@ -205,9 +221,14 @@ interface ClientState {
|
|
|
205
221
|
visibility: TerminalDeliveryVisibility;
|
|
206
222
|
baselineRevision: number;
|
|
207
223
|
baselineHash: string | null;
|
|
224
|
+
projectionBaseline: ProjectionBaseline | null;
|
|
225
|
+
materializeRequested: boolean;
|
|
226
|
+
lastProjectionReceipt: TerminalProjectionReceipt | null;
|
|
227
|
+
lastMaterialize: TerminalProjectionMaterialize | null;
|
|
208
228
|
reseedRequired: boolean;
|
|
209
229
|
inFlight: {
|
|
210
|
-
envelope:
|
|
230
|
+
envelope: DeliveryEnvelope;
|
|
231
|
+
projection: ProjectionBaseline | null;
|
|
211
232
|
deliveryOrdinal: number;
|
|
212
233
|
bytes: Uint8Array;
|
|
213
234
|
nextChunk: number;
|
|
@@ -226,7 +247,7 @@ interface ClientState {
|
|
|
226
247
|
captureUnavailable: boolean;
|
|
227
248
|
lastAck: TerminalDeliveryAck | null;
|
|
228
249
|
/** Delivery displaced by authoritative source close; its racing ACK is benign. */
|
|
229
|
-
sourceClosedFlight:
|
|
250
|
+
sourceClosedFlight: DeliveryEnvelope | null;
|
|
230
251
|
backgroundTimer: SessionRuntimeTimer | null;
|
|
231
252
|
}
|
|
232
253
|
|
|
@@ -246,6 +267,11 @@ interface PendingClientEncoding {
|
|
|
246
267
|
readonly reseedRequired: boolean;
|
|
247
268
|
readonly traceStarted: number;
|
|
248
269
|
}
|
|
270
|
+
class DeferredProjectionEncoding extends Error {
|
|
271
|
+
constructor(readonly payload: TerminalProjectionPayload) {
|
|
272
|
+
super("Deferred projection encoding");
|
|
273
|
+
}
|
|
274
|
+
}
|
|
249
275
|
class DeferredSeedEncoding extends Error {
|
|
250
276
|
constructor(readonly payload: TerminalSemanticDeliveryPayload) {
|
|
251
277
|
super("Deferred compact seed encoding");
|
|
@@ -254,10 +280,16 @@ class DeferredSeedEncoding extends Error {
|
|
|
254
280
|
|
|
255
281
|
interface CachedRepresentation {
|
|
256
282
|
readonly bytes: Uint8Array;
|
|
257
|
-
readonly encoding?:
|
|
283
|
+
readonly encoding?:
|
|
284
|
+
| "semantic-v1"
|
|
285
|
+
| "semantic-compact-v1"
|
|
286
|
+
| "semantic-binary-v1"
|
|
287
|
+
| "alternate-projection-binary-v1";
|
|
288
|
+
readonly projection?: ProjectionBaseline;
|
|
289
|
+
readonly projectionBaseRevision?: number | null;
|
|
258
290
|
readonly frame: "seed" | "patch" | "tombstone";
|
|
259
291
|
readonly canonicalEquivalent: boolean;
|
|
260
|
-
readonly history: "complete" | "truncated" | "not-applicable";
|
|
292
|
+
readonly history: "complete" | "truncated" | "not-applicable" | "deferred";
|
|
261
293
|
readonly representationHash?: string;
|
|
262
294
|
/** Internal reachability metadata; never serialized onto the wire. */
|
|
263
295
|
readonly cachePaneId?: string;
|
|
@@ -274,7 +306,7 @@ interface CachedRepresentation {
|
|
|
274
306
|
attemptedLegacySeedSizeCapped: boolean;
|
|
275
307
|
attemptedCompactPatchBytes: number | null;
|
|
276
308
|
attemptedCompactSeedBytes: number | null;
|
|
277
|
-
selectedEncoding: "semantic-v1" | "semantic-compact-v1";
|
|
309
|
+
selectedEncoding: "semantic-v1" | "semantic-compact-v1" | "semantic-binary-v1";
|
|
278
310
|
selectionStatus:
|
|
279
311
|
| "patch-preferred"
|
|
280
312
|
| "seed-preferred"
|
|
@@ -432,6 +464,10 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
432
464
|
visibility: "visible",
|
|
433
465
|
baselineRevision: -1,
|
|
434
466
|
baselineHash: null,
|
|
467
|
+
projectionBaseline: null,
|
|
468
|
+
materializeRequested: false,
|
|
469
|
+
lastProjectionReceipt: null,
|
|
470
|
+
lastMaterialize: null,
|
|
435
471
|
reseedRequired: false,
|
|
436
472
|
inFlight: null,
|
|
437
473
|
latestRevision: pane.latest?.update.revision ?? null,
|
|
@@ -456,6 +492,8 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
456
492
|
negotiation,
|
|
457
493
|
closed,
|
|
458
494
|
ack: (ack) => this.#ack(client, ack),
|
|
495
|
+
projectionReceipt: (receipt) => this.#projectionReceipt(client, receipt),
|
|
496
|
+
materialize: (request) => this.#materialize(client, request),
|
|
459
497
|
nack: (nack) => this.#nack(client, nack),
|
|
460
498
|
setVisibility: (visibilityInput) => {
|
|
461
499
|
if (client.closed) return;
|
|
@@ -956,7 +994,10 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
956
994
|
client.lifecycleOpenRecorded = this.#recordDeliveryLifecycle(client, pane, "open");
|
|
957
995
|
if (
|
|
958
996
|
update.type === "terminal.tombstone" &&
|
|
959
|
-
(client.inFlight !== null ||
|
|
997
|
+
(client.inFlight !== null ||
|
|
998
|
+
client.encoding !== null ||
|
|
999
|
+
client.visibility !== "visible" ||
|
|
1000
|
+
client.projectionBaseline !== null)
|
|
960
1001
|
) {
|
|
961
1002
|
this.#fault(client, "source-closed", "Terminal source closed before final state delivery");
|
|
962
1003
|
continue;
|
|
@@ -1079,15 +1120,22 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1079
1120
|
let deferred = false;
|
|
1080
1121
|
let encodedRepresentation: CachedRepresentation | null = null;
|
|
1081
1122
|
let failedSelectionObservation: SemanticSelectionObservation | null = null;
|
|
1082
|
-
let deliveryEnvelope:
|
|
1123
|
+
let deliveryEnvelope: DeliveryEnvelope | null = null;
|
|
1083
1124
|
let deliveryOrdinal: number | null = null;
|
|
1084
1125
|
try {
|
|
1085
1126
|
const representation = this.#representation(client, pane, target, completed?.outcome);
|
|
1086
1127
|
encodedRepresentation = representation;
|
|
1087
1128
|
const transactionId = this.#scheduler.createId();
|
|
1088
1129
|
const chunkCount = Math.max(1, Math.ceil(representation.bytes.byteLength / (256 * 1024)));
|
|
1089
|
-
const envelope =
|
|
1090
|
-
|
|
1130
|
+
const envelope = (
|
|
1131
|
+
representation.projection
|
|
1132
|
+
? TerminalProjectionEnvelopeSchemaZ
|
|
1133
|
+
: TerminalDeliveryEnvelopeSchemaZ
|
|
1134
|
+
).parse({
|
|
1135
|
+
type: representation.projection ? "terminal.projection" : "terminal.delivery",
|
|
1136
|
+
...(representation.projection
|
|
1137
|
+
? { projectionStateHash: representation.projection.hash }
|
|
1138
|
+
: {}),
|
|
1091
1139
|
workspaceName: client.deliveryWorkspaceName,
|
|
1092
1140
|
semanticPaneId: target.update.semanticPaneId,
|
|
1093
1141
|
generation: target.update.generation,
|
|
@@ -1098,7 +1146,11 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1098
1146
|
protocolVersion: 1,
|
|
1099
1147
|
encoding: representation.encoding ?? client.negotiated.encoding,
|
|
1100
1148
|
frame: representation.frame,
|
|
1101
|
-
baseRevision: representation.
|
|
1149
|
+
baseRevision: representation.projection
|
|
1150
|
+
? representation.projectionBaseRevision
|
|
1151
|
+
: representation.frame === "seed"
|
|
1152
|
+
? null
|
|
1153
|
+
: Math.max(0, client.baselineRevision),
|
|
1102
1154
|
canonicalRevision: target.update.revision,
|
|
1103
1155
|
canonicalStateHash: target.update.stateHash,
|
|
1104
1156
|
representationHash:
|
|
@@ -1115,6 +1167,7 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1115
1167
|
registerTerminalDeliveryObservationOrdinal(envelope, deliveryOrdinal);
|
|
1116
1168
|
client.inFlight = {
|
|
1117
1169
|
envelope,
|
|
1170
|
+
projection: representation.projection ?? null,
|
|
1118
1171
|
deliveryOrdinal,
|
|
1119
1172
|
bytes: representation.bytes,
|
|
1120
1173
|
nextChunk: 0,
|
|
@@ -1125,9 +1178,16 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1125
1178
|
if (target.trace?.traceId === pane.pendingDeliveryTrace?.traceId)
|
|
1126
1179
|
pane.pendingDeliveryTrace = null;
|
|
1127
1180
|
} catch (error) {
|
|
1128
|
-
if (error instanceof DeferredSeedEncoding) {
|
|
1181
|
+
if (error instanceof DeferredSeedEncoding || error instanceof DeferredProjectionEncoding) {
|
|
1129
1182
|
deferred = true;
|
|
1130
|
-
this.#startEncoding(
|
|
1183
|
+
this.#startEncoding(
|
|
1184
|
+
client,
|
|
1185
|
+
pane,
|
|
1186
|
+
target,
|
|
1187
|
+
error.payload,
|
|
1188
|
+
traceStarted,
|
|
1189
|
+
error instanceof DeferredProjectionEncoding,
|
|
1190
|
+
);
|
|
1131
1191
|
return;
|
|
1132
1192
|
}
|
|
1133
1193
|
failedSelectionObservation = semanticSelectionObservationFromError(error);
|
|
@@ -1205,8 +1265,9 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1205
1265
|
client: ClientState,
|
|
1206
1266
|
pane: PaneState,
|
|
1207
1267
|
target: RevisionRecord,
|
|
1208
|
-
payload: TerminalSemanticDeliveryPayload,
|
|
1268
|
+
payload: TerminalSemanticDeliveryPayload | TerminalProjectionPayload,
|
|
1209
1269
|
traceStarted: number,
|
|
1270
|
+
projection = false,
|
|
1210
1271
|
): void {
|
|
1211
1272
|
const key = cacheKey([
|
|
1212
1273
|
target.update.workspaceName,
|
|
@@ -1217,6 +1278,9 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1217
1278
|
target.update.stateHash,
|
|
1218
1279
|
client.negotiated.encoding,
|
|
1219
1280
|
client.negotiated.richPlacements ? "rich" : "plain",
|
|
1281
|
+
projection
|
|
1282
|
+
? `projection:${client.projectionBaseline?.revision ?? -1}:${client.projectionBaseline?.hash ?? "none"}`
|
|
1283
|
+
: "complete",
|
|
1220
1284
|
]);
|
|
1221
1285
|
let job = this.#pendingEncodings.get(key);
|
|
1222
1286
|
let created = false;
|
|
@@ -1250,10 +1314,19 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1250
1314
|
};
|
|
1251
1315
|
owned.clients.add(client);
|
|
1252
1316
|
if (!created) return;
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1317
|
+
const options = { signal: owned.abort.signal, yieldControl: () => this.#yieldEncoding(owned) };
|
|
1318
|
+
const result = projection
|
|
1319
|
+
? encodeTerminalProjectionCooperatively(payload as TerminalProjectionPayload, options)
|
|
1320
|
+
: client.negotiated.encoding === "semantic-binary-v1"
|
|
1321
|
+
? encodeBinarySemanticTerminalUpdateCooperatively(
|
|
1322
|
+
payload as TerminalSemanticDeliveryPayload,
|
|
1323
|
+
options,
|
|
1324
|
+
)
|
|
1325
|
+
: encodeCompactSemanticTerminalUpdateCooperatively(
|
|
1326
|
+
payload as TerminalSemanticDeliveryPayload,
|
|
1327
|
+
options,
|
|
1328
|
+
);
|
|
1329
|
+
void result.then(
|
|
1257
1330
|
(bytes) => this.#completeEncoding(owned, { bytes }),
|
|
1258
1331
|
(error) => this.#completeEncoding(owned, { error }),
|
|
1259
1332
|
);
|
|
@@ -1328,6 +1401,32 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1328
1401
|
target: RevisionRecord,
|
|
1329
1402
|
seedOutcome?: SeedEncodingOutcome,
|
|
1330
1403
|
): CachedRepresentation {
|
|
1404
|
+
const snapshotForProjection = target.state.snapshot;
|
|
1405
|
+
if (
|
|
1406
|
+
client.negotiated.alternateScreenProjection &&
|
|
1407
|
+
!client.materializeRequested &&
|
|
1408
|
+
(client.baselineRevision < 0 || client.projectionBaseline !== null) &&
|
|
1409
|
+
snapshotForProjection?.modes.alternateScreen &&
|
|
1410
|
+
snapshotForProjection.history.length > 0
|
|
1411
|
+
) {
|
|
1412
|
+
const { payload, retained } = prepareProjection(
|
|
1413
|
+
snapshotForProjection,
|
|
1414
|
+
target.update.revision,
|
|
1415
|
+
target.update.incarnation,
|
|
1416
|
+
client.reseedRequired ? null : client.projectionBaseline,
|
|
1417
|
+
);
|
|
1418
|
+
if (!seedOutcome) throw new DeferredProjectionEncoding(payload);
|
|
1419
|
+
if ("error" in seedOutcome) throw seedOutcome.error;
|
|
1420
|
+
return {
|
|
1421
|
+
bytes: seedOutcome.bytes,
|
|
1422
|
+
encoding: "alternate-projection-binary-v1",
|
|
1423
|
+
frame: payload.frame,
|
|
1424
|
+
history: "deferred",
|
|
1425
|
+
canonicalEquivalent: false,
|
|
1426
|
+
projection: retained,
|
|
1427
|
+
projectionBaseRevision: payload.frame === "seed" ? null : payload.baseRevision,
|
|
1428
|
+
};
|
|
1429
|
+
}
|
|
1331
1430
|
const key = cacheKey([
|
|
1332
1431
|
target.update.workspaceName,
|
|
1333
1432
|
target.update.semanticPaneId,
|
|
@@ -1351,17 +1450,27 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1351
1450
|
let result: CachedRepresentation | null = null;
|
|
1352
1451
|
if (
|
|
1353
1452
|
client.negotiated.encoding === "semantic-v1" ||
|
|
1354
|
-
client.negotiated.encoding === "semantic-compact-v1"
|
|
1453
|
+
client.negotiated.encoding === "semantic-compact-v1" ||
|
|
1454
|
+
client.negotiated.encoding === "semantic-binary-v1"
|
|
1355
1455
|
) {
|
|
1356
1456
|
const encodeSemantic = (payload: TerminalSemanticDeliveryPayload): Uint8Array => {
|
|
1357
|
-
if (
|
|
1457
|
+
if (
|
|
1458
|
+
client.negotiated.encoding !== "semantic-compact-v1" &&
|
|
1459
|
+
client.negotiated.encoding !== "semantic-binary-v1"
|
|
1460
|
+
)
|
|
1358
1461
|
return encodeSemanticTerminalUpdate(payload);
|
|
1359
|
-
if (
|
|
1462
|
+
if (
|
|
1463
|
+
payload.frame === "seed" &&
|
|
1464
|
+
(client.negotiated.encoding === "semantic-binary-v1" ||
|
|
1465
|
+
terminalSemanticUpdateNeedsCooperativeEncoding(payload))
|
|
1466
|
+
) {
|
|
1360
1467
|
if (!seedOutcome) throw new DeferredSeedEncoding(payload);
|
|
1361
1468
|
if ("error" in seedOutcome) throw seedOutcome.error;
|
|
1362
1469
|
return seedOutcome.bytes;
|
|
1363
1470
|
}
|
|
1364
|
-
return
|
|
1471
|
+
return client.negotiated.encoding === "semantic-binary-v1"
|
|
1472
|
+
? encodeBinarySemanticTerminalUpdate(payload)
|
|
1473
|
+
: encodeCompactSemanticTerminalUpdate(payload);
|
|
1365
1474
|
};
|
|
1366
1475
|
if (client.reseedRequired) {
|
|
1367
1476
|
const payload = semanticSeed(target);
|
|
@@ -1693,6 +1802,10 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1693
1802
|
return;
|
|
1694
1803
|
}
|
|
1695
1804
|
const envelope = flight.envelope;
|
|
1805
|
+
if (envelope.type !== "terminal.delivery") {
|
|
1806
|
+
this.#fault(client, "protocol-violation", "Canonical ACK cannot acknowledge a projection");
|
|
1807
|
+
return;
|
|
1808
|
+
}
|
|
1696
1809
|
if (
|
|
1697
1810
|
flight.nextChunk !== envelope.chunkCount ||
|
|
1698
1811
|
ack.workspaceName !== envelope.workspaceName ||
|
|
@@ -1712,6 +1825,8 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1712
1825
|
this.#maxSlowClientMs,
|
|
1713
1826
|
this.#scheduler.nowMs() - flight.sentAt,
|
|
1714
1827
|
);
|
|
1828
|
+
client.projectionBaseline = null;
|
|
1829
|
+
client.lastProjectionReceipt = null;
|
|
1715
1830
|
client.baselineRevision = ack.canonicalRevision;
|
|
1716
1831
|
client.baselineHash = ack.canonicalStateHash;
|
|
1717
1832
|
client.reseedRequired = false;
|
|
@@ -1769,6 +1884,85 @@ export class SessionRuntimeTerminalDeliveryHub {
|
|
|
1769
1884
|
this.#recordDeliveryStatus(client, this.#panes.get(client.paneId));
|
|
1770
1885
|
}
|
|
1771
1886
|
|
|
1887
|
+
#projectionReceipt(client: ClientState, input: TerminalProjectionReceipt): void {
|
|
1888
|
+
const receipt = TerminalProjectionReceiptSchemaZ.parse(input);
|
|
1889
|
+
if (
|
|
1890
|
+
client.lastProjectionReceipt &&
|
|
1891
|
+
JSON.stringify(client.lastProjectionReceipt) === JSON.stringify(receipt)
|
|
1892
|
+
)
|
|
1893
|
+
return;
|
|
1894
|
+
const flight = client.inFlight;
|
|
1895
|
+
const envelope = flight?.envelope;
|
|
1896
|
+
if (
|
|
1897
|
+
!flight ||
|
|
1898
|
+
!flight.projection ||
|
|
1899
|
+
envelope?.type !== "terminal.projection" ||
|
|
1900
|
+
flight.nextChunk !== envelope.chunkCount ||
|
|
1901
|
+
receipt.workspaceName !== envelope.workspaceName ||
|
|
1902
|
+
receipt.semanticPaneId !== envelope.semanticPaneId ||
|
|
1903
|
+
receipt.generation !== envelope.generation ||
|
|
1904
|
+
receipt.incarnation !== envelope.incarnation ||
|
|
1905
|
+
receipt.deliveryNonce !== envelope.deliveryNonce ||
|
|
1906
|
+
receipt.transactionId !== envelope.transactionId ||
|
|
1907
|
+
receipt.canonicalRevision !== envelope.canonicalRevision ||
|
|
1908
|
+
receipt.canonicalStateHash !== envelope.canonicalStateHash ||
|
|
1909
|
+
receipt.projectionStateHash !== envelope.projectionStateHash ||
|
|
1910
|
+
receipt.representationHash !== envelope.representationHash
|
|
1911
|
+
) {
|
|
1912
|
+
this.#fault(
|
|
1913
|
+
client,
|
|
1914
|
+
"protocol-violation",
|
|
1915
|
+
"Projection receipt does not identify the in-flight projection",
|
|
1916
|
+
);
|
|
1917
|
+
return;
|
|
1918
|
+
}
|
|
1919
|
+
client.projectionBaseline = flight.projection;
|
|
1920
|
+
client.lastProjectionReceipt = receipt;
|
|
1921
|
+
client.reseedRequired = false;
|
|
1922
|
+
client.inFlight = null;
|
|
1923
|
+
if (client.latestRevision === receipt.canonicalRevision) client.latestRevision = null;
|
|
1924
|
+
if (client.materializeRequested)
|
|
1925
|
+
client.latestRevision = this.#panes.get(client.paneId)?.latest?.update.revision ?? null;
|
|
1926
|
+
this.#pruneCanonicalRevisions(client.paneId);
|
|
1927
|
+
this.#schedule(client);
|
|
1928
|
+
this.#recordDeliveryStatus(client, this.#panes.get(client.paneId));
|
|
1929
|
+
}
|
|
1930
|
+
|
|
1931
|
+
#materialize(client: ClientState, input: TerminalProjectionMaterialize): void {
|
|
1932
|
+
const request = TerminalProjectionMaterializeSchemaZ.parse(input);
|
|
1933
|
+
if (
|
|
1934
|
+
client.lastMaterialize &&
|
|
1935
|
+
JSON.stringify(client.lastMaterialize) === JSON.stringify(request)
|
|
1936
|
+
)
|
|
1937
|
+
return;
|
|
1938
|
+
const baseline = client.projectionBaseline;
|
|
1939
|
+
if (
|
|
1940
|
+
!client.negotiated.alternateScreenProjection ||
|
|
1941
|
+
!baseline ||
|
|
1942
|
+
request.workspaceName !== client.deliveryWorkspaceName ||
|
|
1943
|
+
request.semanticPaneId !== client.paneId ||
|
|
1944
|
+
request.generation !== client.negotiated.generation ||
|
|
1945
|
+
request.incarnation !== baseline.incarnation ||
|
|
1946
|
+
request.deliveryNonce !== client.negotiated.deliveryNonce ||
|
|
1947
|
+
request.canonicalRevision !== baseline.revision ||
|
|
1948
|
+
request.projectionStateHash !== baseline.hash
|
|
1949
|
+
) {
|
|
1950
|
+
this.#fault(
|
|
1951
|
+
client,
|
|
1952
|
+
"protocol-violation",
|
|
1953
|
+
"History request does not identify the retained projection",
|
|
1954
|
+
);
|
|
1955
|
+
return;
|
|
1956
|
+
}
|
|
1957
|
+
client.lastMaterialize = request;
|
|
1958
|
+
client.materializeRequested = true;
|
|
1959
|
+
this.#cancelEncoding(client);
|
|
1960
|
+
client.latestRevision = this.#panes.get(client.paneId)?.latest?.update.revision ?? null;
|
|
1961
|
+
// A queued projection still owns its transaction until its receipt arrives.
|
|
1962
|
+
// The following complete seed then supersedes it, including at the same revision.
|
|
1963
|
+
this.#schedule(client);
|
|
1964
|
+
}
|
|
1965
|
+
|
|
1772
1966
|
#nack(client: ClientState, input: TerminalDeliveryNack): void {
|
|
1773
1967
|
const nack = TerminalDeliveryNackSchemaZ.parse(input);
|
|
1774
1968
|
const closed = client.sourceClosedFlight;
|
|
@@ -2224,7 +2418,7 @@ export function selectExactSemanticRepresentation<T extends { readonly frame: st
|
|
|
2224
2418
|
|
|
2225
2419
|
function completeSemanticSelectionObservation(
|
|
2226
2420
|
observation: ExactSemanticSelectionObservation,
|
|
2227
|
-
selectedEncoding: "semantic-v1" | "semantic-compact-v1",
|
|
2421
|
+
selectedEncoding: "semantic-v1" | "semantic-compact-v1" | "semantic-binary-v1",
|
|
2228
2422
|
legacyAttempts: Readonly<{ patchBytes: number | null; seedBytes: number | null }> | null,
|
|
2229
2423
|
): SemanticSelectionObservation {
|
|
2230
2424
|
const compact = selectedEncoding === "semantic-compact-v1";
|
package/packages/daemon/src/terminal/session-runtime/terminal-delivery-observation-identity.ts
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import type { TerminalDeliveryEnvelope } from "@tmux-ide/contracts";
|
|
1
|
+
import type { TerminalDeliveryEnvelope, TerminalProjectionEnvelope } from "@tmux-ide/contracts";
|
|
2
2
|
|
|
3
|
-
const deliveryOrdinals = new WeakMap<
|
|
3
|
+
const deliveryOrdinals = new WeakMap<
|
|
4
|
+
TerminalDeliveryEnvelope | TerminalProjectionEnvelope,
|
|
5
|
+
number
|
|
6
|
+
>();
|
|
4
7
|
|
|
5
8
|
export function registerTerminalDeliveryObservationOrdinal(
|
|
6
|
-
envelope: TerminalDeliveryEnvelope,
|
|
9
|
+
envelope: TerminalDeliveryEnvelope | TerminalProjectionEnvelope,
|
|
7
10
|
ordinal: number,
|
|
8
11
|
): void {
|
|
9
12
|
if (!Number.isSafeInteger(ordinal) || ordinal < 1)
|
|
@@ -12,7 +15,7 @@ export function registerTerminalDeliveryObservationOrdinal(
|
|
|
12
15
|
}
|
|
13
16
|
|
|
14
17
|
export function terminalDeliveryObservationOrdinal(
|
|
15
|
-
envelope: TerminalDeliveryEnvelope,
|
|
18
|
+
envelope: TerminalDeliveryEnvelope | TerminalProjectionEnvelope,
|
|
16
19
|
): number | null {
|
|
17
20
|
return deliveryOrdinals.get(envelope) ?? null;
|
|
18
21
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
TerminalProjectionPayload,
|
|
3
|
+
TerminalProjectionSnapshot,
|
|
4
|
+
TerminalReplicaSnapshot,
|
|
5
|
+
} from "@tmux-ide/contracts";
|
|
6
|
+
import {
|
|
7
|
+
hashTerminalProjection,
|
|
8
|
+
projectAlternateSnapshot,
|
|
9
|
+
terminalReplicaRowsEqual,
|
|
10
|
+
} from "@tmux-ide/core";
|
|
11
|
+
|
|
12
|
+
/** A projection receipt never becomes a complete canonical baseline. */
|
|
13
|
+
export interface ProjectionBaseline {
|
|
14
|
+
readonly revision: number;
|
|
15
|
+
readonly incarnation: string;
|
|
16
|
+
readonly hash: string;
|
|
17
|
+
readonly snapshot: TerminalProjectionSnapshot;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function prepareProjection(
|
|
21
|
+
snapshot: TerminalReplicaSnapshot,
|
|
22
|
+
revision: number,
|
|
23
|
+
incarnation: string,
|
|
24
|
+
baseline: ProjectionBaseline | null,
|
|
25
|
+
): { payload: TerminalProjectionPayload; retained: ProjectionBaseline } {
|
|
26
|
+
const projected = projectAlternateSnapshot(snapshot);
|
|
27
|
+
const retained = {
|
|
28
|
+
revision,
|
|
29
|
+
incarnation,
|
|
30
|
+
hash: hashTerminalProjection(projected),
|
|
31
|
+
snapshot: projected,
|
|
32
|
+
};
|
|
33
|
+
if (
|
|
34
|
+
!baseline ||
|
|
35
|
+
baseline.incarnation !== incarnation ||
|
|
36
|
+
baseline.snapshot.cols !== projected.cols ||
|
|
37
|
+
baseline.snapshot.rows !== projected.rows
|
|
38
|
+
)
|
|
39
|
+
return { payload: { frame: "seed", revision, projection: projected }, retained };
|
|
40
|
+
const rows = projected.grid.flatMap((row, index) =>
|
|
41
|
+
terminalReplicaRowsEqual(row, baseline.snapshot.grid[index]!) ? [] : [{ index, row }],
|
|
42
|
+
);
|
|
43
|
+
return {
|
|
44
|
+
retained,
|
|
45
|
+
payload: {
|
|
46
|
+
frame: "patch",
|
|
47
|
+
baseRevision: baseline.revision,
|
|
48
|
+
revision,
|
|
49
|
+
patch: {
|
|
50
|
+
rows,
|
|
51
|
+
deferredHistory: projected.deferredHistory,
|
|
52
|
+
cursor: projected.cursor,
|
|
53
|
+
modes: projected.modes,
|
|
54
|
+
placements: projected.placements,
|
|
55
|
+
bootstrap: projected.bootstrap,
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|