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
|
@@ -507,6 +507,8 @@ export class SessionChannel {
|
|
|
507
507
|
private readonly fittedWindows = new Map<string, { cols: number; rows: number }>();
|
|
508
508
|
private cancelSync: (() => void) | null = null;
|
|
509
509
|
private syncOrdinal = 0;
|
|
510
|
+
private identityJoinOrdinal = 0;
|
|
511
|
+
private trustedInventoryJoinsMissing = false;
|
|
510
512
|
private completedSyncOrdinal = 0;
|
|
511
513
|
private lastDisplayNameSyncAtMs = 0;
|
|
512
514
|
private disposed = false;
|
|
@@ -799,22 +801,36 @@ export class SessionChannel {
|
|
|
799
801
|
|
|
800
802
|
/**
|
|
801
803
|
* Strict daemon-internal inventory from this channel's current tmux truth.
|
|
802
|
-
* Unlike
|
|
803
|
-
*
|
|
804
|
-
*
|
|
804
|
+
* Unlike background discovery, this query verifies current native membership
|
|
805
|
+
* before projecting and rejects incomplete identity. Stream admission may
|
|
806
|
+
* explicitly join missing pane records after that verification; ordinary
|
|
807
|
+
* inventory readers retain their existing fail-closed no-join contract.
|
|
805
808
|
*/
|
|
806
809
|
describeTrustedInventory(
|
|
807
810
|
expectedRuntimeSessionId: string,
|
|
811
|
+
joinMissingPanes = false,
|
|
808
812
|
): Promise<TrustedMirrorSessionInventory> {
|
|
809
813
|
if (this.disposed) {
|
|
810
814
|
return Promise.reject(new Error(`mirror session ${this.opts.session} is disposed`));
|
|
811
815
|
}
|
|
812
816
|
if (this.trustedInventoryFlight) {
|
|
817
|
+
// A default read can fail precisely because it does not join new panes.
|
|
818
|
+
// Preserve that failure for its caller, then qualify admission separately.
|
|
819
|
+
if (
|
|
820
|
+
joinMissingPanes &&
|
|
821
|
+
!this.trustedInventoryJoinsMissing &&
|
|
822
|
+
this.trustedInventoryFlightSessionId === expectedRuntimeSessionId
|
|
823
|
+
)
|
|
824
|
+
return this.trustedInventoryFlight.then(
|
|
825
|
+
() => this.describeTrustedInventory(expectedRuntimeSessionId, true),
|
|
826
|
+
() => this.describeTrustedInventory(expectedRuntimeSessionId, true),
|
|
827
|
+
);
|
|
813
828
|
return this.trustedInventoryFlightSessionId === expectedRuntimeSessionId
|
|
814
829
|
? this.trustedInventoryFlight
|
|
815
830
|
: Promise.reject(new Error(`trusted inventory identity changed for ${this.opts.session}`));
|
|
816
831
|
}
|
|
817
|
-
|
|
832
|
+
this.trustedInventoryJoinsMissing = joinMissingPanes;
|
|
833
|
+
const flight = this.refreshTrustedInventory(expectedRuntimeSessionId, 0, joinMissingPanes)
|
|
818
834
|
.catch((error) => {
|
|
819
835
|
this.windowLinkAuthority?.invalidate();
|
|
820
836
|
this.latestWindowStage = null;
|
|
@@ -3387,6 +3403,7 @@ export class SessionChannel {
|
|
|
3387
3403
|
private async refreshTrustedInventory(
|
|
3388
3404
|
expectedRuntimeSessionId: string,
|
|
3389
3405
|
attempt = 0,
|
|
3406
|
+
joinMissingPanes = false,
|
|
3390
3407
|
): Promise<TrustedMirrorSessionInventory> {
|
|
3391
3408
|
const authorityOrdinal = this.windowIdentityOrdinal;
|
|
3392
3409
|
const beforeLines = await this.io.request(
|
|
@@ -3486,7 +3503,11 @@ export class SessionChannel {
|
|
|
3486
3503
|
) {
|
|
3487
3504
|
if (attempt >= 1)
|
|
3488
3505
|
throw new Error(`trusted inventory for ${this.opts.session} did not settle`);
|
|
3489
|
-
return await this.refreshTrustedInventory(
|
|
3506
|
+
return await this.refreshTrustedInventory(
|
|
3507
|
+
expectedRuntimeSessionId,
|
|
3508
|
+
attempt + 1,
|
|
3509
|
+
joinMissingPanes,
|
|
3510
|
+
);
|
|
3490
3511
|
}
|
|
3491
3512
|
if (this.disposed) throw new Error(`mirror session ${this.opts.session} is disposed`);
|
|
3492
3513
|
const activeWindowId = activeWindowIds.values().next().value;
|
|
@@ -3521,7 +3542,28 @@ export class SessionChannel {
|
|
|
3521
3542
|
stagedPaneIds.size !== descriptors.length ||
|
|
3522
3543
|
[...computedWindowCounts.keys()].some(
|
|
3523
3544
|
(runtimeWindowId) => !confirmedWindowStage.windows.has(runtimeWindowId),
|
|
3524
|
-
)
|
|
3545
|
+
)
|
|
3546
|
+
)
|
|
3547
|
+
throw new Error(`trusted inventory for ${this.opts.session} lacks verified identity`);
|
|
3548
|
+
// Fresh native inventory can beat background discovery after window creation.
|
|
3549
|
+
// Join verified stable membership before projecting semantic records.
|
|
3550
|
+
if (
|
|
3551
|
+
joinMissingPanes &&
|
|
3552
|
+
descriptors.some(
|
|
3553
|
+
(pane) => this.panesByRuntime.get(pane.runtimePaneId)?.semanticId !== pane.semanticPaneId,
|
|
3554
|
+
)
|
|
3555
|
+
) {
|
|
3556
|
+
this.discovery.invalidate();
|
|
3557
|
+
await this.reconcileIdentity(
|
|
3558
|
+
descriptors,
|
|
3559
|
+
runtimePaneIds,
|
|
3560
|
+
() => authorityOrdinal === this.windowIdentityOrdinal,
|
|
3561
|
+
false,
|
|
3562
|
+
);
|
|
3563
|
+
}
|
|
3564
|
+
if (this.disposed || authorityOrdinal !== this.windowIdentityOrdinal)
|
|
3565
|
+
throw new Error(`trusted inventory for ${this.opts.session} changed during identity join`);
|
|
3566
|
+
if (
|
|
3525
3567
|
descriptors.some((descriptor) => {
|
|
3526
3568
|
const pane = this.panesByRuntime.get(descriptor.runtimePaneId);
|
|
3527
3569
|
const window = confirmedWindowStage.windows.get(descriptor.windowId!);
|
|
@@ -3934,7 +3976,10 @@ export class SessionChannel {
|
|
|
3934
3976
|
private async reconcileIdentity(
|
|
3935
3977
|
descriptors: readonly SessionPaneDescriptor[],
|
|
3936
3978
|
listed: ReadonlySet<string>,
|
|
3979
|
+
current: () => boolean = () => true,
|
|
3980
|
+
publish = true,
|
|
3937
3981
|
): Promise<boolean> {
|
|
3982
|
+
const joinOrdinal = ++this.identityJoinOrdinal;
|
|
3938
3983
|
if (this.disposed) return false;
|
|
3939
3984
|
if (this.opts.hasSharedWindowConflict?.()) {
|
|
3940
3985
|
this.settleFirstJoin();
|
|
@@ -3973,7 +4018,8 @@ export class SessionChannel {
|
|
|
3973
4018
|
),
|
|
3974
4019
|
),
|
|
3975
4020
|
);
|
|
3976
|
-
if (this.disposed)
|
|
4021
|
+
if (this.disposed || !current() || joinOrdinal !== this.identityJoinOrdinal)
|
|
4022
|
+
return plan.stampEffects.length > 0;
|
|
3977
4023
|
const reconciliation = finalizeWorkspaceTmuxReconciliation(plan, outcomes);
|
|
3978
4024
|
const descriptorByRuntime = new Map(descriptors.map((d) => [d.runtimePaneId, d]));
|
|
3979
4025
|
for (const verified of reconciliation.panes) {
|
|
@@ -4080,8 +4126,10 @@ export class SessionChannel {
|
|
|
4080
4126
|
* without this the new pane stays invisible until something else happens to
|
|
4081
4127
|
* move a layout, and a split looks like it did not reach the view.
|
|
4082
4128
|
*/
|
|
4083
|
-
|
|
4084
|
-
|
|
4129
|
+
if (publish) {
|
|
4130
|
+
for (const runtimeId of this.layoutByWindow.keys()) this.emitLayout(runtimeId);
|
|
4131
|
+
this.emitLayoutAuthority();
|
|
4132
|
+
}
|
|
4085
4133
|
this.settleFirstJoin();
|
|
4086
4134
|
return plan.stampEffects.length > 0;
|
|
4087
4135
|
}
|
|
@@ -1,3 +1,15 @@
|
|
|
1
|
+
import {
|
|
2
|
+
TERMINAL_PROJECTION_CAPABILITY_VERSION,
|
|
3
|
+
TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION,
|
|
4
|
+
type TerminalProjectionReceipt,
|
|
5
|
+
type TerminalProjectionMaterialize,
|
|
6
|
+
} from "@tmux-ide/contracts";
|
|
7
|
+
import type { LocalImageOwner } from "../graphics/local-image-owner.ts";
|
|
8
|
+
import {
|
|
9
|
+
PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION,
|
|
10
|
+
PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION,
|
|
11
|
+
type PaneStreamInputLifetime,
|
|
12
|
+
} from "@tmux-ide/contracts";
|
|
1
13
|
import { inflateSync } from "node:zlib";
|
|
2
14
|
import { PaneStreamLayoutSnapshotFrameSchemaZ, type WindowLinkTopology } from "@tmux-ide/contracts";
|
|
3
15
|
import type { TerminalDeliveryGraphics } from "../session-runtime/terminal-delivery-hub.ts";
|
|
@@ -11,6 +23,12 @@ import {
|
|
|
11
23
|
PANE_STREAM_REDEEM_PATH,
|
|
12
24
|
PANE_STREAM_WEBSOCKET_SUBPROTOCOL,
|
|
13
25
|
TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES,
|
|
26
|
+
TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION,
|
|
27
|
+
TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION,
|
|
28
|
+
TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION,
|
|
29
|
+
TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION,
|
|
30
|
+
encodeTerminalGraphicsBinary,
|
|
31
|
+
encodeTerminalDeliveryBinaryChunk,
|
|
14
32
|
TERMINAL_GRAPHICS_CELL_PIXELS_CAPABILITY_VERSION,
|
|
15
33
|
TERMINAL_GRAPHICS_PRECISE_PIXELS_CAPABILITY_VERSION,
|
|
16
34
|
TERMINAL_GRAPHICS_LEGACY_MAX_IMAGE_BYTES,
|
|
@@ -149,6 +167,7 @@ export interface PaneStreamMirror {
|
|
|
149
167
|
describeSession(session: string): Promise<MirrorSessionDescription>;
|
|
150
168
|
describeSessionAuthority?(
|
|
151
169
|
session: string,
|
|
170
|
+
expectedSemanticPaneIds?: readonly string[],
|
|
152
171
|
): Promise<{ readonly description: MirrorSessionDescription; readonly runtimeSessionId: string }>;
|
|
153
172
|
subscribe(request: MirrorSubscribeRequest): Promise<MirrorSubscription>;
|
|
154
173
|
subscribeLayout?(
|
|
@@ -240,6 +259,7 @@ export interface PaneStreamAdmissionSnapshot {
|
|
|
240
259
|
}
|
|
241
260
|
|
|
242
261
|
export interface PaneStreamAdmissionCoordinatorOptions {
|
|
262
|
+
readonly localImages?: LocalImageOwner;
|
|
243
263
|
readonly daemonInstanceId: string;
|
|
244
264
|
readonly webSocketUrl: string;
|
|
245
265
|
readonly leaseManager: PaneStreamLeaseAuthority;
|
|
@@ -304,6 +324,7 @@ export interface SessionRuntimePaneStreamTransportBinding {
|
|
|
304
324
|
performanceTraceId?: string,
|
|
305
325
|
causalProbe?: CausalCellProbeV1,
|
|
306
326
|
onCausalResult?: (result: CausalCellLedgerResult) => void,
|
|
327
|
+
expectedLifetime?: PaneStreamInputLifetime,
|
|
307
328
|
): void;
|
|
308
329
|
fitViewport(
|
|
309
330
|
lease: SessionRuntimeAuthorityLease,
|
|
@@ -320,6 +341,8 @@ export interface SessionRuntimeTerminalDeliveryConnection {
|
|
|
320
341
|
readonly closed?: Promise<"closed" | "sink-failed">;
|
|
321
342
|
readonly negotiation: TerminalDeliveryNegotiationResult;
|
|
322
343
|
ack(ack: TerminalDeliveryAck): void;
|
|
344
|
+
projectionReceipt?(receipt: TerminalProjectionReceipt): void;
|
|
345
|
+
materialize?(request: TerminalProjectionMaterialize): void;
|
|
323
346
|
nack(nack: TerminalDeliveryNack): void;
|
|
324
347
|
setVisibility(visibility: TerminalDeliveryVisibility): void;
|
|
325
348
|
close(): Promise<void>;
|
|
@@ -382,6 +405,7 @@ function errorFrameCode(error: unknown): PaneStreamErrorFrameCode {
|
|
|
382
405
|
* ticket digests; a daemon restart makes every prior descriptor inert.
|
|
383
406
|
*/
|
|
384
407
|
export class PaneStreamAdmissionCoordinator {
|
|
408
|
+
readonly #localImages: LocalImageOwner | undefined;
|
|
385
409
|
readonly #instanceId: string;
|
|
386
410
|
readonly #webSocketUrl: string;
|
|
387
411
|
readonly #leaseManager: PaneStreamLeaseAuthority;
|
|
@@ -415,6 +439,7 @@ export class PaneStreamAdmissionCoordinator {
|
|
|
415
439
|
#shutdownPromise: Promise<void> | null = null;
|
|
416
440
|
|
|
417
441
|
constructor(options: PaneStreamAdmissionCoordinatorOptions) {
|
|
442
|
+
this.#localImages = options.localImages;
|
|
418
443
|
this.#instanceId = BindingIdSchemaZ.parse(options.daemonInstanceId);
|
|
419
444
|
this.#webSocketUrl = PaneStreamLoopbackWebSocketUrlSchemaZ.parse(options.webSocketUrl);
|
|
420
445
|
this.#leaseManager = options.leaseManager;
|
|
@@ -493,7 +518,10 @@ export class PaneStreamAdmissionCoordinator {
|
|
|
493
518
|
try {
|
|
494
519
|
const describeStartedAtMicros = trace ? this.#observability!.nowMicros() : 0;
|
|
495
520
|
if (this.#mirror.describeSessionAuthority) {
|
|
496
|
-
const authority = await this.#mirror.describeSessionAuthority(
|
|
521
|
+
const authority = await this.#mirror.describeSessionAuthority(
|
|
522
|
+
context.sessionName,
|
|
523
|
+
request.panes,
|
|
524
|
+
);
|
|
497
525
|
described = authority.description;
|
|
498
526
|
runtimeSessionId = authority.runtimeSessionId;
|
|
499
527
|
} else {
|
|
@@ -783,6 +811,7 @@ export class PaneStreamAdmissionCoordinator {
|
|
|
783
811
|
let live: PaneStreamLiveConnection;
|
|
784
812
|
try {
|
|
785
813
|
live = new PaneStreamLiveConnection({
|
|
814
|
+
localImages: this.#localImages,
|
|
786
815
|
clientId: `psc-${this.#clientCounter}`,
|
|
787
816
|
socket,
|
|
788
817
|
descriptor: structuredClone(descriptor),
|
|
@@ -1001,6 +1030,7 @@ class PreAuthAdmission implements PaneStreamPreAuthAdmission {
|
|
|
1001
1030
|
}
|
|
1002
1031
|
|
|
1003
1032
|
interface LiveConnectionOptions {
|
|
1033
|
+
readonly localImages?: LocalImageOwner;
|
|
1004
1034
|
readonly clientId: string;
|
|
1005
1035
|
readonly socket: DirectTerminalSocket;
|
|
1006
1036
|
readonly descriptor: PaneStreamLeaseDescriptor;
|
|
@@ -1057,6 +1087,10 @@ interface PaneChannel {
|
|
|
1057
1087
|
graphicsServed: Map<number, string>;
|
|
1058
1088
|
graphicsState: TerminalGraphicsState | null;
|
|
1059
1089
|
graphicsZlib?: boolean;
|
|
1090
|
+
graphicsBinary?: boolean;
|
|
1091
|
+
binaryCells?: boolean;
|
|
1092
|
+
graphicsLocal?: boolean;
|
|
1093
|
+
graphicsInline?: Set<string>;
|
|
1060
1094
|
}
|
|
1061
1095
|
|
|
1062
1096
|
interface QueuedSend {
|
|
@@ -1088,6 +1122,8 @@ export function rawGraphicsState(state: TerminalGraphicsState): TerminalGraphics
|
|
|
1088
1122
|
const GRAPHICS_SEND_WINDOW_BYTES = 2 * 1024 * 1024;
|
|
1089
1123
|
|
|
1090
1124
|
export class PaneStreamLiveConnection {
|
|
1125
|
+
readonly #localImages: LocalImageOwner | undefined;
|
|
1126
|
+
readonly #localLeases = new Map<string, { pane: string; expiresAt: number }>();
|
|
1091
1127
|
readonly #clientId: string;
|
|
1092
1128
|
readonly #socket: DirectTerminalSocket;
|
|
1093
1129
|
readonly #descriptor: PaneStreamLeaseDescriptor;
|
|
@@ -1212,6 +1248,7 @@ export class PaneStreamLiveConnection {
|
|
|
1212
1248
|
}
|
|
1213
1249
|
|
|
1214
1250
|
constructor(options: LiveConnectionOptions) {
|
|
1251
|
+
this.#localImages = options.localImages;
|
|
1215
1252
|
this.#clientId = options.clientId;
|
|
1216
1253
|
this.#socket = options.socket;
|
|
1217
1254
|
this.#descriptor = options.descriptor;
|
|
@@ -1279,6 +1316,18 @@ export class PaneStreamLiveConnection {
|
|
|
1279
1316
|
try {
|
|
1280
1317
|
sendControl(this.#socket, {
|
|
1281
1318
|
type: "ready",
|
|
1319
|
+
...(this.#sessionRuntimeBinding &&
|
|
1320
|
+
this.#descriptor.terminalDelivery?.protocolVersions.includes(
|
|
1321
|
+
PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION,
|
|
1322
|
+
)
|
|
1323
|
+
? { inputLifetime: true as const }
|
|
1324
|
+
: {}),
|
|
1325
|
+
...(this.#sessionRuntimeBinding &&
|
|
1326
|
+
this.#descriptor.terminalDelivery?.protocolVersions.includes(
|
|
1327
|
+
PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION,
|
|
1328
|
+
)
|
|
1329
|
+
? { inputBytes: true as const }
|
|
1330
|
+
: {}),
|
|
1282
1331
|
protocolVersion: PANE_STREAM_PROTOCOL_VERSION,
|
|
1283
1332
|
daemonInstanceId: this.#binding.daemonInstanceId,
|
|
1284
1333
|
requestId: this.#binding.requestId,
|
|
@@ -1321,6 +1370,14 @@ export class PaneStreamLiveConnection {
|
|
|
1321
1370
|
this.#stopAuthoritySnapshots?.();
|
|
1322
1371
|
this.#stopAuthoritySnapshots = null;
|
|
1323
1372
|
const closures: Promise<unknown>[] = [];
|
|
1373
|
+
this.#localLeases.clear();
|
|
1374
|
+
if (this.#localImages)
|
|
1375
|
+
closures.push(
|
|
1376
|
+
this.#localImages.releaseClient({
|
|
1377
|
+
generation: this.#localImages.identity.generation,
|
|
1378
|
+
client: this.#descriptor.leaseId,
|
|
1379
|
+
}),
|
|
1380
|
+
);
|
|
1324
1381
|
const layoutSubscription = this.#layoutSubscription;
|
|
1325
1382
|
this.#layoutSubscription = null;
|
|
1326
1383
|
if (layoutSubscription) closures.push(layoutSubscription.close().catch(() => undefined));
|
|
@@ -1549,7 +1606,8 @@ export class PaneStreamLiveConnection {
|
|
|
1549
1606
|
if (negotiated.generation !== this.#binding.daemonInstanceId) return true;
|
|
1550
1607
|
let incarnation: string | null = null;
|
|
1551
1608
|
for (const message of pending) {
|
|
1552
|
-
if (message.type !== "terminal.delivery")
|
|
1609
|
+
if (message.type !== "terminal.delivery" && message.type !== "terminal.projection")
|
|
1610
|
+
continue;
|
|
1553
1611
|
if (
|
|
1554
1612
|
message.workspaceName !== this.#descriptor.workspaceName ||
|
|
1555
1613
|
message.semanticPaneId !== channel.semanticPaneId ||
|
|
@@ -1671,13 +1729,19 @@ export class PaneStreamLiveConnection {
|
|
|
1671
1729
|
}
|
|
1672
1730
|
|
|
1673
1731
|
async #sendTerminalDelivery(pane: string, message: TerminalDeliveryServerMessage): Promise<void> {
|
|
1674
|
-
if (message.type === "terminal.
|
|
1732
|
+
if (message.type === "terminal.projection") {
|
|
1733
|
+
const channel = this.#panes.get(pane);
|
|
1734
|
+
if (channel?.deliveryAddress) channel.deliveryAddress.incarnation = message.incarnation;
|
|
1735
|
+
if (channel) channel.binaryCells = true;
|
|
1736
|
+
this.#sendFrame(pane, { type: "terminal-projection-envelope", pane, envelope: message });
|
|
1737
|
+
} else if (message.type === "terminal.delivery") {
|
|
1675
1738
|
if (!this.#diagnosticFirstSeedObserved) {
|
|
1676
1739
|
this.#diagnosticFirstSeedObserved = true;
|
|
1677
1740
|
this.#recordDiagnosticLifecycle("pane-stream-first-seed");
|
|
1678
1741
|
}
|
|
1679
1742
|
const channel = this.#panes.get(pane);
|
|
1680
1743
|
if (channel?.deliveryAddress) channel.deliveryAddress.incarnation = message.incarnation;
|
|
1744
|
+
if (channel) channel.binaryCells = message.encoding === "semantic-binary-v1";
|
|
1681
1745
|
// The trusted lease address was fixed at delivery admission; preserve the
|
|
1682
1746
|
// exact envelope and its observation identity through transmission.
|
|
1683
1747
|
const detailedDeliveryObservation = this.#observability?.enabled === true;
|
|
@@ -1760,13 +1824,26 @@ export class PaneStreamLiveConnection {
|
|
|
1760
1824
|
}
|
|
1761
1825
|
}
|
|
1762
1826
|
} else if (message.type === "terminal.delivery.chunk") {
|
|
1763
|
-
this.#
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1827
|
+
if (this.#panes.get(pane)?.binaryCells) {
|
|
1828
|
+
this.#sendEncoded(
|
|
1829
|
+
pane,
|
|
1830
|
+
Buffer.from(
|
|
1831
|
+
encodeTerminalDeliveryBinaryChunk(
|
|
1832
|
+
{ pane, transactionId: message.transactionId, index: message.index },
|
|
1833
|
+
message.bytes,
|
|
1834
|
+
),
|
|
1835
|
+
),
|
|
1836
|
+
true,
|
|
1837
|
+
);
|
|
1838
|
+
} else {
|
|
1839
|
+
this.#sendFrame(pane, {
|
|
1840
|
+
type: "terminal-delivery-chunk",
|
|
1841
|
+
pane,
|
|
1842
|
+
transactionId: message.transactionId,
|
|
1843
|
+
index: message.index,
|
|
1844
|
+
data: Buffer.from(message.bytes).toString("base64"),
|
|
1845
|
+
});
|
|
1846
|
+
}
|
|
1770
1847
|
} else {
|
|
1771
1848
|
this.#sendFrame(pane, { type: "terminal-delivery-fault", pane, fault: message });
|
|
1772
1849
|
// Lifecycle/control truth must never queue behind bulk terminal bytes.
|
|
@@ -1788,6 +1865,25 @@ export class PaneStreamLiveConnection {
|
|
|
1788
1865
|
TERMINAL_GRAPHICS_ZLIB_CAPABILITY_VERSION,
|
|
1789
1866
|
) === true;
|
|
1790
1867
|
channel.graphicsZlib = zlib;
|
|
1868
|
+
channel.graphicsLocal =
|
|
1869
|
+
!!this.#localImages &&
|
|
1870
|
+
this.#descriptor.terminalDelivery?.protocolVersions.some(
|
|
1871
|
+
(version) =>
|
|
1872
|
+
version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
|
|
1873
|
+
version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
|
|
1874
|
+
version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION,
|
|
1875
|
+
) === true;
|
|
1876
|
+
channel.graphicsInline = new Set();
|
|
1877
|
+
channel.graphicsBinary =
|
|
1878
|
+
this.#descriptor.terminalDelivery?.protocolVersions.some(
|
|
1879
|
+
(version) =>
|
|
1880
|
+
version === TERMINAL_GRAPHICS_BINARY_CAPABILITY_VERSION ||
|
|
1881
|
+
version === TERMINAL_GRAPHICS_LOCAL_CAPABILITY_VERSION ||
|
|
1882
|
+
version === TERMINAL_DELIVERY_BINARY_CAPABILITY_VERSION ||
|
|
1883
|
+
version === TERMINAL_PROJECTION_CAPABILITY_VERSION ||
|
|
1884
|
+
version === TERMINAL_DELIVERY_BINARY_LOCAL_CAPABILITY_VERSION ||
|
|
1885
|
+
version === TERMINAL_PROJECTION_LOCAL_CAPABILITY_VERSION,
|
|
1886
|
+
) === true;
|
|
1791
1887
|
// Renderers that report cell pixels learn up front that this daemon
|
|
1792
1888
|
// accepts them, before any image exists.
|
|
1793
1889
|
const cellPixels =
|
|
@@ -1819,6 +1915,8 @@ export class PaneStreamLiveConnection {
|
|
|
1819
1915
|
...(zlib ? state : rawGraphicsState(state)),
|
|
1820
1916
|
...(cellPixels ? { cellPixels: true as const } : {}),
|
|
1821
1917
|
...(preciseCellPixels ? { preciseCellPixels: true as const } : {}),
|
|
1918
|
+
...(channel.graphicsBinary ? { binaryBlobs: true as const } : {}),
|
|
1919
|
+
...(channel.graphicsLocal ? { localFiles: true as const } : {}),
|
|
1822
1920
|
},
|
|
1823
1921
|
});
|
|
1824
1922
|
if (pushed.length > 0) this.#queueGraphics(channel, pushed);
|
|
@@ -1834,15 +1932,23 @@ export class PaneStreamLiveConnection {
|
|
|
1834
1932
|
placements: [],
|
|
1835
1933
|
cellPixels: true,
|
|
1836
1934
|
...(preciseCellPixels ? { preciseCellPixels: true as const } : {}),
|
|
1935
|
+
...(channel.graphicsBinary ? { binaryBlobs: true as const } : {}),
|
|
1936
|
+
...(channel.graphicsLocal ? { localFiles: true as const } : {}),
|
|
1837
1937
|
},
|
|
1838
1938
|
});
|
|
1839
1939
|
}
|
|
1840
1940
|
}
|
|
1841
1941
|
|
|
1842
|
-
#requestGraphics(pane: string, digests: readonly string[]): void {
|
|
1942
|
+
#requestGraphics(pane: string, digests: readonly string[], preferInline = false): void {
|
|
1843
1943
|
const channel = this.#panes.get(pane);
|
|
1844
1944
|
// Requests are only meaningful after an announcement; ignore stray ones.
|
|
1845
1945
|
if (!channel || channel.closed || !channel.graphicsAnnounced) return;
|
|
1946
|
+
if (preferInline && channel.graphicsLocal) {
|
|
1947
|
+
for (const digest of digests) {
|
|
1948
|
+
if ((channel.graphicsInline?.size ?? 0) < GRAPHICS_MAX_QUEUED_DIGESTS)
|
|
1949
|
+
channel.graphicsInline?.add(digest);
|
|
1950
|
+
}
|
|
1951
|
+
}
|
|
1846
1952
|
this.#queueGraphics(channel, digests);
|
|
1847
1953
|
}
|
|
1848
1954
|
|
|
@@ -1866,6 +1972,7 @@ export class PaneStreamLiveConnection {
|
|
|
1866
1972
|
try {
|
|
1867
1973
|
while (!this.#closed && !channel.closed && channel.graphicsQueue.length > 0) {
|
|
1868
1974
|
const digest = channel.graphicsQueue.shift()!;
|
|
1975
|
+
const inline = channel.graphicsInline?.delete(digest) ?? false;
|
|
1869
1976
|
const graphics = channel.delivery?.graphics;
|
|
1870
1977
|
const image = graphics?.image(digest);
|
|
1871
1978
|
if (!graphics || !image) continue;
|
|
@@ -1887,6 +1994,47 @@ export class PaneStreamLiveConnection {
|
|
|
1887
1994
|
continue;
|
|
1888
1995
|
const format = raw ? (image.format === "rgba-zlib" ? "rgba" : "rgb") : image.format;
|
|
1889
1996
|
const total = data.byteLength;
|
|
1997
|
+
if (!inline && channel.graphicsLocal && !raw && this.#localImages) {
|
|
1998
|
+
for (const [id, lease] of this.#localLeases)
|
|
1999
|
+
if (lease.expiresAt <= Date.now()) this.#localLeases.delete(id);
|
|
2000
|
+
if (this.#localLeases.size < 64) {
|
|
2001
|
+
try {
|
|
2002
|
+
const lease = await this.#localImages.acquire({
|
|
2003
|
+
generation: this.#localImages.identity.generation,
|
|
2004
|
+
client: this.#descriptor.leaseId,
|
|
2005
|
+
digest,
|
|
2006
|
+
bytes: data,
|
|
2007
|
+
});
|
|
2008
|
+
if (this.#closed || channel.closed || graphics.image(digest) !== image) {
|
|
2009
|
+
await this.#localImages.release(lease);
|
|
2010
|
+
continue;
|
|
2011
|
+
}
|
|
2012
|
+
this.#localLeases.set(lease.id, {
|
|
2013
|
+
pane: channel.semanticPaneId,
|
|
2014
|
+
expiresAt: lease.expiresAt,
|
|
2015
|
+
});
|
|
2016
|
+
this.#sendFrame(null, {
|
|
2017
|
+
type: "terminal-graphics-local",
|
|
2018
|
+
pane: channel.semanticPaneId,
|
|
2019
|
+
generation: lease.generation,
|
|
2020
|
+
leaseId: lease.id,
|
|
2021
|
+
path: lease.path,
|
|
2022
|
+
expiresAt: lease.expiresAt,
|
|
2023
|
+
blob: {
|
|
2024
|
+
digest,
|
|
2025
|
+
format,
|
|
2026
|
+
width: image.width,
|
|
2027
|
+
height: image.height,
|
|
2028
|
+
total,
|
|
2029
|
+
offset: 0,
|
|
2030
|
+
},
|
|
2031
|
+
});
|
|
2032
|
+
continue;
|
|
2033
|
+
} catch {
|
|
2034
|
+
/* Optional transport: retain the negotiated inline fallback. */
|
|
2035
|
+
}
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
1890
2038
|
for (let offset = 0; offset < total; offset += TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES) {
|
|
1891
2039
|
while ((this.#socket.bufferedAmount ?? 0) > GRAPHICS_SEND_WINDOW_BYTES) {
|
|
1892
2040
|
if (this.#closed || channel.closed) return;
|
|
@@ -1897,6 +2045,29 @@ export class PaneStreamLiveConnection {
|
|
|
1897
2045
|
// the renderer drops the partial image and shows the newer one.
|
|
1898
2046
|
if (offset > 0 && graphics.image(digest) !== image) break;
|
|
1899
2047
|
const end = Math.min(total, offset + TERMINAL_GRAPHICS_BLOB_CHUNK_BYTES);
|
|
2048
|
+
if (channel.graphicsBinary) {
|
|
2049
|
+
this.#sendEncoded(
|
|
2050
|
+
null,
|
|
2051
|
+
Buffer.from(
|
|
2052
|
+
encodeTerminalGraphicsBinary(
|
|
2053
|
+
{
|
|
2054
|
+
pane: channel.semanticPaneId,
|
|
2055
|
+
blob: {
|
|
2056
|
+
digest,
|
|
2057
|
+
format,
|
|
2058
|
+
width: image.width,
|
|
2059
|
+
height: image.height,
|
|
2060
|
+
total,
|
|
2061
|
+
offset,
|
|
2062
|
+
},
|
|
2063
|
+
},
|
|
2064
|
+
data.subarray(offset, end),
|
|
2065
|
+
),
|
|
2066
|
+
),
|
|
2067
|
+
true,
|
|
2068
|
+
);
|
|
2069
|
+
continue;
|
|
2070
|
+
}
|
|
1900
2071
|
this.#sendFrame(null, {
|
|
1901
2072
|
type: "terminal-graphics-blob",
|
|
1902
2073
|
pane: channel.semanticPaneId,
|
|
@@ -2230,7 +2401,12 @@ export class PaneStreamLiveConnection {
|
|
|
2230
2401
|
#sendFrame(pane: string | null, frame: Record<string, unknown>): void {
|
|
2231
2402
|
if (this.#closed || this.#socket.readyState !== WS_OPEN) return;
|
|
2232
2403
|
const encoded = JSON.stringify(frame);
|
|
2233
|
-
|
|
2404
|
+
this.#sendEncoded(pane, encoded, false);
|
|
2405
|
+
}
|
|
2406
|
+
|
|
2407
|
+
#sendEncoded(pane: string | null, encoded: string | Buffer, binary: boolean): void {
|
|
2408
|
+
if (this.#closed || this.#socket.readyState !== WS_OPEN) return;
|
|
2409
|
+
const bytes = Buffer.byteLength(encoded);
|
|
2234
2410
|
if (pane !== null) {
|
|
2235
2411
|
this.#ledger.take(this.#clientId, pane, "ws-send-buffer", bytes);
|
|
2236
2412
|
if (this.#deliveryAcks) this.#ledger.take(this.#clientId, pane, "renderer-backlog", 1);
|
|
@@ -2238,7 +2414,7 @@ export class PaneStreamLiveConnection {
|
|
|
2238
2414
|
this.#sendQueue.push({ pane, remaining: bytes });
|
|
2239
2415
|
this.#sentBytesTotal += bytes;
|
|
2240
2416
|
try {
|
|
2241
|
-
this.#socket.send(encoded, { binary
|
|
2417
|
+
this.#socket.send(encoded, { binary });
|
|
2242
2418
|
} catch {
|
|
2243
2419
|
this.close(1011, "stream-unavailable");
|
|
2244
2420
|
return;
|
|
@@ -2460,6 +2636,20 @@ export class PaneStreamLiveConnection {
|
|
|
2460
2636
|
});
|
|
2461
2637
|
return;
|
|
2462
2638
|
}
|
|
2639
|
+
if (frame.type === "terminal-projection-receipt") {
|
|
2640
|
+
const channel = this.#deliveryChannel(frame.receipt);
|
|
2641
|
+
if (!channel) return;
|
|
2642
|
+
if (!channel.delivery?.projectionReceipt) return this.#failProtocol("protocol-error");
|
|
2643
|
+
channel.delivery.projectionReceipt(frame.receipt);
|
|
2644
|
+
return;
|
|
2645
|
+
}
|
|
2646
|
+
if (frame.type === "terminal-projection-materialize") {
|
|
2647
|
+
const channel = this.#deliveryChannel(frame.request);
|
|
2648
|
+
if (!channel) return;
|
|
2649
|
+
if (!channel.delivery?.materialize) return this.#failProtocol("protocol-error");
|
|
2650
|
+
channel.delivery.materialize(frame.request);
|
|
2651
|
+
return;
|
|
2652
|
+
}
|
|
2463
2653
|
if (frame.type === "terminal-delivery-ack") {
|
|
2464
2654
|
const channel = this.#deliveryChannel(frame.ack);
|
|
2465
2655
|
if (!channel) return;
|
|
@@ -2489,7 +2679,25 @@ export class PaneStreamLiveConnection {
|
|
|
2489
2679
|
return;
|
|
2490
2680
|
}
|
|
2491
2681
|
if (frame.type === "terminal-graphics-request") {
|
|
2492
|
-
this.#requestGraphics(frame.pane, frame.digests);
|
|
2682
|
+
this.#requestGraphics(frame.pane, frame.digests, frame.preferInline === true);
|
|
2683
|
+
return;
|
|
2684
|
+
}
|
|
2685
|
+
if (frame.type === "terminal-graphics-local-release") {
|
|
2686
|
+
const lease = this.#localLeases.get(frame.leaseId);
|
|
2687
|
+
if (
|
|
2688
|
+
lease &&
|
|
2689
|
+
lease.pane === frame.pane &&
|
|
2690
|
+
frame.generation === this.#localImages?.identity.generation
|
|
2691
|
+
) {
|
|
2692
|
+
this.#localLeases.delete(frame.leaseId);
|
|
2693
|
+
void this.#localImages
|
|
2694
|
+
?.release({
|
|
2695
|
+
generation: frame.generation,
|
|
2696
|
+
client: this.#descriptor.leaseId,
|
|
2697
|
+
id: frame.leaseId,
|
|
2698
|
+
})
|
|
2699
|
+
.catch(() => {});
|
|
2700
|
+
}
|
|
2493
2701
|
return;
|
|
2494
2702
|
}
|
|
2495
2703
|
if (frame.type === "terminal-cell-pixels") {
|
|
@@ -2560,6 +2768,7 @@ export class PaneStreamLiveConnection {
|
|
|
2560
2768
|
ingressAtMicros,
|
|
2561
2769
|
callbackAtSharedMicros,
|
|
2562
2770
|
ingressAtSharedMicros,
|
|
2771
|
+
frame.expectedLifetime,
|
|
2563
2772
|
);
|
|
2564
2773
|
};
|
|
2565
2774
|
|
|
@@ -2704,7 +2913,7 @@ export class PaneStreamLiveConnection {
|
|
|
2704
2913
|
#acceptInput(
|
|
2705
2914
|
pane: string,
|
|
2706
2915
|
seq: number,
|
|
2707
|
-
kind: "text" | "key",
|
|
2916
|
+
kind: "text" | "key" | "bytes",
|
|
2708
2917
|
data: string,
|
|
2709
2918
|
frameBytes: number,
|
|
2710
2919
|
performanceTraceId?: string,
|
|
@@ -2712,7 +2921,28 @@ export class PaneStreamLiveConnection {
|
|
|
2712
2921
|
ingressAtMicros: number | null = null,
|
|
2713
2922
|
callbackAtSharedMicros: number | null = null,
|
|
2714
2923
|
ingressAtSharedMicros: number | null = null,
|
|
2924
|
+
expectedLifetime?: PaneStreamInputLifetime,
|
|
2715
2925
|
): void {
|
|
2926
|
+
if (
|
|
2927
|
+
kind === "bytes" &&
|
|
2928
|
+
(!this.#sessionRuntimeBinding ||
|
|
2929
|
+
!this.#descriptor.terminalDelivery?.protocolVersions.includes(
|
|
2930
|
+
PANE_STREAM_INPUT_BYTES_CAPABILITY_VERSION,
|
|
2931
|
+
))
|
|
2932
|
+
) {
|
|
2933
|
+
this.#failProtocol("input-rejected");
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
if (
|
|
2937
|
+
expectedLifetime &&
|
|
2938
|
+
(!this.#sessionRuntimeBinding ||
|
|
2939
|
+
!this.#descriptor.terminalDelivery?.protocolVersions.includes(
|
|
2940
|
+
PANE_STREAM_INPUT_LIFETIME_CAPABILITY_VERSION,
|
|
2941
|
+
))
|
|
2942
|
+
) {
|
|
2943
|
+
this.#failProtocol("input-rejected");
|
|
2944
|
+
return;
|
|
2945
|
+
}
|
|
2716
2946
|
if (this.#descriptor.viewerMode !== "interactive") {
|
|
2717
2947
|
this.#failProtocol("input-rejected");
|
|
2718
2948
|
return;
|
|
@@ -2832,9 +3062,11 @@ export class PaneStreamLiveConnection {
|
|
|
2832
3062
|
}
|
|
2833
3063
|
}
|
|
2834
3064
|
: undefined,
|
|
3065
|
+
expectedLifetime,
|
|
2835
3066
|
);
|
|
2836
3067
|
else if (kind === "text") channel.sub!.sendText(data);
|
|
2837
|
-
else channel.sub!.sendKey(data);
|
|
3068
|
+
else if (kind === "key") channel.sub!.sendKey(data);
|
|
3069
|
+
else throw new Error("Byte input requires semantic runtime");
|
|
2838
3070
|
let ackStartedAtMicros: number | null = null;
|
|
2839
3071
|
let ackStartedAtSharedMicros: number | null = null;
|
|
2840
3072
|
if (trace) {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { LocalImageOwner } from "../graphics/local-image-owner.ts";
|
|
1
2
|
import type { SemanticPaneCatalog } from "../attachments/semantic-pane-catalog.ts";
|
|
2
3
|
import type { SessionRuntimeRegistry } from "../session-runtime/registry.ts";
|
|
3
4
|
import { SessionRuntimeTransportBinder } from "../session-runtime/transport-binding.ts";
|
|
@@ -9,6 +10,7 @@ import {
|
|
|
9
10
|
} from "./pane-stream-websocket.ts";
|
|
10
11
|
|
|
11
12
|
export interface PaneStreamRuntimeOptions {
|
|
13
|
+
readonly localImages?: LocalImageOwner;
|
|
12
14
|
readonly daemonInstanceId: string;
|
|
13
15
|
readonly webSocketUrl: string;
|
|
14
16
|
/** Canonical daemon-generation session/control owner. */
|
|
@@ -38,6 +40,7 @@ export class PaneStreamRuntime {
|
|
|
38
40
|
const transportBinder = new SessionRuntimeTransportBinder(options.sessionRuntimeRegistry);
|
|
39
41
|
this.coordinator = new PaneStreamAdmissionCoordinator({
|
|
40
42
|
...options.admission,
|
|
43
|
+
localImages: options.localImages,
|
|
41
44
|
daemonInstanceId: options.daemonInstanceId,
|
|
42
45
|
webSocketUrl: options.webSocketUrl,
|
|
43
46
|
leaseManager,
|