tmux-ide 2.9.2 → 2.9.3
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/README.md +1 -1
- package/bin/cli.js +2071 -625
- package/package.json +14 -5
- package/packages/contracts/src/desktop-host.ts +3 -0
- package/packages/contracts/src/saved-machines.ts +19 -0
- package/packages/contracts/src/terminal-replica.ts +1 -1
- package/packages/core/package.json +3 -2
- package/packages/core/src/terminal-delivery.ts +51 -3
- package/packages/core/src/terminal-fnv64-wasm.ts +4 -3
- package/packages/core/src/terminal-replica-hash-cache.ts +129 -31
- package/packages/core/src/terminal-replica-owned-row.ts +27 -0
- package/packages/core/src/terminal-replica.ts +13 -28
- package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +96 -10
- package/packages/daemon/dist/command-center/resources/saved-machine-route.js +40 -1
- package/packages/daemon/dist/command-center/ws-events.js +5 -24
- package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +8 -0
- package/packages/daemon/dist/lib/canonical-daemon.js +28 -2
- package/packages/daemon/dist/lib/daemon-embed.js +1 -1
- package/packages/daemon/dist/lib/daemon-service-manager.js +122 -0
- package/packages/daemon/dist/lib/daemon-service-plan.js +127 -0
- package/packages/daemon/dist/lib/daemon-service.js +259 -0
- package/packages/daemon/dist/lib/install-origin.js +2 -0
- package/packages/daemon/dist/lib/local-fleet-request.js +9 -3
- package/packages/daemon/dist/lib/log.js +6 -10
- package/packages/daemon/dist/lib/managed-installer-update.js +67 -0
- package/packages/daemon/dist/lib/{tui-download-lock.js → private-operation-lock.js} +11 -11
- package/packages/daemon/dist/lib/remote-daemon-info.js +3 -2
- package/packages/daemon/dist/lib/remote-daemon-lifecycle.js +4 -4
- package/packages/daemon/dist/lib/remote-tmux-command.js +10 -0
- package/packages/daemon/dist/lib/runtime-namespace.js +28 -16
- package/packages/daemon/dist/lib/saved-machines.js +40 -2
- package/packages/daemon/dist/lib/ssh-daemon-transport.js +24 -9
- package/packages/daemon/dist/lib/tmux-server-owner.js +1 -1
- package/packages/daemon/dist/lib/tmux-server-registration.js +21 -3
- package/packages/daemon/dist/lib/tui-binary.js +2 -2
- package/packages/daemon/dist/lib/update.js +18 -0
- package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +24 -1
- package/packages/daemon/dist/machines.js +40 -4
- 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/attachments/direct-websocket.js +4 -1
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +22 -13
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +101 -3
- package/packages/daemon/dist/terminal/mirror/registered-window-guard.js +62 -0
- package/packages/daemon/dist/terminal/mirror/session-channel.js +75 -4
- package/packages/daemon/dist/terminal/mirror/shared-window-index.js +45 -0
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +11 -1
- package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +1 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +56 -7
- package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
- package/packages/daemon/dist/tui/mirror/dialog-model.js +2 -35
- package/packages/daemon/dist/tui/mirror/runtime/application-daemon-authority-owner.js +8 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +20 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +7 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-authority.js +59 -9
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +27 -19
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +23 -18
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-startup.js +31 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +7 -3
- package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +3 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +146 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +201 -50
- package/packages/daemon/dist/tui/mirror/runtime/bounded-performance-record-writer.js +50 -10
- package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -5
- package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +3 -0
- package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +8 -2
- package/packages/daemon/dist/tui/mirror/terminal-text.js +35 -0
- package/packages/daemon/dist/tui/mirror/workspace/connection-feedback.js +5 -3
- package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +100 -10
- package/packages/daemon/src/command-center/resources/saved-machine-route.ts +48 -1
- package/packages/daemon/src/command-center/ws-events.ts +5 -25
- package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +12 -0
- package/packages/daemon/src/lib/canonical-daemon.ts +44 -2
- package/packages/daemon/src/lib/daemon-embed.ts +1 -0
- package/packages/daemon/src/lib/daemon-service-manager.ts +152 -0
- package/packages/daemon/src/lib/daemon-service-plan.ts +153 -0
- package/packages/daemon/src/lib/daemon-service.ts +301 -0
- package/packages/daemon/src/lib/install-origin.ts +5 -0
- package/packages/daemon/src/lib/local-fleet-request.ts +19 -3
- package/packages/daemon/src/lib/log.ts +13 -9
- package/packages/daemon/src/lib/managed-installer-update.ts +79 -0
- package/packages/daemon/src/lib/{tui-download-lock.ts → private-operation-lock.ts} +15 -11
- package/packages/daemon/src/lib/remote-daemon-info.ts +3 -3
- package/packages/daemon/src/lib/remote-daemon-lifecycle.ts +4 -4
- package/packages/daemon/src/lib/remote-tmux-command.ts +12 -0
- package/packages/daemon/src/lib/runtime-namespace.ts +27 -18
- package/packages/daemon/src/lib/saved-machines.ts +40 -1
- package/packages/daemon/src/lib/ssh-daemon-transport.ts +25 -7
- package/packages/daemon/src/lib/tmux-server-owner.ts +1 -0
- package/packages/daemon/src/lib/tmux-server-registration.ts +21 -3
- package/packages/daemon/src/lib/tui-binary.ts +2 -2
- package/packages/daemon/src/lib/update.ts +24 -0
- package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +22 -0
- package/packages/daemon/src/machines.ts +53 -5
- package/packages/daemon/src/terminal/attachments/direct-websocket.ts +8 -2
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +105 -3
- package/packages/daemon/src/terminal/mirror/registered-window-guard.ts +66 -0
- package/packages/daemon/src/terminal/mirror/session-channel.ts +92 -4
- package/packages/daemon/src/terminal/mirror/shared-window-index.ts +42 -0
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -1
- package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +2 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +70 -8
- package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +3 -1
- package/packages/daemon/src/tui/mirror/dialog-model.ts +2 -32
- package/packages/daemon/src/tui/mirror/runtime/application-daemon-authority-owner.ts +8 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +19 -2
- package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +6 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-authority.ts +60 -11
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +39 -33
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +31 -23
- package/packages/daemon/src/tui/mirror/runtime/application-machine-startup.ts +38 -2
- package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +7 -4
- package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +1 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +47 -46
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +165 -2
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +207 -53
- package/packages/daemon/src/tui/mirror/runtime/bounded-performance-record-writer.ts +49 -9
- package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +6 -5
- package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +4 -0
- package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +8 -3
- package/packages/daemon/src/tui/mirror/terminal-text.ts +32 -0
- package/packages/daemon/src/tui/mirror/workspace/connection-feedback.ts +6 -3
- package/packages/daemon-client/src/fleet-connection-status.ts +10 -1
- package/packages/daemon-client/src/workspace-client.ts +4 -1
- package/packages/contracts/src/__tests__/actions-contract.test.ts +0 -119
- package/packages/contracts/src/__tests__/agent-graph-overlay.test.ts +0 -428
- package/packages/contracts/src/__tests__/app-window-mutation.test.ts +0 -51
- package/packages/contracts/src/__tests__/app-window-state.test.ts +0 -34
- package/packages/contracts/src/__tests__/application-shell.test.ts +0 -645
- package/packages/contracts/src/__tests__/automation-operations.test.ts +0 -78
- package/packages/contracts/src/__tests__/causal-cell.test.ts +0 -47
- package/packages/contracts/src/__tests__/client-view-state.test.ts +0 -115
- package/packages/contracts/src/__tests__/cohesion-fixture.test.ts +0 -106
- package/packages/contracts/src/__tests__/commands.test.ts +0 -86
- package/packages/contracts/src/__tests__/control.test.ts +0 -154
- package/packages/contracts/src/__tests__/daemon-events.test.ts +0 -405
- package/packages/contracts/src/__tests__/daemon-resources.test.ts +0 -143
- package/packages/contracts/src/__tests__/daemon-wire.test.ts +0 -149
- package/packages/contracts/src/__tests__/desktop-host.test.ts +0 -269
- package/packages/contracts/src/__tests__/desktop-missions.test.ts +0 -215
- package/packages/contracts/src/__tests__/experience-conformance.test.ts +0 -153
- package/packages/contracts/src/__tests__/experience-kernel-boundary.test.ts +0 -113
- package/packages/contracts/src/__tests__/experience-shell.test.ts +0 -65
- package/packages/contracts/src/__tests__/fixtures/daemon-wire-v2.json +0 -35
- package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +0 -35
- package/packages/contracts/src/__tests__/fixtures/daemon-wire-v4.json +0 -35
- package/packages/contracts/src/__tests__/fixtures/scoped-receipt.ts +0 -62
- package/packages/contracts/src/__tests__/fleet-agent-graph.test.ts +0 -231
- package/packages/contracts/src/__tests__/fleet-catalog.test.ts +0 -270
- package/packages/contracts/src/__tests__/fleet-lifecycle.test.ts +0 -62
- package/packages/contracts/src/__tests__/focus-overlay.test.ts +0 -110
- package/packages/contracts/src/__tests__/ide-config.test.ts +0 -64
- package/packages/contracts/src/__tests__/interaction-evidence.test.ts +0 -288
- package/packages/contracts/src/__tests__/interaction-receipt-v2.test.ts +0 -72
- package/packages/contracts/src/__tests__/issue-error.test.ts +0 -78
- package/packages/contracts/src/__tests__/mission-projections.test.ts +0 -186
- package/packages/contracts/src/__tests__/missions.test.ts +0 -272
- package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +0 -242
- package/packages/contracts/src/__tests__/pane-appearance.test.ts +0 -146
- package/packages/contracts/src/__tests__/pane-read-result.test.ts +0 -55
- package/packages/contracts/src/__tests__/pane-stream.test.ts +0 -536
- package/packages/contracts/src/__tests__/pane-widget-marker.test.ts +0 -327
- package/packages/contracts/src/__tests__/performance-metrics.test.ts +0 -73
- package/packages/contracts/src/__tests__/performance-qualification.test.ts +0 -137
- package/packages/contracts/src/__tests__/rich-card-widget.test.ts +0 -29
- package/packages/contracts/src/__tests__/session-runtime.test.ts +0 -512
- package/packages/contracts/src/__tests__/startup-readiness.test.ts +0 -409
- package/packages/contracts/src/__tests__/terminal-attachment-stream.test.ts +0 -72
- package/packages/contracts/src/__tests__/terminal-attachments.test.ts +0 -329
- package/packages/contracts/src/__tests__/terminal-delivery.test.ts +0 -88
- package/packages/contracts/src/__tests__/terminal-runtime-inventory.test.ts +0 -68
- package/packages/contracts/src/__tests__/terminals.test.ts +0 -87
- package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +0 -166
- package/packages/contracts/src/__tests__/transport-contract-loading.test.ts +0 -91
- package/packages/contracts/src/__tests__/visual-tokens.test.ts +0 -224
- package/packages/contracts/src/__tests__/window-links.test.ts +0 -243
- package/packages/contracts/src/__tests__/workspace-changes-resource.test.ts +0 -317
- package/packages/contracts/src/__tests__/workspace-changes-view.test.ts +0 -122
- package/packages/contracts/src/__tests__/workspace-config.test.ts +0 -294
- package/packages/contracts/src/__tests__/workspace-files-resource.test.ts +0 -261
- package/packages/contracts/src/__tests__/workspace-files-tree.test.ts +0 -147
- package/packages/contracts/src/__tests__/workspace-pane-creation.test.ts +0 -249
- package/packages/contracts/src/__tests__/workspace-promotion.test.ts +0 -94
- package/packages/contracts/src/__tests__/workspace-resource-identity.test.ts +0 -90
- package/packages/contracts/src/daemon-resource-request.test.ts +0 -192
- package/packages/contracts/src/native-operation-identity.test.ts +0 -23
- package/packages/contracts/src/workspace-admission.test.ts +0 -21
- package/packages/contracts/src/workspace-catalog-resource.test.ts +0 -146
- package/packages/core/src/agent-provisioning.test.ts +0 -42
- package/packages/core/src/application-shell-session.test.ts +0 -105
- package/packages/core/src/interaction-evidence.test.ts +0 -134
- package/packages/core/src/interaction-flow.test.ts +0 -66
- package/packages/core/src/interaction-receipts.test.ts +0 -671
- package/packages/core/src/navigator.test.ts +0 -34
- package/packages/core/src/optimistic-projection-conformance.test.ts +0 -75
- package/packages/core/src/optimistic-projection.test.ts +0 -118
- package/packages/core/src/performance-metrics.test.ts +0 -121
- package/packages/core/src/performance-qualification.test.ts +0 -283
- package/packages/core/src/saved-machines.test.ts +0 -80
- package/packages/core/src/terminal-compact-cold.test.ts +0 -87
- package/packages/core/src/terminal-conformance.test.ts +0 -48
- package/packages/core/src/terminal-delivery-cache-lifetime.test.ts +0 -167
- package/packages/core/src/terminal-delivery-encode-cooperative.test.ts +0 -140
- package/packages/core/src/terminal-delivery-representation-hash.test.ts +0 -77
- package/packages/core/src/terminal-delivery.test.ts +0 -1471
- package/packages/core/src/terminal-fnv64-wasm.test.ts +0 -61
- package/packages/core/src/terminal-replica-cold.test.ts +0 -31
- package/packages/core/src/terminal-replica-cooperative.test.ts +0 -170
- package/packages/core/src/terminal-replica-hash-cache.test.ts +0 -283
- package/packages/core/src/terminal-replica.test.ts +0 -347
- package/packages/core/src/workspace-selection.test.ts +0 -63
- package/packages/daemon/dist/__tests__/support.js +0 -33
- package/packages/daemon/dist/lib/__tests__/fixtures/workspace-state-writer.js +0 -58
- package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +0 -400
- package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +0 -182
- package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +0 -98
- package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +0 -156
- package/packages/daemon/src/tui/mirror/__snapshots__/activity-surface-renderer.test.tsx.snap +0 -42
- package/packages/daemon/src/tui/mirror/__snapshots__/changes-terminal-surface-renderer.test.tsx.snap +0 -267
- package/packages/daemon/src/tui/mirror/__snapshots__/home-files-surface-renderer.test.tsx.snap +0 -267
- package/packages/daemon/src/tui/mirror/__snapshots__/missions-surface-renderer.test.tsx.snap +0 -134
- package/packages/daemon/src/tui/mirror/__snapshots__/recipes-gallery-renderer.test.tsx.snap +0 -141
- package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +0 -134
- package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +0 -87
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/agent-terminal-canvas-renderer.test.tsx.snap +0 -348
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +0 -134
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +0 -255
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/pane-frame-renderer.test.tsx.snap +0 -525
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +0 -31
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/workbench-shell-renderer.test.tsx.snap +0 -134
- package/packages/daemon-client/src/application-shell-session.test.ts +0 -196
- package/packages/daemon-client/src/automation-client.test.ts +0 -214
- package/packages/daemon-client/src/bootstrap-coordinator.test.ts +0 -145
- package/packages/daemon-client/src/connection-supervisor.test.ts +0 -109
- package/packages/daemon-client/src/direct-application-shell-transport.test.ts +0 -941
- package/packages/daemon-client/src/first-latest-coordinator.test.ts +0 -175
- package/packages/daemon-client/src/fleet-dial-scheduler.test.ts +0 -114
- package/packages/daemon-client/src/fleet-environments.test.ts +0 -40
- package/packages/daemon-client/src/generation-bound-store-timers.test.ts +0 -74
- package/packages/daemon-client/src/owner-action-client.test.ts +0 -245
- package/packages/daemon-client/src/pane-stream-client.test.ts +0 -2841
- package/packages/daemon-client/src/pane-stream-clock-calibration.test.ts +0 -207
- package/packages/daemon-client/src/pane-stream-loopback-benchmark.test.ts +0 -51
- package/packages/daemon-client/src/push-resource-session.test.ts +0 -509
- package/packages/daemon-client/src/resource-replica.test.ts +0 -186
- package/packages/daemon-client/src/runtime-resource-ledger.test.ts +0 -18
- package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +0 -114
- package/packages/daemon-client/src/terminal-fast-lane.test.ts +0 -835
- package/packages/daemon-client/src/tmux-server-client.test.ts +0 -279
- package/packages/daemon-client/src/tmux-server-interaction-events.test.ts +0 -230
- package/packages/daemon-client/src/workspace-catalog-v2.test.ts +0 -109
- package/packages/daemon-client/src/workspace-client-boundaries.test.ts +0 -59
- package/packages/daemon-client/src/workspace-client-operations.test.ts +0 -96
- package/packages/daemon-client/src/workspace-client.test.ts +0 -2912
- package/packages/daemon-client/src/workspace-event-supervisor.test.ts +0 -1086
- package/packages/daemon-client/src/workspace-pane-client.test.ts +0 -80
- package/packages/presentation/src/presentation-contract.test.ts +0 -63
- package/packages/tmux-bridge/src/runner.test.ts +0 -614
- package/packages/tmux-bridge/src/targeting.test.ts +0 -107
|
@@ -1,1471 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
TerminalDeliveryEnvelopeSchemaZ,
|
|
4
|
-
type TerminalDeliveryEnvelope,
|
|
5
|
-
type TerminalReplicaSnapshot,
|
|
6
|
-
} from "@tmux-ide/contracts";
|
|
7
|
-
import {
|
|
8
|
-
admitTerminalDeliveryChunk,
|
|
9
|
-
admitTerminalDeliveryEnvelope,
|
|
10
|
-
applyTerminalReplicaUpdate,
|
|
11
|
-
blankTerminalReplicaSnapshot,
|
|
12
|
-
commitTerminalDelivery,
|
|
13
|
-
completeTerminalDelivery,
|
|
14
|
-
createTerminalDeliveryClientState,
|
|
15
|
-
decodeCompactSemanticTerminalUpdate,
|
|
16
|
-
decodeSemanticTerminalUpdate,
|
|
17
|
-
decodeVerifiedCompactSemanticTerminalUpdate,
|
|
18
|
-
decodeVerifiedCompactSemanticTerminalUpdateCooperatively,
|
|
19
|
-
encodeAnsiTerminalPatchRepresentation,
|
|
20
|
-
encodeAnsiTerminalRepresentation,
|
|
21
|
-
encodeCompactSemanticTerminalUpdate,
|
|
22
|
-
encodeSemanticTerminalUpdate,
|
|
23
|
-
hashTerminalDeliveryRepresentation,
|
|
24
|
-
hashTerminalReplicaSnapshot,
|
|
25
|
-
negotiateTerminalDelivery,
|
|
26
|
-
nackTerminalDelivery,
|
|
27
|
-
preaccountSemanticTerminalUpdateBytes,
|
|
28
|
-
splitTerminalDeliveryChunks,
|
|
29
|
-
TerminalDeliveryAssembler,
|
|
30
|
-
TerminalDeliveryStateTooLargeError,
|
|
31
|
-
type CompactSemanticCommitProfile,
|
|
32
|
-
} from "./index.ts";
|
|
33
|
-
|
|
34
|
-
const generation = "00000000-0000-4000-8000-000000000001";
|
|
35
|
-
const nonce = "00000000-0000-4000-8000-000000000002";
|
|
36
|
-
const tx = "00000000-0000-4000-8000-000000000003";
|
|
37
|
-
|
|
38
|
-
function seedEnvelope(): { envelope: TerminalDeliveryEnvelope; bytes: Uint8Array } {
|
|
39
|
-
const snapshot = blankTerminalReplicaSnapshot(2, 1);
|
|
40
|
-
const bytes = encodeSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot });
|
|
41
|
-
return {
|
|
42
|
-
bytes,
|
|
43
|
-
envelope: TerminalDeliveryEnvelopeSchemaZ.parse({
|
|
44
|
-
type: "terminal.delivery",
|
|
45
|
-
workspaceName: "workspace",
|
|
46
|
-
semanticPaneId: "pane-a",
|
|
47
|
-
generation,
|
|
48
|
-
incarnation: `${generation}:0`,
|
|
49
|
-
deliveryNonce: nonce,
|
|
50
|
-
transactionId: tx,
|
|
51
|
-
protocolVersion: 1,
|
|
52
|
-
encoding: "semantic-v1",
|
|
53
|
-
frame: "seed",
|
|
54
|
-
baseRevision: null,
|
|
55
|
-
canonicalRevision: 0,
|
|
56
|
-
canonicalStateHash: hashTerminalReplicaSnapshot(snapshot),
|
|
57
|
-
representationHash: hashTerminalDeliveryRepresentation(bytes),
|
|
58
|
-
representationBytes: bytes.byteLength,
|
|
59
|
-
chunkCount: Math.max(1, Math.ceil(bytes.byteLength / (256 * 1024))),
|
|
60
|
-
canonicalEquivalent: true,
|
|
61
|
-
history: "complete",
|
|
62
|
-
richPlacements: false,
|
|
63
|
-
}),
|
|
64
|
-
};
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
describe("terminal delivery client", () => {
|
|
68
|
-
it("serializes canonical history before the viewport for an unknown renderer", () => {
|
|
69
|
-
const blank = blankTerminalReplicaSnapshot(4, 2);
|
|
70
|
-
const row = (text: string) => ({
|
|
71
|
-
...blank.grid[0]!,
|
|
72
|
-
cells: blank.grid[0]!.cells.map((cell, index) => ({
|
|
73
|
-
...cell,
|
|
74
|
-
grapheme: text[index] ?? " ",
|
|
75
|
-
})),
|
|
76
|
-
});
|
|
77
|
-
const target = {
|
|
78
|
-
...blank,
|
|
79
|
-
history: [row("A"), row("B")],
|
|
80
|
-
grid: [row("C"), row("D")],
|
|
81
|
-
};
|
|
82
|
-
const encoded = new TextDecoder().decode(encodeAnsiTerminalRepresentation(null, target));
|
|
83
|
-
expect(encoded).toContain("\u001b[3J");
|
|
84
|
-
expect(encoded.indexOf("A")).toBeLessThan(encoded.indexOf("B"));
|
|
85
|
-
expect(encoded.indexOf("B")).toBeLessThan(encoded.indexOf("C"));
|
|
86
|
-
expect(encoded.indexOf("C")).toBeLessThan(encoded.indexOf("D"));
|
|
87
|
-
});
|
|
88
|
-
|
|
89
|
-
it("encodes a rolling 5k history delta as bounded xterm scroll work", () => {
|
|
90
|
-
const blank = blankTerminalReplicaSnapshot(20, 5);
|
|
91
|
-
const history = Array.from({ length: 5_000 }, () => blank.grid[0]!);
|
|
92
|
-
const baseline = { ...blank, history };
|
|
93
|
-
const appended = { ...blank.grid[0]!, wrapped: false };
|
|
94
|
-
const latest = {
|
|
95
|
-
...blank.grid[0]!,
|
|
96
|
-
cells: blank.grid[0]!.cells.map((cell, index) => ({
|
|
97
|
-
...cell,
|
|
98
|
-
grapheme: index === 0 ? "Z" : " ",
|
|
99
|
-
})),
|
|
100
|
-
};
|
|
101
|
-
const target = {
|
|
102
|
-
...blank,
|
|
103
|
-
history: [...history, appended],
|
|
104
|
-
grid: [...blank.grid.slice(1), latest],
|
|
105
|
-
};
|
|
106
|
-
const bytes = encodeAnsiTerminalPatchRepresentation(
|
|
107
|
-
{
|
|
108
|
-
historyDelta: { trim: 0, append: [appended] },
|
|
109
|
-
rows: [{ index: blank.rows - 1, row: latest }],
|
|
110
|
-
},
|
|
111
|
-
target,
|
|
112
|
-
baseline,
|
|
113
|
-
);
|
|
114
|
-
const encoded = new TextDecoder().decode(bytes);
|
|
115
|
-
expect(encoded).toContain("\u001b[5;1H\n");
|
|
116
|
-
expect(encoded).not.toContain("\u001b[2J");
|
|
117
|
-
expect(encoded).not.toContain("\u001b[3J");
|
|
118
|
-
expect(bytes.byteLength).toBeLessThan(256);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
it("rejects a history-only delta whose canonical viewport did not scroll", () => {
|
|
122
|
-
const baseline = blankTerminalReplicaSnapshot(20, 5);
|
|
123
|
-
const appended = baseline.grid[0]!;
|
|
124
|
-
const target = { ...baseline, history: [...baseline.history, appended] };
|
|
125
|
-
const encoded = new TextDecoder().decode(
|
|
126
|
-
encodeAnsiTerminalPatchRepresentation(
|
|
127
|
-
{ historyDelta: { trim: 0, append: [appended] }, rows: [] },
|
|
128
|
-
target,
|
|
129
|
-
baseline,
|
|
130
|
-
),
|
|
131
|
-
);
|
|
132
|
-
expect(encoded).toContain("\u001b[2J");
|
|
133
|
-
expect(encoded).toContain("\u001b[3J");
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it("rejects a capped history trim even when its viewport shift is otherwise coherent", () => {
|
|
137
|
-
const baseline = blankTerminalReplicaSnapshot(20, 5);
|
|
138
|
-
const latest = baseline.grid[4]!;
|
|
139
|
-
const target = {
|
|
140
|
-
...baseline,
|
|
141
|
-
history: [baseline.grid[0]!],
|
|
142
|
-
grid: [...baseline.grid.slice(1), latest],
|
|
143
|
-
};
|
|
144
|
-
const encoded = new TextDecoder().decode(
|
|
145
|
-
encodeAnsiTerminalPatchRepresentation(
|
|
146
|
-
{
|
|
147
|
-
historyDelta: { trim: 1, append: [baseline.grid[0]!] },
|
|
148
|
-
rows: [{ index: 4, row: latest }],
|
|
149
|
-
},
|
|
150
|
-
target,
|
|
151
|
-
baseline,
|
|
152
|
-
),
|
|
153
|
-
);
|
|
154
|
-
expect(encoded).toContain("\u001b[2J");
|
|
155
|
-
expect(encoded).toContain("\u001b[3J");
|
|
156
|
-
});
|
|
157
|
-
|
|
158
|
-
it("encodes a proven multi-row shift with only exact bottom replacements", () => {
|
|
159
|
-
const blank = blankTerminalReplicaSnapshot(8, 4);
|
|
160
|
-
const row = (text: string) => ({
|
|
161
|
-
...blank.grid[0]!,
|
|
162
|
-
cells: blank.grid[0]!.cells.map((cell, index) => ({
|
|
163
|
-
...cell,
|
|
164
|
-
grapheme: text[index] ?? " ",
|
|
165
|
-
})),
|
|
166
|
-
});
|
|
167
|
-
const baseline = { ...blank, grid: [row("a"), row("b"), row("c"), row("d")] };
|
|
168
|
-
const bottom = [row("e"), row("f")];
|
|
169
|
-
const target = {
|
|
170
|
-
...baseline,
|
|
171
|
-
history: [baseline.grid[0]!, baseline.grid[1]!],
|
|
172
|
-
grid: [baseline.grid[2]!, baseline.grid[3]!, ...bottom],
|
|
173
|
-
};
|
|
174
|
-
const encoded = new TextDecoder().decode(
|
|
175
|
-
encodeAnsiTerminalPatchRepresentation(
|
|
176
|
-
{
|
|
177
|
-
historyDelta: { trim: 0, append: baseline.grid.slice(0, 2) },
|
|
178
|
-
rows: [
|
|
179
|
-
{ index: 2, row: bottom[0]! },
|
|
180
|
-
{ index: 3, row: bottom[1]! },
|
|
181
|
-
],
|
|
182
|
-
},
|
|
183
|
-
target,
|
|
184
|
-
baseline,
|
|
185
|
-
),
|
|
186
|
-
);
|
|
187
|
-
expect(encoded).toContain("\u001b[4;1H\n\n");
|
|
188
|
-
expect(encoded).not.toContain("\u001b[2J");
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
it("rejects a history delta when any unpatched viewport row is not the exact shift", () => {
|
|
192
|
-
const blank = blankTerminalReplicaSnapshot(8, 4);
|
|
193
|
-
const changed = {
|
|
194
|
-
...blank.grid[0]!,
|
|
195
|
-
cells: blank.grid[0]!.cells.map((cell, index) => ({
|
|
196
|
-
...cell,
|
|
197
|
-
grapheme: index === 0 ? "X" : " ",
|
|
198
|
-
})),
|
|
199
|
-
};
|
|
200
|
-
const target = {
|
|
201
|
-
...blank,
|
|
202
|
-
history: [blank.grid[0]!],
|
|
203
|
-
grid: [changed, blank.grid[2]!, blank.grid[3]!, blank.grid[3]!],
|
|
204
|
-
};
|
|
205
|
-
const encoded = new TextDecoder().decode(
|
|
206
|
-
encodeAnsiTerminalPatchRepresentation(
|
|
207
|
-
{
|
|
208
|
-
historyDelta: { trim: 0, append: [blank.grid[0]!] },
|
|
209
|
-
rows: [{ index: 3, row: blank.grid[3]! }],
|
|
210
|
-
},
|
|
211
|
-
target,
|
|
212
|
-
blank,
|
|
213
|
-
),
|
|
214
|
-
);
|
|
215
|
-
expect(encoded).toContain("\u001b[2J");
|
|
216
|
-
expect(encoded).toContain("\u001b[3J");
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
it("projects every input-affecting emulator mode on seed and mode-only patch", () => {
|
|
220
|
-
const blank = blankTerminalReplicaSnapshot(8, 3);
|
|
221
|
-
const enabled = {
|
|
222
|
-
...blank,
|
|
223
|
-
modes: {
|
|
224
|
-
...blank.modes,
|
|
225
|
-
applicationCursor: true,
|
|
226
|
-
applicationKeypad: true,
|
|
227
|
-
bracketedPaste: true,
|
|
228
|
-
insert: true,
|
|
229
|
-
origin: true,
|
|
230
|
-
mouseTracking: true,
|
|
231
|
-
mouseProtocol: "drag" as const,
|
|
232
|
-
mouseEncoding: "sgr" as const,
|
|
233
|
-
synchronizedOutput: true,
|
|
234
|
-
},
|
|
235
|
-
};
|
|
236
|
-
const seed = new TextDecoder().decode(encodeAnsiTerminalRepresentation(null, enabled));
|
|
237
|
-
for (const sequence of [
|
|
238
|
-
"\u001b[?1h",
|
|
239
|
-
"\u001b=",
|
|
240
|
-
"\u001b[?2004h",
|
|
241
|
-
"\u001b[4h",
|
|
242
|
-
"\u001b[?6h",
|
|
243
|
-
"\u001b[?1002h",
|
|
244
|
-
"\u001b[?1006h",
|
|
245
|
-
"\u001b[?2026h",
|
|
246
|
-
])
|
|
247
|
-
expect(seed).toContain(sequence);
|
|
248
|
-
|
|
249
|
-
const reset = {
|
|
250
|
-
...enabled,
|
|
251
|
-
modes: { ...blank.modes, mouseProtocol: "none" as const, mouseEncoding: "default" as const },
|
|
252
|
-
};
|
|
253
|
-
const patch = new TextDecoder().decode(
|
|
254
|
-
encodeAnsiTerminalPatchRepresentation({ rows: [], modes: reset.modes }, reset, enabled),
|
|
255
|
-
);
|
|
256
|
-
expect(patch).not.toContain("\u001b[2J");
|
|
257
|
-
for (const sequence of [
|
|
258
|
-
"\u001b[?1l",
|
|
259
|
-
"\u001b>",
|
|
260
|
-
"\u001b[?2004l",
|
|
261
|
-
"\u001b[4l",
|
|
262
|
-
"\u001b[?6l",
|
|
263
|
-
"\u001b[?1002l",
|
|
264
|
-
"\u001b[?1006l",
|
|
265
|
-
"\u001b[?2026l",
|
|
266
|
-
])
|
|
267
|
-
expect(patch).toContain(sequence);
|
|
268
|
-
});
|
|
269
|
-
|
|
270
|
-
it.each([
|
|
271
|
-
["x10", "default", "\u001b[?9h", null],
|
|
272
|
-
["vt200", "utf8", "\u001b[?1000h", "\u001b[?1005h"],
|
|
273
|
-
["drag", "sgr", "\u001b[?1002h", "\u001b[?1006h"],
|
|
274
|
-
["any", "sgr-pixels", "\u001b[?1003h", "\u001b[?1016h"],
|
|
275
|
-
] as const)(
|
|
276
|
-
"projects %s mouse tracking with %s encoding",
|
|
277
|
-
(protocol, encoding, tracking, encoded) => {
|
|
278
|
-
const blank = blankTerminalReplicaSnapshot(8, 3);
|
|
279
|
-
const target = {
|
|
280
|
-
...blank,
|
|
281
|
-
modes: {
|
|
282
|
-
...blank.modes,
|
|
283
|
-
mouseTracking: true,
|
|
284
|
-
mouseProtocol: protocol,
|
|
285
|
-
mouseEncoding: encoding,
|
|
286
|
-
},
|
|
287
|
-
};
|
|
288
|
-
const seed = new TextDecoder().decode(encodeAnsiTerminalRepresentation(null, target));
|
|
289
|
-
expect(seed).toContain(tracking);
|
|
290
|
-
if (encoded) expect(seed).toContain(encoded);
|
|
291
|
-
},
|
|
292
|
-
);
|
|
293
|
-
|
|
294
|
-
it("repaints an unprovable history delta instead of inventing scrollback", () => {
|
|
295
|
-
const baseline = blankTerminalReplicaSnapshot(20, 5);
|
|
296
|
-
const appended = {
|
|
297
|
-
...baseline.grid[0]!,
|
|
298
|
-
cells: baseline.grid[0]!.cells.map((cell, index) => ({
|
|
299
|
-
...cell,
|
|
300
|
-
grapheme: index === 0 ? "X" : " ",
|
|
301
|
-
})),
|
|
302
|
-
};
|
|
303
|
-
const target = { ...baseline, history: [appended] };
|
|
304
|
-
const encoded = new TextDecoder().decode(
|
|
305
|
-
encodeAnsiTerminalPatchRepresentation(
|
|
306
|
-
{ historyDelta: { trim: 0, append: [appended] }, rows: [] },
|
|
307
|
-
target,
|
|
308
|
-
baseline,
|
|
309
|
-
),
|
|
310
|
-
);
|
|
311
|
-
expect(encoded).toContain("\u001b[2J");
|
|
312
|
-
expect(encoded).toContain("\u001b[3J");
|
|
313
|
-
});
|
|
314
|
-
|
|
315
|
-
it("paints unused width-one cells as spaces in ANSI seeds and patches", () => {
|
|
316
|
-
const baseline = blankTerminalReplicaSnapshot(4, 2);
|
|
317
|
-
const row = {
|
|
318
|
-
...baseline.grid[0]!,
|
|
319
|
-
cells: baseline.grid[0]!.cells.map((cell, index) => ({
|
|
320
|
-
...cell,
|
|
321
|
-
grapheme: ["A", "", "B", ""][index]!,
|
|
322
|
-
})),
|
|
323
|
-
};
|
|
324
|
-
const target = { ...baseline, grid: [row, baseline.grid[1]!] };
|
|
325
|
-
for (const bytes of [
|
|
326
|
-
encodeAnsiTerminalRepresentation(null, target),
|
|
327
|
-
encodeAnsiTerminalPatchRepresentation({ rows: [{ index: 0, row }] }, target, baseline),
|
|
328
|
-
]) {
|
|
329
|
-
const ansi = new TextDecoder()
|
|
330
|
-
.decode(bytes)
|
|
331
|
-
.replaceAll("\u001b", "")
|
|
332
|
-
.replace(/\[[0-9;]*m/gu, "");
|
|
333
|
-
expect(ansi).toContain("A B ");
|
|
334
|
-
}
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
it("encodes ordinary ANSI deltas from dirty rows without inspecting the full grid", () => {
|
|
338
|
-
const snapshot = blankTerminalReplicaSnapshot(4, 3);
|
|
339
|
-
const inaccessibleGrid = new Proxy(snapshot.grid, {
|
|
340
|
-
get() {
|
|
341
|
-
throw new Error("unchanged grid row was inspected");
|
|
342
|
-
},
|
|
343
|
-
});
|
|
344
|
-
const target = { ...snapshot, grid: inaccessibleGrid };
|
|
345
|
-
const bytes = encodeAnsiTerminalPatchRepresentation(
|
|
346
|
-
{ rows: [{ index: 1, row: snapshot.grid[1]! }] },
|
|
347
|
-
target,
|
|
348
|
-
snapshot,
|
|
349
|
-
);
|
|
350
|
-
expect(new TextDecoder().decode(bytes)).toContain("\u001b[2;1H");
|
|
351
|
-
});
|
|
352
|
-
it("encodes cursor-only patches without a grid walk and preserves DECSCUSR", () => {
|
|
353
|
-
const baseline = blankTerminalReplicaSnapshot(4, 3);
|
|
354
|
-
const inaccessibleGrid = new Proxy(baseline.grid, {
|
|
355
|
-
get() {
|
|
356
|
-
throw new Error("cursor-only patch inspected the grid");
|
|
357
|
-
},
|
|
358
|
-
});
|
|
359
|
-
const target = {
|
|
360
|
-
...baseline,
|
|
361
|
-
grid: inaccessibleGrid,
|
|
362
|
-
cursor: { x: 2, y: 1, hidden: false, style: "bar" as const, blink: false },
|
|
363
|
-
};
|
|
364
|
-
const output = new TextDecoder().decode(
|
|
365
|
-
encodeAnsiTerminalPatchRepresentation({ rows: [], cursor: target.cursor }, target, baseline),
|
|
366
|
-
);
|
|
367
|
-
expect(output).toBe("\u001b[2;3H\u001b[6 q\u001b[?25h");
|
|
368
|
-
});
|
|
369
|
-
|
|
370
|
-
it("restores the canonical wraparound mode after row painting and mode-only patches", () => {
|
|
371
|
-
const baseline = blankTerminalReplicaSnapshot(4, 3);
|
|
372
|
-
const target = {
|
|
373
|
-
...baseline,
|
|
374
|
-
modes: { ...baseline.modes, wraparound: false },
|
|
375
|
-
};
|
|
376
|
-
const modeOnly = new TextDecoder().decode(
|
|
377
|
-
encodeAnsiTerminalPatchRepresentation({ rows: [], modes: target.modes }, target, baseline),
|
|
378
|
-
);
|
|
379
|
-
expect(modeOnly.startsWith("\u001b[?7l")).toBe(true);
|
|
380
|
-
const painted = new TextDecoder().decode(encodeAnsiTerminalRepresentation(null, target));
|
|
381
|
-
expect(painted).toContain("\u001b[?7h");
|
|
382
|
-
expect(painted.lastIndexOf("\u001b[?7l")).toBeGreaterThan(painted.lastIndexOf("\u001b[?7h"));
|
|
383
|
-
});
|
|
384
|
-
|
|
385
|
-
it("uses an exact buffer repaint when a dirty row changes soft-wrap topology", () => {
|
|
386
|
-
const baseline = blankTerminalReplicaSnapshot(4, 3);
|
|
387
|
-
const wrappedRow = { ...baseline.grid[1]!, wrapped: true };
|
|
388
|
-
const wrapped = { ...baseline, grid: [baseline.grid[0]!, wrappedRow, baseline.grid[2]!] };
|
|
389
|
-
const enter = new TextDecoder().decode(
|
|
390
|
-
encodeAnsiTerminalPatchRepresentation(
|
|
391
|
-
{ rows: [{ index: 1, row: wrappedRow }] },
|
|
392
|
-
wrapped,
|
|
393
|
-
baseline,
|
|
394
|
-
),
|
|
395
|
-
);
|
|
396
|
-
const leave = new TextDecoder().decode(
|
|
397
|
-
encodeAnsiTerminalPatchRepresentation(
|
|
398
|
-
{ rows: [{ index: 1, row: baseline.grid[1]! }] },
|
|
399
|
-
baseline,
|
|
400
|
-
wrapped,
|
|
401
|
-
),
|
|
402
|
-
);
|
|
403
|
-
expect(enter.startsWith("\u001b[?1049l\u001b[0m\u001b[2J\u001b[3J\u001b[H")).toBe(true);
|
|
404
|
-
expect(leave.startsWith("\u001b[?1049l\u001b[0m\u001b[2J\u001b[3J\u001b[H")).toBe(true);
|
|
405
|
-
});
|
|
406
|
-
|
|
407
|
-
it("switches 1049 buffers before a full baseline-aware repaint and restores normal exactly", () => {
|
|
408
|
-
const normal = blankTerminalReplicaSnapshot(2, 1);
|
|
409
|
-
const alternate = { ...normal, modes: { ...normal.modes, alternateScreen: true } };
|
|
410
|
-
const enter = new TextDecoder().decode(
|
|
411
|
-
encodeAnsiTerminalPatchRepresentation(
|
|
412
|
-
{ rows: [], modes: alternate.modes },
|
|
413
|
-
alternate,
|
|
414
|
-
normal,
|
|
415
|
-
),
|
|
416
|
-
);
|
|
417
|
-
const leave = new TextDecoder().decode(
|
|
418
|
-
encodeAnsiTerminalPatchRepresentation({ rows: [], modes: normal.modes }, normal, alternate),
|
|
419
|
-
);
|
|
420
|
-
expect(enter.startsWith("\u001b[?1049h\u001b[0m\u001b[2J\u001b[3J\u001b[H")).toBe(true);
|
|
421
|
-
expect(leave.startsWith("\u001b[?1049l\u001b[0m\u001b[2J\u001b[3J\u001b[H")).toBe(true);
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it("enters 1049 before a seed that begins in the alternate buffer", () => {
|
|
425
|
-
const normal = blankTerminalReplicaSnapshot(2, 1);
|
|
426
|
-
const alternate = { ...normal, modes: { ...normal.modes, alternateScreen: true } };
|
|
427
|
-
const output = new TextDecoder().decode(encodeAnsiTerminalRepresentation(null, alternate));
|
|
428
|
-
expect(output.startsWith("\u001b[?1049h\u001b[0m\u001b[2J\u001b[3J\u001b[H")).toBe(true);
|
|
429
|
-
});
|
|
430
|
-
it("exits 1049 before a normal seed and encodes every DECSCUSR visibility combination", () => {
|
|
431
|
-
const baseline = blankTerminalReplicaSnapshot(2, 1);
|
|
432
|
-
const seed = new TextDecoder().decode(encodeAnsiTerminalRepresentation(null, baseline));
|
|
433
|
-
expect(seed.startsWith("\u001b[?1049l\u001b[0m\u001b[2J\u001b[3J\u001b[H")).toBe(true);
|
|
434
|
-
const expected = [
|
|
435
|
-
["block", true, 1],
|
|
436
|
-
["block", false, 2],
|
|
437
|
-
["underline", true, 3],
|
|
438
|
-
["underline", false, 4],
|
|
439
|
-
["bar", true, 5],
|
|
440
|
-
["bar", false, 6],
|
|
441
|
-
] as const;
|
|
442
|
-
for (const [style, blink, shape] of expected) {
|
|
443
|
-
const target = { ...baseline, cursor: { x: 0, y: 0, hidden: true, style, blink } };
|
|
444
|
-
const output = new TextDecoder().decode(
|
|
445
|
-
encodeAnsiTerminalPatchRepresentation(
|
|
446
|
-
{ rows: [], cursor: target.cursor },
|
|
447
|
-
target,
|
|
448
|
-
baseline,
|
|
449
|
-
),
|
|
450
|
-
);
|
|
451
|
-
expect(output).toBe(`\u001b[1;1H\u001b[${shape} q\u001b[?25l`);
|
|
452
|
-
}
|
|
453
|
-
});
|
|
454
|
-
it("negotiates deterministically and rejects unsupported rich ANSI", () => {
|
|
455
|
-
expect(
|
|
456
|
-
negotiateTerminalDelivery(
|
|
457
|
-
{
|
|
458
|
-
protocolVersions: [1],
|
|
459
|
-
encodings: ["semantic-v1", "semantic-compact-v1"],
|
|
460
|
-
richPlacements: true,
|
|
461
|
-
},
|
|
462
|
-
generation,
|
|
463
|
-
nonce,
|
|
464
|
-
),
|
|
465
|
-
).toMatchObject({
|
|
466
|
-
accepted: true,
|
|
467
|
-
negotiated: {
|
|
468
|
-
encoding: "semantic-compact-v1",
|
|
469
|
-
fallbackEncoding: "semantic-v1",
|
|
470
|
-
richPlacements: true,
|
|
471
|
-
},
|
|
472
|
-
});
|
|
473
|
-
expect(
|
|
474
|
-
negotiateTerminalDelivery(
|
|
475
|
-
{ protocolVersions: [2], encodings: ["semantic-v1"], richPlacements: false },
|
|
476
|
-
generation,
|
|
477
|
-
nonce,
|
|
478
|
-
),
|
|
479
|
-
).toEqual({ accepted: false, reason: "protocol-version-mismatch" });
|
|
480
|
-
expect(
|
|
481
|
-
negotiateTerminalDelivery(
|
|
482
|
-
{ protocolVersions: [1], encodings: ["ansi-diff-v1"], richPlacements: true },
|
|
483
|
-
generation,
|
|
484
|
-
nonce,
|
|
485
|
-
),
|
|
486
|
-
).toEqual({ accepted: false, reason: "unsupported-capability-combination" });
|
|
487
|
-
const compactOnly = negotiateTerminalDelivery(
|
|
488
|
-
{ protocolVersions: [1], encodings: ["semantic-compact-v1"], richPlacements: false },
|
|
489
|
-
generation,
|
|
490
|
-
nonce,
|
|
491
|
-
);
|
|
492
|
-
if (!compactOnly.accepted) throw new Error("compact-only negotiation failed");
|
|
493
|
-
expect(
|
|
494
|
-
admitTerminalDeliveryEnvelope(
|
|
495
|
-
createTerminalDeliveryClientState(compactOnly.negotiated, "workspace", "pane-a"),
|
|
496
|
-
seedEnvelope().envelope,
|
|
497
|
-
).failed,
|
|
498
|
-
).toBe(true);
|
|
499
|
-
});
|
|
500
|
-
|
|
501
|
-
it("roundtrips every canonical semantic field through compact v1", () => {
|
|
502
|
-
const blank = blankTerminalReplicaSnapshot(4, 2);
|
|
503
|
-
const styled = {
|
|
504
|
-
cells: [
|
|
505
|
-
{
|
|
506
|
-
grapheme: "界",
|
|
507
|
-
width: 2 as const,
|
|
508
|
-
foreground: { kind: "indexed" as const, index: 196 },
|
|
509
|
-
background: { kind: "rgb" as const, value: 0x010203 },
|
|
510
|
-
attributes: 0x7f,
|
|
511
|
-
},
|
|
512
|
-
{
|
|
513
|
-
grapheme: "",
|
|
514
|
-
width: 0 as const,
|
|
515
|
-
foreground: { kind: "indexed" as const, index: 196 },
|
|
516
|
-
background: { kind: "rgb" as const, value: 0x010203 },
|
|
517
|
-
attributes: 0x7f,
|
|
518
|
-
},
|
|
519
|
-
{
|
|
520
|
-
grapheme: "é",
|
|
521
|
-
width: 1 as const,
|
|
522
|
-
foreground: { kind: "default" as const },
|
|
523
|
-
background: { kind: "default" as const },
|
|
524
|
-
attributes: 3,
|
|
525
|
-
},
|
|
526
|
-
blank.grid[0]!.cells[3]!,
|
|
527
|
-
],
|
|
528
|
-
wrapped: true,
|
|
529
|
-
};
|
|
530
|
-
const snapshot = {
|
|
531
|
-
...blank,
|
|
532
|
-
grid: [styled, blank.grid[1]!],
|
|
533
|
-
history: [styled],
|
|
534
|
-
cursor: { x: 2, y: 1, hidden: true, style: "bar" as const, blink: true },
|
|
535
|
-
modes: {
|
|
536
|
-
...blank.modes,
|
|
537
|
-
alternateScreen: true,
|
|
538
|
-
applicationCursor: true,
|
|
539
|
-
mouseTracking: true,
|
|
540
|
-
mouseProtocol: "drag" as const,
|
|
541
|
-
mouseEncoding: "sgr" as const,
|
|
542
|
-
},
|
|
543
|
-
placements: [
|
|
544
|
-
{
|
|
545
|
-
id: "placement-a",
|
|
546
|
-
kind: "image",
|
|
547
|
-
row: 0,
|
|
548
|
-
column: 1,
|
|
549
|
-
columns: 2,
|
|
550
|
-
rows: 1,
|
|
551
|
-
contentDigest: "digest-a",
|
|
552
|
-
},
|
|
553
|
-
],
|
|
554
|
-
bootstrap: {
|
|
555
|
-
kind: "authoritative-stream" as const,
|
|
556
|
-
hiddenState: "observed-from-start" as const,
|
|
557
|
-
},
|
|
558
|
-
};
|
|
559
|
-
const seed = { frame: "seed" as const, revision: 7, snapshot };
|
|
560
|
-
const encoded = encodeCompactSemanticTerminalUpdate(seed);
|
|
561
|
-
expect(decodeCompactSemanticTerminalUpdate(encoded)).toEqual(seed);
|
|
562
|
-
const patch = {
|
|
563
|
-
frame: "patch" as const,
|
|
564
|
-
baseRevision: 7,
|
|
565
|
-
revision: 8,
|
|
566
|
-
patch: {
|
|
567
|
-
rows: [{ index: 0, row: styled }],
|
|
568
|
-
historyDelta: { trim: 1, append: [styled] },
|
|
569
|
-
cursor: snapshot.cursor,
|
|
570
|
-
modes: snapshot.modes,
|
|
571
|
-
placements: snapshot.placements,
|
|
572
|
-
bootstrap: snapshot.bootstrap,
|
|
573
|
-
},
|
|
574
|
-
};
|
|
575
|
-
expect(decodeCompactSemanticTerminalUpdate(encodeCompactSemanticTerminalUpdate(patch))).toEqual(
|
|
576
|
-
patch,
|
|
577
|
-
);
|
|
578
|
-
const canonicalJsonReference = (value: unknown): string => {
|
|
579
|
-
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
580
|
-
if (Array.isArray(value)) return `[${value.map(canonicalJsonReference).join(",")}]`;
|
|
581
|
-
const record = value as Record<string, unknown>;
|
|
582
|
-
return `{${Object.keys(record)
|
|
583
|
-
.sort()
|
|
584
|
-
.map((key) => `${JSON.stringify(key)}:${canonicalJsonReference(record[key])}`)
|
|
585
|
-
.join(",")}}`;
|
|
586
|
-
};
|
|
587
|
-
for (const payload of [
|
|
588
|
-
seed,
|
|
589
|
-
patch,
|
|
590
|
-
{
|
|
591
|
-
frame: "tombstone" as const,
|
|
592
|
-
baseRevision: 8,
|
|
593
|
-
revision: 9,
|
|
594
|
-
tombstone: { reason: "pane-closed" as const },
|
|
595
|
-
},
|
|
596
|
-
]) {
|
|
597
|
-
const text = new TextDecoder().decode(encodeCompactSemanticTerminalUpdate(payload));
|
|
598
|
-
expect(text).toBe(canonicalJsonReference(JSON.parse(text)));
|
|
599
|
-
expect(decodeCompactSemanticTerminalUpdate(new TextEncoder().encode(text))).toEqual(payload);
|
|
600
|
-
}
|
|
601
|
-
});
|
|
602
|
-
|
|
603
|
-
it("deep-freezes compact decodes and adopts only the decoder-owned verified snapshot", () => {
|
|
604
|
-
const blank = blankTerminalReplicaSnapshot(3, 2);
|
|
605
|
-
const historyRow = {
|
|
606
|
-
cells: blank.grid[0]!.cells.map((cell, index) => ({
|
|
607
|
-
...cell,
|
|
608
|
-
grapheme: index === 0 ? "界" : index === 1 ? "" : "é",
|
|
609
|
-
width: (index === 0 ? 2 : index === 1 ? 0 : 1) as 0 | 1 | 2,
|
|
610
|
-
foreground: { kind: "indexed" as const, index: 17 },
|
|
611
|
-
background: { kind: "rgb" as const, value: 0x010203 },
|
|
612
|
-
})),
|
|
613
|
-
wrapped: true,
|
|
614
|
-
};
|
|
615
|
-
const snapshot = { ...blank, history: [historyRow] };
|
|
616
|
-
const bytes = encodeCompactSemanticTerminalUpdate({
|
|
617
|
-
frame: "seed",
|
|
618
|
-
revision: 0,
|
|
619
|
-
snapshot,
|
|
620
|
-
});
|
|
621
|
-
const publicDecode = decodeCompactSemanticTerminalUpdate(bytes);
|
|
622
|
-
if (publicDecode.frame !== "seed") throw new Error("expected compact seed");
|
|
623
|
-
expect(Object.isFrozen(publicDecode)).toBe(true);
|
|
624
|
-
expect(Object.isFrozen(publicDecode.snapshot)).toBe(true);
|
|
625
|
-
expect(Object.isFrozen(publicDecode.snapshot.history)).toBe(true);
|
|
626
|
-
expect(Object.isFrozen(publicDecode.snapshot.history[0]!.cells)).toBe(true);
|
|
627
|
-
expect(Object.isFrozen(publicDecode.snapshot.history[0]!.cells[0]!.foreground)).toBe(true);
|
|
628
|
-
expect(() => {
|
|
629
|
-
publicDecode.snapshot.history[0]!.cells[0]!.grapheme = "forged";
|
|
630
|
-
}).toThrow();
|
|
631
|
-
let forgedProfile: unknown = null;
|
|
632
|
-
const ordinary = applyTerminalReplicaUpdate(
|
|
633
|
-
null,
|
|
634
|
-
{
|
|
635
|
-
type: "terminal.seed",
|
|
636
|
-
workspaceName: "workspace",
|
|
637
|
-
semanticPaneId: "pane-a",
|
|
638
|
-
generation,
|
|
639
|
-
incarnation: `${generation}:0`,
|
|
640
|
-
revision: 0,
|
|
641
|
-
cols: snapshot.cols,
|
|
642
|
-
rows: snapshot.rows,
|
|
643
|
-
stateHash: hashTerminalReplicaSnapshot(snapshot),
|
|
644
|
-
hashAlgorithm: "fnv1a64-v1",
|
|
645
|
-
snapshot: publicDecode.snapshot,
|
|
646
|
-
},
|
|
647
|
-
{
|
|
648
|
-
instrumentation: { nowMicros: () => 1, onComplete: (profile) => (forgedProfile = profile) },
|
|
649
|
-
},
|
|
650
|
-
);
|
|
651
|
-
if (ordinary.status !== "applied") throw new Error("ordinary seed rejected");
|
|
652
|
-
expect(ordinary.state.snapshot).not.toBe(publicDecode.snapshot);
|
|
653
|
-
expect(forgedProfile).toMatchObject({ trustedCompactAdoption: false });
|
|
654
|
-
let compactProfile: unknown = null;
|
|
655
|
-
const verified = decodeVerifiedCompactSemanticTerminalUpdate(
|
|
656
|
-
bytes,
|
|
657
|
-
null,
|
|
658
|
-
hashTerminalReplicaSnapshot(snapshot),
|
|
659
|
-
{ onComplete: (profile) => (compactProfile = profile) },
|
|
660
|
-
);
|
|
661
|
-
expect(verified.canonicalSnapshot).toBe(
|
|
662
|
-
verified.payload.frame === "seed" ? verified.payload.snapshot : null,
|
|
663
|
-
);
|
|
664
|
-
expect(compactProfile).toMatchObject({
|
|
665
|
-
expandedRows: 3,
|
|
666
|
-
expandedCells: 9,
|
|
667
|
-
schemaTraversals: 1,
|
|
668
|
-
hashTraversals: 1,
|
|
669
|
-
applyTraversals: 0,
|
|
670
|
-
trustedAdoption: true,
|
|
671
|
-
retainedSnapshots: 0,
|
|
672
|
-
});
|
|
673
|
-
|
|
674
|
-
const negotiated = negotiateTerminalDelivery(
|
|
675
|
-
{ protocolVersions: [1], encodings: ["semantic-compact-v1"], richPlacements: false },
|
|
676
|
-
generation,
|
|
677
|
-
nonce,
|
|
678
|
-
);
|
|
679
|
-
if (!negotiated.accepted) throw new Error("compact negotiation failed");
|
|
680
|
-
const envelope = TerminalDeliveryEnvelopeSchemaZ.parse({
|
|
681
|
-
...seedEnvelope().envelope,
|
|
682
|
-
encoding: "semantic-compact-v1",
|
|
683
|
-
canonicalStateHash: hashTerminalReplicaSnapshot(snapshot),
|
|
684
|
-
representationHash: hashTerminalDeliveryRepresentation(bytes),
|
|
685
|
-
representationBytes: bytes.byteLength,
|
|
686
|
-
chunkCount: Math.max(1, Math.ceil(bytes.byteLength / (256 * 1024))),
|
|
687
|
-
});
|
|
688
|
-
let state = admitTerminalDeliveryEnvelope(
|
|
689
|
-
createTerminalDeliveryClientState(negotiated.negotiated, "workspace", "pane-a"),
|
|
690
|
-
envelope,
|
|
691
|
-
);
|
|
692
|
-
if (!state.inFlight) throw new Error("compact seed was not admitted");
|
|
693
|
-
const assembler = new TerminalDeliveryAssembler(state.inFlight);
|
|
694
|
-
for (const chunk of splitTerminalDeliveryChunks(tx, bytes)) {
|
|
695
|
-
state = admitTerminalDeliveryChunk(state, chunk);
|
|
696
|
-
assembler.write(chunk);
|
|
697
|
-
}
|
|
698
|
-
const committed = commitTerminalDelivery(state, completeTerminalDelivery(state, assembler));
|
|
699
|
-
if (committed.semanticUpdate?.frame !== "seed") throw new Error("compact seed missing");
|
|
700
|
-
expect(committed.state.canonicalSnapshot).toBe(committed.semanticUpdate.snapshot);
|
|
701
|
-
expect(committed.state.canonicalSnapshot?.history[0]?.cells[0]?.foreground).toBe(
|
|
702
|
-
committed.semanticUpdate.snapshot.history[0]?.cells[0]?.foreground,
|
|
703
|
-
);
|
|
704
|
-
|
|
705
|
-
const appended = { ...historyRow, wrapped: false };
|
|
706
|
-
const patch = {
|
|
707
|
-
frame: "patch" as const,
|
|
708
|
-
baseRevision: 0,
|
|
709
|
-
revision: 1,
|
|
710
|
-
patch: { rows: [], historyDelta: { trim: 1, append: [appended] } },
|
|
711
|
-
};
|
|
712
|
-
const patchBytes = encodeCompactSemanticTerminalUpdate(patch);
|
|
713
|
-
const target = { ...snapshot, history: [appended] };
|
|
714
|
-
const patchEnvelope = TerminalDeliveryEnvelopeSchemaZ.parse({
|
|
715
|
-
...envelope,
|
|
716
|
-
transactionId: "00000000-0000-4000-8000-000000000004",
|
|
717
|
-
frame: "patch",
|
|
718
|
-
baseRevision: 0,
|
|
719
|
-
canonicalRevision: 1,
|
|
720
|
-
canonicalStateHash: hashTerminalReplicaSnapshot(target),
|
|
721
|
-
representationHash: hashTerminalDeliveryRepresentation(patchBytes),
|
|
722
|
-
representationBytes: patchBytes.byteLength,
|
|
723
|
-
chunkCount: Math.max(1, Math.ceil(patchBytes.byteLength / (256 * 1024))),
|
|
724
|
-
});
|
|
725
|
-
state = admitTerminalDeliveryEnvelope(committed.state, patchEnvelope);
|
|
726
|
-
if (!state.inFlight) throw new Error("compact patch was not admitted");
|
|
727
|
-
const patchAssembler = new TerminalDeliveryAssembler(state.inFlight);
|
|
728
|
-
for (const chunk of splitTerminalDeliveryChunks(patchEnvelope.transactionId, patchBytes)) {
|
|
729
|
-
state = admitTerminalDeliveryChunk(state, chunk);
|
|
730
|
-
patchAssembler.write(chunk);
|
|
731
|
-
}
|
|
732
|
-
const patched = commitTerminalDelivery(state, completeTerminalDelivery(state, patchAssembler));
|
|
733
|
-
if (patched.semanticUpdate?.frame !== "patch") throw new Error("compact patch missing");
|
|
734
|
-
expect(patched.state.canonicalSnapshot?.history[0]).toBe(
|
|
735
|
-
patched.semanticUpdate.patch.historyDelta?.append[0],
|
|
736
|
-
);
|
|
737
|
-
expect(patched.state.canonicalSnapshot?.history[0]?.cells[0]).toBe(
|
|
738
|
-
patched.semanticUpdate.patch.historyDelta?.append[0]?.cells[0],
|
|
739
|
-
);
|
|
740
|
-
});
|
|
741
|
-
|
|
742
|
-
it("keeps a real-width five-thousand-row semantic seed below the unchanged cap", () => {
|
|
743
|
-
const blank = blankTerminalReplicaSnapshot(132, 40);
|
|
744
|
-
const row = {
|
|
745
|
-
cells: blank.grid[0]!.cells.map((cell, index) =>
|
|
746
|
-
index < 28 ? { ...cell, grapheme: String.fromCharCode(65 + (index % 26)) } : cell,
|
|
747
|
-
),
|
|
748
|
-
wrapped: false,
|
|
749
|
-
};
|
|
750
|
-
const snapshot = { ...blank, history: Array.from({ length: 5_000 }, () => row) };
|
|
751
|
-
const bytes = encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 1, snapshot });
|
|
752
|
-
expect(bytes.byteLength).toBeLessThan(16 * 1024 * 1024);
|
|
753
|
-
expect(decodeCompactSemanticTerminalUpdate(bytes)).toEqual({
|
|
754
|
-
frame: "seed",
|
|
755
|
-
revision: 1,
|
|
756
|
-
snapshot,
|
|
757
|
-
});
|
|
758
|
-
}, 30_000);
|
|
759
|
-
|
|
760
|
-
it("skips impossible old-width history reuse while preserving seed validation and adoption", async () => {
|
|
761
|
-
const oldBlank = blankTerminalReplicaSnapshot(80, 2);
|
|
762
|
-
const oldSnapshot = {
|
|
763
|
-
...oldBlank,
|
|
764
|
-
history: Array.from({ length: 256 }, () => oldBlank.grid[0]!),
|
|
765
|
-
};
|
|
766
|
-
const oldBytes = encodeCompactSemanticTerminalUpdate({
|
|
767
|
-
frame: "seed",
|
|
768
|
-
revision: 0,
|
|
769
|
-
snapshot: oldSnapshot,
|
|
770
|
-
});
|
|
771
|
-
const baseline = decodeVerifiedCompactSemanticTerminalUpdate(
|
|
772
|
-
oldBytes,
|
|
773
|
-
null,
|
|
774
|
-
hashTerminalReplicaSnapshot(oldSnapshot),
|
|
775
|
-
).canonicalSnapshot!;
|
|
776
|
-
const resizedBlank = blankTerminalReplicaSnapshot(66, 2);
|
|
777
|
-
const resized = {
|
|
778
|
-
...resizedBlank,
|
|
779
|
-
history: Array.from({ length: 256 }, () => resizedBlank.grid[0]!),
|
|
780
|
-
};
|
|
781
|
-
const bytes = encodeCompactSemanticTerminalUpdate({
|
|
782
|
-
frame: "seed",
|
|
783
|
-
revision: 1,
|
|
784
|
-
snapshot: resized,
|
|
785
|
-
});
|
|
786
|
-
const hash = hashTerminalReplicaSnapshot(resized);
|
|
787
|
-
const counts: number[] = [];
|
|
788
|
-
for (const prior of [baseline, null]) {
|
|
789
|
-
let yields = 0;
|
|
790
|
-
const decoded = await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
791
|
-
bytes,
|
|
792
|
-
prior,
|
|
793
|
-
hash,
|
|
794
|
-
{
|
|
795
|
-
yieldControl: async () => {
|
|
796
|
-
yields++;
|
|
797
|
-
},
|
|
798
|
-
grantReducerAdoption: true,
|
|
799
|
-
},
|
|
800
|
-
);
|
|
801
|
-
expect(decoded.canonicalSnapshot).toEqual(resized);
|
|
802
|
-
counts.push(yields);
|
|
803
|
-
}
|
|
804
|
-
// The resized decode does no additional baseline indexing work. It still
|
|
805
|
-
// yields during parsing, expansion and verification just like a cold seed.
|
|
806
|
-
expect(counts[0]).toBeGreaterThan(0);
|
|
807
|
-
expect(counts[0]).toBe(counts[1]);
|
|
808
|
-
await expect(
|
|
809
|
-
decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
810
|
-
bytes,
|
|
811
|
-
baseline,
|
|
812
|
-
"0000000000000000",
|
|
813
|
-
{ yieldControl: async () => {} },
|
|
814
|
-
),
|
|
815
|
-
).rejects.toThrow(/hash mismatch/);
|
|
816
|
-
let sameWidthProfile: CompactSemanticCommitProfile | undefined;
|
|
817
|
-
await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
818
|
-
oldBytes,
|
|
819
|
-
baseline,
|
|
820
|
-
hashTerminalReplicaSnapshot(oldSnapshot),
|
|
821
|
-
{
|
|
822
|
-
yieldControl: async () => {},
|
|
823
|
-
onComplete: (profile) => {
|
|
824
|
-
sameWidthProfile = profile;
|
|
825
|
-
},
|
|
826
|
-
},
|
|
827
|
-
);
|
|
828
|
-
expect(sameWidthProfile!.reusedRows).toBeGreaterThan(0);
|
|
829
|
-
});
|
|
830
|
-
|
|
831
|
-
it("reuses authenticated baseline rows before allocating repeated compact history", async () => {
|
|
832
|
-
const blank = blankTerminalReplicaSnapshot(132, 41);
|
|
833
|
-
const defaultCell = blank.grid[0]!.cells[0]!;
|
|
834
|
-
const history = Object.freeze(
|
|
835
|
-
Array.from({ length: 4_096 }, (_, ordinal) => {
|
|
836
|
-
const prefix = `LOAD_${String(ordinal).padStart(4, "0")} 0123456789abcdef`;
|
|
837
|
-
return Object.freeze({
|
|
838
|
-
wrapped: false,
|
|
839
|
-
cells: Object.freeze([
|
|
840
|
-
...[...prefix].map((grapheme) => Object.freeze({ ...defaultCell, grapheme })),
|
|
841
|
-
...Array.from({ length: 132 - prefix.length }, () => defaultCell),
|
|
842
|
-
]),
|
|
843
|
-
});
|
|
844
|
-
}),
|
|
845
|
-
) as unknown as TerminalReplicaSnapshot["history"];
|
|
846
|
-
const seedSnapshot = Object.freeze({ ...blank, history }) as TerminalReplicaSnapshot;
|
|
847
|
-
const seedBytes = encodeCompactSemanticTerminalUpdate({
|
|
848
|
-
frame: "seed",
|
|
849
|
-
revision: 0,
|
|
850
|
-
snapshot: seedSnapshot,
|
|
851
|
-
});
|
|
852
|
-
const yieldControl = () => new Promise<void>((resolve) => setImmediate(resolve));
|
|
853
|
-
const seed = await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
854
|
-
seedBytes,
|
|
855
|
-
null,
|
|
856
|
-
hashTerminalReplicaSnapshot(seedSnapshot),
|
|
857
|
-
{ yieldControl },
|
|
858
|
-
);
|
|
859
|
-
if (!seed.canonicalSnapshot) throw new Error("compact reuse seed missing");
|
|
860
|
-
|
|
861
|
-
const patchPayload = {
|
|
862
|
-
frame: "patch" as const,
|
|
863
|
-
baseRevision: 0,
|
|
864
|
-
revision: 1,
|
|
865
|
-
patch: { rows: [], historyDelta: { trim: history.length, append: history } },
|
|
866
|
-
};
|
|
867
|
-
const patchBytes = encodeCompactSemanticTerminalUpdate(patchPayload);
|
|
868
|
-
let profile: CompactSemanticCommitProfile | null = null;
|
|
869
|
-
const repeated = await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
870
|
-
patchBytes,
|
|
871
|
-
seed.canonicalSnapshot,
|
|
872
|
-
hashTerminalReplicaSnapshot(seed.canonicalSnapshot),
|
|
873
|
-
{ yieldControl, onComplete: (value) => (profile = value) },
|
|
874
|
-
);
|
|
875
|
-
expect(profile).toMatchObject({
|
|
876
|
-
expandedRows: 4_096,
|
|
877
|
-
expandedCells: 540_672,
|
|
878
|
-
reusedRows: 4_096,
|
|
879
|
-
allocatedCells: 0,
|
|
880
|
-
});
|
|
881
|
-
expect(repeated.canonicalSnapshot?.history[2_048]).toBe(seed.canonicalSnapshot.history[2_048]);
|
|
882
|
-
|
|
883
|
-
const changedCells = [...history[2_048]!.cells];
|
|
884
|
-
changedCells[0] = Object.freeze({ ...changedCells[0]!, grapheme: "X" });
|
|
885
|
-
const changed = Object.freeze({
|
|
886
|
-
...history[2_048]!,
|
|
887
|
-
cells: Object.freeze(changedCells),
|
|
888
|
-
}) as TerminalReplicaSnapshot["history"][number];
|
|
889
|
-
const changedHistory = Object.freeze([
|
|
890
|
-
...history.slice(0, 2_048),
|
|
891
|
-
changed,
|
|
892
|
-
...history.slice(2_049),
|
|
893
|
-
]) as unknown as TerminalReplicaSnapshot["history"];
|
|
894
|
-
const changedPayload = {
|
|
895
|
-
frame: "patch" as const,
|
|
896
|
-
baseRevision: 1,
|
|
897
|
-
revision: 2,
|
|
898
|
-
patch: {
|
|
899
|
-
rows: [],
|
|
900
|
-
historyDelta: { trim: changedHistory.length, append: changedHistory },
|
|
901
|
-
},
|
|
902
|
-
};
|
|
903
|
-
const changedBytes = encodeCompactSemanticTerminalUpdate(changedPayload);
|
|
904
|
-
const changedTarget = Object.freeze({
|
|
905
|
-
...repeated.canonicalSnapshot!,
|
|
906
|
-
history: changedHistory,
|
|
907
|
-
}) as TerminalReplicaSnapshot;
|
|
908
|
-
profile = null;
|
|
909
|
-
const changedResult = await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
910
|
-
changedBytes,
|
|
911
|
-
repeated.canonicalSnapshot,
|
|
912
|
-
hashTerminalReplicaSnapshot(changedTarget),
|
|
913
|
-
{ yieldControl, onComplete: (value) => (profile = value) },
|
|
914
|
-
);
|
|
915
|
-
expect(profile).toMatchObject({ reusedRows: 4_095, allocatedCells: 132 });
|
|
916
|
-
expect(changedResult.canonicalSnapshot?.history[2_048]).not.toBe(
|
|
917
|
-
repeated.canonicalSnapshot?.history[2_048],
|
|
918
|
-
);
|
|
919
|
-
expect(changedResult.canonicalSnapshot?.history[2_048]).toEqual(changed);
|
|
920
|
-
}, 30_000);
|
|
921
|
-
|
|
922
|
-
it("keeps only row-level reuse across 100 distinct history sequences", async () => {
|
|
923
|
-
const blank = blankTerminalReplicaSnapshot(132, 41);
|
|
924
|
-
const baseRow = blank.grid[0]!;
|
|
925
|
-
let target = Object.freeze({
|
|
926
|
-
...blank,
|
|
927
|
-
history: Object.freeze(Array.from({ length: 8 }, () => baseRow)),
|
|
928
|
-
}) as TerminalReplicaSnapshot;
|
|
929
|
-
const yieldControl = () => new Promise<void>((resolve) => setImmediate(resolve));
|
|
930
|
-
const seed = await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
931
|
-
encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot: target }),
|
|
932
|
-
null,
|
|
933
|
-
hashTerminalReplicaSnapshot(target),
|
|
934
|
-
{ yieldControl },
|
|
935
|
-
);
|
|
936
|
-
let current = seed.canonicalSnapshot!;
|
|
937
|
-
for (let revision = 1; revision <= 100; revision += 1) {
|
|
938
|
-
const cells = [...baseRow.cells];
|
|
939
|
-
cells[0] = Object.freeze({ ...cells[0]!, grapheme: String.fromCodePoint(0x100 + revision) });
|
|
940
|
-
const changed = Object.freeze({ ...baseRow, cells: Object.freeze(cells) });
|
|
941
|
-
const history = Object.freeze([changed, ...Array.from({ length: 7 }, () => baseRow)]);
|
|
942
|
-
const patch = Object.freeze({
|
|
943
|
-
rows: [],
|
|
944
|
-
history: history as TerminalReplicaSnapshot["history"],
|
|
945
|
-
});
|
|
946
|
-
target = Object.freeze({ ...current, history }) as TerminalReplicaSnapshot;
|
|
947
|
-
let profile: CompactSemanticCommitProfile | null = null;
|
|
948
|
-
const decoded = await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
949
|
-
encodeCompactSemanticTerminalUpdate({
|
|
950
|
-
frame: "patch",
|
|
951
|
-
baseRevision: revision - 1,
|
|
952
|
-
revision,
|
|
953
|
-
patch,
|
|
954
|
-
}),
|
|
955
|
-
current,
|
|
956
|
-
hashTerminalReplicaSnapshot(target),
|
|
957
|
-
{ yieldControl, onComplete: (value) => (profile = value) },
|
|
958
|
-
);
|
|
959
|
-
expect(profile).toMatchObject({ reusedRows: 7, allocatedCells: 132 });
|
|
960
|
-
expect(decoded.canonicalSnapshot?.history).toEqual(history);
|
|
961
|
-
current = decoded.canonicalSnapshot!;
|
|
962
|
-
}
|
|
963
|
-
}, 30_000);
|
|
964
|
-
|
|
965
|
-
it("rejects unknown, malformed, over-wide and expansion-bomb compact payloads", () => {
|
|
966
|
-
const snapshot = blankTerminalReplicaSnapshot(2, 1);
|
|
967
|
-
const bytes = encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot });
|
|
968
|
-
const wire: unknown = JSON.parse(new TextDecoder().decode(bytes));
|
|
969
|
-
const root = (value: unknown) => value as Record<string, unknown>;
|
|
970
|
-
const at = (value: unknown, ...indices: number[]): unknown[] => {
|
|
971
|
-
let current = value;
|
|
972
|
-
for (const index of indices) current = (current as unknown[])[index];
|
|
973
|
-
return current as unknown[];
|
|
974
|
-
};
|
|
975
|
-
for (const mutate of [
|
|
976
|
-
(value: unknown) => (root(value).v = 2),
|
|
977
|
-
(value: unknown) => (root(value).extra = true),
|
|
978
|
-
(value: unknown) => (at(root(value).s)[0] = 4_097),
|
|
979
|
-
(value: unknown) => (at(root(value).s, 2, 0, 1, 0)[0] = 4_097),
|
|
980
|
-
(value: unknown) => at(root(value).s, 2, 0, 1).push([10_000_000, " ", 1, 0, 0, 0]),
|
|
981
|
-
]) {
|
|
982
|
-
const candidate = structuredClone(wire);
|
|
983
|
-
mutate(candidate);
|
|
984
|
-
expect(() =>
|
|
985
|
-
decodeCompactSemanticTerminalUpdate(new TextEncoder().encode(JSON.stringify(candidate))),
|
|
986
|
-
).toThrow();
|
|
987
|
-
}
|
|
988
|
-
const aggregateBomb = structuredClone(wire);
|
|
989
|
-
root(aggregateBomb).s = [
|
|
990
|
-
4_096,
|
|
991
|
-
1,
|
|
992
|
-
[[0, [[4_096, " ", 1, 0, 0, 0]]]],
|
|
993
|
-
Array.from({ length: 244 }, () => [0, [[4_096, " ", 1, 0, 0, 0]]]),
|
|
994
|
-
[0, 0, 0, "block", 0],
|
|
995
|
-
at(root(wire).s, 5),
|
|
996
|
-
[],
|
|
997
|
-
at(root(wire).s, 7),
|
|
998
|
-
];
|
|
999
|
-
const aggregateBytes = new TextEncoder().encode(JSON.stringify(aggregateBomb));
|
|
1000
|
-
expect(aggregateBytes.byteLength).toBeLessThan(64 * 1024);
|
|
1001
|
-
expect(() => decodeCompactSemanticTerminalUpdate(aggregateBytes)).toThrow(
|
|
1002
|
-
"Compact semantic expanded cell budget exceeded",
|
|
1003
|
-
);
|
|
1004
|
-
const oversizedString = {
|
|
1005
|
-
...snapshot,
|
|
1006
|
-
grid: [
|
|
1007
|
-
{
|
|
1008
|
-
...snapshot.grid[0]!,
|
|
1009
|
-
cells: [
|
|
1010
|
-
{ ...snapshot.grid[0]!.cells[0]!, grapheme: "x".repeat(4_097) },
|
|
1011
|
-
snapshot.grid[0]!.cells[1]!,
|
|
1012
|
-
],
|
|
1013
|
-
},
|
|
1014
|
-
],
|
|
1015
|
-
};
|
|
1016
|
-
expect(() =>
|
|
1017
|
-
encodeCompactSemanticTerminalUpdate({
|
|
1018
|
-
frame: "seed",
|
|
1019
|
-
revision: 0,
|
|
1020
|
-
snapshot: oversizedString,
|
|
1021
|
-
}),
|
|
1022
|
-
).toThrow(TerminalDeliveryStateTooLargeError);
|
|
1023
|
-
expect(
|
|
1024
|
-
preaccountSemanticTerminalUpdateBytes({
|
|
1025
|
-
frame: "seed",
|
|
1026
|
-
revision: 0,
|
|
1027
|
-
snapshot: oversizedString,
|
|
1028
|
-
}),
|
|
1029
|
-
).toMatchObject({ exact: true, bytes: expect.any(Number) });
|
|
1030
|
-
});
|
|
1031
|
-
|
|
1032
|
-
it("reuses identical current-message rows without skipping aggregate validation", async () => {
|
|
1033
|
-
const snapshot = blankTerminalReplicaSnapshot(66, 41);
|
|
1034
|
-
const bytes = encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot });
|
|
1035
|
-
let profile: unknown;
|
|
1036
|
-
const verified = await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
1037
|
-
bytes,
|
|
1038
|
-
null,
|
|
1039
|
-
hashTerminalReplicaSnapshot(snapshot),
|
|
1040
|
-
{
|
|
1041
|
-
yieldControl: async () => {},
|
|
1042
|
-
onComplete: (value) => {
|
|
1043
|
-
profile = value;
|
|
1044
|
-
},
|
|
1045
|
-
},
|
|
1046
|
-
);
|
|
1047
|
-
expect(verified.canonicalSnapshot).toEqual(snapshot);
|
|
1048
|
-
expect(profile).toMatchObject({
|
|
1049
|
-
expandedRows: 41,
|
|
1050
|
-
reusedRows: 40,
|
|
1051
|
-
allocatedCells: 66,
|
|
1052
|
-
expandedCells: 2706,
|
|
1053
|
-
});
|
|
1054
|
-
expect(verified.canonicalSnapshot!.grid[0]).toBe(verified.canonicalSnapshot!.grid[40]);
|
|
1055
|
-
const wire = JSON.parse(new TextDecoder().decode(bytes));
|
|
1056
|
-
wire.s[0] = 4096;
|
|
1057
|
-
wire.s[1] = 245;
|
|
1058
|
-
wire.s[2] = Array.from({ length: 245 }, () => [0, [[4096, " ", 1, 0, 0, 0]]]);
|
|
1059
|
-
await expect(
|
|
1060
|
-
decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
1061
|
-
new TextEncoder().encode(JSON.stringify(wire)),
|
|
1062
|
-
null,
|
|
1063
|
-
"unused",
|
|
1064
|
-
{ yieldControl: async () => {} },
|
|
1065
|
-
),
|
|
1066
|
-
).rejects.toThrow("expanded cell budget exceeded");
|
|
1067
|
-
wire.s[0] = 66;
|
|
1068
|
-
wire.s[1] = 41;
|
|
1069
|
-
wire.s[2] = Array.from({ length: 41 }, (_, index) => [
|
|
1070
|
-
0,
|
|
1071
|
-
[[index === 40 ? 65 : 66, " ", 1, 0, 0, 0]],
|
|
1072
|
-
]);
|
|
1073
|
-
await expect(
|
|
1074
|
-
decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
1075
|
-
new TextEncoder().encode(JSON.stringify(wire)),
|
|
1076
|
-
null,
|
|
1077
|
-
"unused",
|
|
1078
|
-
{ yieldControl: async () => {} },
|
|
1079
|
-
),
|
|
1080
|
-
).rejects.toThrow("width mismatch");
|
|
1081
|
-
});
|
|
1082
|
-
|
|
1083
|
-
it("checks exact bytes within raw-row hash collisions and stops caching at its entry limit", async () => {
|
|
1084
|
-
for (const labels of [
|
|
1085
|
-
["dkzbb9", "1mu5745", "dkzbb9", "1mu5745"],
|
|
1086
|
-
[...Array.from({ length: 1025 }, (_, index) => String(index)), "0", "1024"],
|
|
1087
|
-
]) {
|
|
1088
|
-
const blank = blankTerminalReplicaSnapshot(1, labels.length);
|
|
1089
|
-
const snapshot = {
|
|
1090
|
-
...blank,
|
|
1091
|
-
grid: labels.map((grapheme) => ({
|
|
1092
|
-
wrapped: false,
|
|
1093
|
-
cells: [{ ...blank.grid[0]!.cells[0]!, grapheme }],
|
|
1094
|
-
})),
|
|
1095
|
-
};
|
|
1096
|
-
const bytes = encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot });
|
|
1097
|
-
let profile: unknown;
|
|
1098
|
-
const verified = await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
1099
|
-
bytes,
|
|
1100
|
-
null,
|
|
1101
|
-
hashTerminalReplicaSnapshot(snapshot),
|
|
1102
|
-
{
|
|
1103
|
-
yieldControl: async () => {},
|
|
1104
|
-
onComplete: (value) => {
|
|
1105
|
-
profile = value;
|
|
1106
|
-
},
|
|
1107
|
-
},
|
|
1108
|
-
);
|
|
1109
|
-
expect(verified.canonicalSnapshot).toEqual(snapshot);
|
|
1110
|
-
expect(profile).toMatchObject({ reusedRows: labels.length === 4 ? 2 : 1 });
|
|
1111
|
-
}
|
|
1112
|
-
});
|
|
1113
|
-
|
|
1114
|
-
it("keeps cancellation checkpoints while expanding cached rows", async () => {
|
|
1115
|
-
const snapshot = blankTerminalReplicaSnapshot(1, 128);
|
|
1116
|
-
const bytes = encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot });
|
|
1117
|
-
await expect(
|
|
1118
|
-
decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
1119
|
-
bytes,
|
|
1120
|
-
null,
|
|
1121
|
-
hashTerminalReplicaSnapshot(snapshot),
|
|
1122
|
-
{
|
|
1123
|
-
yieldControl: async () => {
|
|
1124
|
-
throw new Error("retired");
|
|
1125
|
-
},
|
|
1126
|
-
},
|
|
1127
|
-
),
|
|
1128
|
-
).rejects.toThrow("retired");
|
|
1129
|
-
});
|
|
1130
|
-
|
|
1131
|
-
it("preserves verified rows below and above the bounded native JSON row size", async () => {
|
|
1132
|
-
for (const length of [1, 3_000]) {
|
|
1133
|
-
const blank = blankTerminalReplicaSnapshot(4, 1);
|
|
1134
|
-
const snapshot = {
|
|
1135
|
-
...blank,
|
|
1136
|
-
grid: [
|
|
1137
|
-
{
|
|
1138
|
-
wrapped: false,
|
|
1139
|
-
cells: blank.grid[0]!.cells.map((cell, index) => ({
|
|
1140
|
-
...cell,
|
|
1141
|
-
grapheme: "x".repeat(length) + index,
|
|
1142
|
-
})),
|
|
1143
|
-
},
|
|
1144
|
-
],
|
|
1145
|
-
};
|
|
1146
|
-
const bytes = encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot });
|
|
1147
|
-
const wire = JSON.parse(new TextDecoder().decode(bytes)) as { s: unknown[] };
|
|
1148
|
-
const rowBytes = new TextEncoder().encode(JSON.stringify((wire.s[2] as unknown[])[0])).length;
|
|
1149
|
-
expect(rowBytes > 8 * 1_024).toBe(length === 3_000);
|
|
1150
|
-
const verified = await decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
1151
|
-
bytes,
|
|
1152
|
-
null,
|
|
1153
|
-
hashTerminalReplicaSnapshot(snapshot),
|
|
1154
|
-
{ yieldControl: async () => {} },
|
|
1155
|
-
);
|
|
1156
|
-
expect(verified.payload).toEqual(decodeCompactSemanticTerminalUpdate(bytes));
|
|
1157
|
-
expect(verified.canonicalSnapshot).toEqual(snapshot);
|
|
1158
|
-
}
|
|
1159
|
-
});
|
|
1160
|
-
|
|
1161
|
-
it("rejects invalid bounded JSON rows before verified adoption", async () => {
|
|
1162
|
-
const snapshot = blankTerminalReplicaSnapshot(1, 1);
|
|
1163
|
-
const seed = encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot });
|
|
1164
|
-
let deep: unknown = "x";
|
|
1165
|
-
for (let index = 0; index < 70; index++) deep = [deep];
|
|
1166
|
-
const invalidRows = [
|
|
1167
|
-
[0, [[1, deep, 1, 0, 0, 0]]],
|
|
1168
|
-
[0, [[1, "x".repeat(4_097), 1, 0, 0, 0]]],
|
|
1169
|
-
[0, [[1, "x", 2, 0, 0, 0]]],
|
|
1170
|
-
[0, [[1, "x", 1, 0, 0, 256]]],
|
|
1171
|
-
[0, [[2, "x", 1, 0, 0, 0]]],
|
|
1172
|
-
[0, [[0, "x", 1, 0, 0, 0]]],
|
|
1173
|
-
];
|
|
1174
|
-
for (const row of invalidRows) {
|
|
1175
|
-
const wire = JSON.parse(new TextDecoder().decode(seed)) as { s: unknown[] };
|
|
1176
|
-
(wire.s[2] as unknown[])[0] = row;
|
|
1177
|
-
expect(new TextEncoder().encode(JSON.stringify(row)).length).toBeLessThan(8 * 1_024);
|
|
1178
|
-
const bytes = new TextEncoder().encode(JSON.stringify(wire));
|
|
1179
|
-
expect(() => decodeCompactSemanticTerminalUpdate(bytes)).toThrow();
|
|
1180
|
-
await expect(
|
|
1181
|
-
decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
1182
|
-
bytes,
|
|
1183
|
-
null,
|
|
1184
|
-
hashTerminalReplicaSnapshot(snapshot),
|
|
1185
|
-
{ yieldControl: async () => {} },
|
|
1186
|
-
),
|
|
1187
|
-
).rejects.toThrow();
|
|
1188
|
-
}
|
|
1189
|
-
});
|
|
1190
|
-
|
|
1191
|
-
it("keeps cooperative compact JSON syntax exact with the synchronous decoder", async () => {
|
|
1192
|
-
const snapshot = blankTerminalReplicaSnapshot(2, 1);
|
|
1193
|
-
const bytes = encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot });
|
|
1194
|
-
const text = new TextDecoder().decode(bytes);
|
|
1195
|
-
const expectedHash = hashTerminalReplicaSnapshot(snapshot);
|
|
1196
|
-
const candidates = [
|
|
1197
|
-
new TextEncoder().encode(`\u00a0${text}`),
|
|
1198
|
-
new TextEncoder().encode(text.replace("{", '{"__proto__":{"polluted":true},')),
|
|
1199
|
-
];
|
|
1200
|
-
for (const candidate of candidates) {
|
|
1201
|
-
expect(() => decodeCompactSemanticTerminalUpdate(candidate)).toThrow();
|
|
1202
|
-
await expect(
|
|
1203
|
-
decodeVerifiedCompactSemanticTerminalUpdateCooperatively(candidate, null, expectedHash, {
|
|
1204
|
-
yieldControl: async () => Promise.resolve(),
|
|
1205
|
-
}),
|
|
1206
|
-
).rejects.toThrow();
|
|
1207
|
-
}
|
|
1208
|
-
expect(({} as { polluted?: boolean }).polluted).toBeUndefined();
|
|
1209
|
-
});
|
|
1210
|
-
|
|
1211
|
-
it("rejects malformed UTF-8 identically below and above the cooperative threshold", async () => {
|
|
1212
|
-
const marker = "INVALID-UTF8-MARKER";
|
|
1213
|
-
const blank = blankTerminalReplicaSnapshot(2, 1);
|
|
1214
|
-
const markedRow = {
|
|
1215
|
-
...blank.grid[0]!,
|
|
1216
|
-
cells: [{ ...blank.grid[0]!.cells[0]!, grapheme: marker }, blank.grid[0]!.cells[1]!],
|
|
1217
|
-
};
|
|
1218
|
-
const marked = { ...blank, grid: [markedRow] };
|
|
1219
|
-
const large = { ...marked, history: Array.from({ length: 4_000 }, () => markedRow) };
|
|
1220
|
-
const corruptMarker = (bytes: Uint8Array): Uint8Array => {
|
|
1221
|
-
const markerBytes = new TextEncoder().encode(marker);
|
|
1222
|
-
const offset = bytes.findIndex((byte, index) =>
|
|
1223
|
-
markerBytes.every((expected, markerIndex) => bytes[index + markerIndex] === expected),
|
|
1224
|
-
);
|
|
1225
|
-
if (offset < 0) throw new Error("UTF-8 marker missing from representation");
|
|
1226
|
-
const corrupted = bytes.slice();
|
|
1227
|
-
corrupted[offset] = 0xff;
|
|
1228
|
-
return corrupted;
|
|
1229
|
-
};
|
|
1230
|
-
const smallBytes = corruptMarker(
|
|
1231
|
-
encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot: marked }),
|
|
1232
|
-
);
|
|
1233
|
-
const largeBytes = corruptMarker(
|
|
1234
|
-
encodeCompactSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot: large }),
|
|
1235
|
-
);
|
|
1236
|
-
expect(smallBytes.byteLength).toBeLessThan(64 * 1_024);
|
|
1237
|
-
expect(largeBytes.byteLength).toBeGreaterThan(64 * 1_024);
|
|
1238
|
-
for (const [bytes, snapshot] of [
|
|
1239
|
-
[smallBytes, marked],
|
|
1240
|
-
[largeBytes, large],
|
|
1241
|
-
] as const) {
|
|
1242
|
-
expect(() => decodeCompactSemanticTerminalUpdate(bytes)).toThrow();
|
|
1243
|
-
await expect(
|
|
1244
|
-
decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
1245
|
-
bytes,
|
|
1246
|
-
null,
|
|
1247
|
-
hashTerminalReplicaSnapshot(snapshot),
|
|
1248
|
-
{ yieldControl: async () => Promise.resolve() },
|
|
1249
|
-
),
|
|
1250
|
-
).rejects.toThrow();
|
|
1251
|
-
}
|
|
1252
|
-
const legacyBytes = corruptMarker(
|
|
1253
|
-
encodeSemanticTerminalUpdate({ frame: "seed", revision: 0, snapshot: marked }),
|
|
1254
|
-
);
|
|
1255
|
-
expect(() => decodeSemanticTerminalUpdate(legacyBytes)).toThrow();
|
|
1256
|
-
});
|
|
1257
|
-
|
|
1258
|
-
it("preaccounts canonical legacy JSON bytes exactly and saturates without visiting the tail", () => {
|
|
1259
|
-
const blank = blankTerminalReplicaSnapshot(2, 1);
|
|
1260
|
-
for (const grapheme of [
|
|
1261
|
-
'quote"slash\\',
|
|
1262
|
-
"control\b\f\n\r\t\u0000",
|
|
1263
|
-
"\ud800",
|
|
1264
|
-
"\udc00",
|
|
1265
|
-
"😀",
|
|
1266
|
-
"界e\u0301",
|
|
1267
|
-
]) {
|
|
1268
|
-
const snapshot = {
|
|
1269
|
-
...blank,
|
|
1270
|
-
grid: [
|
|
1271
|
-
{
|
|
1272
|
-
...blank.grid[0]!,
|
|
1273
|
-
cells: [{ ...blank.grid[0]!.cells[0]!, grapheme }, blank.grid[0]!.cells[1]!],
|
|
1274
|
-
},
|
|
1275
|
-
],
|
|
1276
|
-
};
|
|
1277
|
-
const payload = { frame: "seed" as const, revision: 0, snapshot };
|
|
1278
|
-
const encoded = encodeSemanticTerminalUpdate(payload);
|
|
1279
|
-
expect(preaccountSemanticTerminalUpdateBytes(payload)).toEqual({
|
|
1280
|
-
exact: true,
|
|
1281
|
-
bytes: encoded.byteLength,
|
|
1282
|
-
});
|
|
1283
|
-
expect(preaccountSemanticTerminalUpdateBytes(payload, encoded.byteLength)).toEqual({
|
|
1284
|
-
exact: true,
|
|
1285
|
-
bytes: encoded.byteLength,
|
|
1286
|
-
});
|
|
1287
|
-
expect(preaccountSemanticTerminalUpdateBytes(payload, encoded.byteLength - 1)).toEqual({
|
|
1288
|
-
exact: false,
|
|
1289
|
-
atLeastBytes: encoded.byteLength,
|
|
1290
|
-
});
|
|
1291
|
-
}
|
|
1292
|
-
|
|
1293
|
-
let tailVisited = false;
|
|
1294
|
-
const input = Object.defineProperty({ frame: "seed", revision: 0 }, "zzTail", {
|
|
1295
|
-
enumerable: true,
|
|
1296
|
-
get() {
|
|
1297
|
-
tailVisited = true;
|
|
1298
|
-
throw new Error("saturated traversal reached tail");
|
|
1299
|
-
},
|
|
1300
|
-
});
|
|
1301
|
-
expect(preaccountSemanticTerminalUpdateBytes(input as never, 1)).toEqual({
|
|
1302
|
-
exact: false,
|
|
1303
|
-
atLeastBytes: 2,
|
|
1304
|
-
});
|
|
1305
|
-
expect(tailVisited).toBe(false);
|
|
1306
|
-
});
|
|
1307
|
-
|
|
1308
|
-
it("counts a compact-ineligible 170k-placement legacy seed exactly below the wire cap", () => {
|
|
1309
|
-
const blank = blankTerminalReplicaSnapshot(1, 1);
|
|
1310
|
-
const placement = {
|
|
1311
|
-
id: "i",
|
|
1312
|
-
kind: "k",
|
|
1313
|
-
row: 0,
|
|
1314
|
-
column: 0,
|
|
1315
|
-
columns: 1,
|
|
1316
|
-
rows: 1,
|
|
1317
|
-
contentDigest: "d",
|
|
1318
|
-
};
|
|
1319
|
-
const payload = {
|
|
1320
|
-
frame: "seed" as const,
|
|
1321
|
-
revision: 0,
|
|
1322
|
-
snapshot: { ...blank, placements: Array.from({ length: 170_000 }, () => placement) },
|
|
1323
|
-
};
|
|
1324
|
-
const counted = preaccountSemanticTerminalUpdateBytes(payload);
|
|
1325
|
-
expect(counted).toMatchObject({ exact: true });
|
|
1326
|
-
const encoded = encodeSemanticTerminalUpdate(payload);
|
|
1327
|
-
expect(counted).toEqual({ exact: true, bytes: encoded.byteLength });
|
|
1328
|
-
expect(encoded.byteLength).toBeLessThan(16 * 1024 * 1024);
|
|
1329
|
-
expect(() => encodeCompactSemanticTerminalUpdate(payload)).toThrow(
|
|
1330
|
-
TerminalDeliveryStateTooLargeError,
|
|
1331
|
-
);
|
|
1332
|
-
}, 30_000);
|
|
1333
|
-
|
|
1334
|
-
it("ACKs only after exact chunks decode, apply and canonical hash verification", () => {
|
|
1335
|
-
const negotiated = negotiateTerminalDelivery(
|
|
1336
|
-
{ protocolVersions: [1], encodings: ["semantic-v1"], richPlacements: false },
|
|
1337
|
-
generation,
|
|
1338
|
-
nonce,
|
|
1339
|
-
);
|
|
1340
|
-
if (!negotiated.accepted) throw new Error("negotiation failed");
|
|
1341
|
-
const { envelope, bytes } = seedEnvelope();
|
|
1342
|
-
let state = admitTerminalDeliveryEnvelope(
|
|
1343
|
-
createTerminalDeliveryClientState(negotiated.negotiated, "workspace", "pane-a"),
|
|
1344
|
-
envelope,
|
|
1345
|
-
);
|
|
1346
|
-
if (!state.inFlight) throw new Error("not admitted");
|
|
1347
|
-
const assembler = new TerminalDeliveryAssembler(state.inFlight);
|
|
1348
|
-
for (const chunk of splitTerminalDeliveryChunks(tx, bytes)) {
|
|
1349
|
-
state = admitTerminalDeliveryChunk(state, chunk);
|
|
1350
|
-
assembler.write(chunk);
|
|
1351
|
-
}
|
|
1352
|
-
const committed = commitTerminalDelivery(state, completeTerminalDelivery(state, assembler));
|
|
1353
|
-
expect(committed.ack.canonicalRevision).toBe(0);
|
|
1354
|
-
expect(committed.state.canonicalSnapshot).not.toBeNull();
|
|
1355
|
-
expect(committed.semanticUpdate).toMatchObject({ frame: "seed", revision: 0 });
|
|
1356
|
-
});
|
|
1357
|
-
|
|
1358
|
-
it("rejects representation corruption and semantic frame confusion", () => {
|
|
1359
|
-
const { envelope, bytes } = seedEnvelope();
|
|
1360
|
-
const corrupt = new TerminalDeliveryAssembler(envelope);
|
|
1361
|
-
const chunks = splitTerminalDeliveryChunks(tx, bytes);
|
|
1362
|
-
chunks[0]!.bytes[0] = (chunks[0]!.bytes[0] ?? 0) ^ 1;
|
|
1363
|
-
for (const chunk of chunks) corrupt.write(chunk);
|
|
1364
|
-
expect(() => corrupt.complete()).toThrow(/hash mismatch/u);
|
|
1365
|
-
const negotiated = negotiateTerminalDelivery(
|
|
1366
|
-
{ protocolVersions: [1], encodings: ["semantic-v1"], richPlacements: false },
|
|
1367
|
-
generation,
|
|
1368
|
-
nonce,
|
|
1369
|
-
);
|
|
1370
|
-
if (!negotiated.accepted) throw new Error("negotiation failed");
|
|
1371
|
-
let state = admitTerminalDeliveryEnvelope(
|
|
1372
|
-
createTerminalDeliveryClientState(negotiated.negotiated, "workspace", "pane-a"),
|
|
1373
|
-
envelope,
|
|
1374
|
-
);
|
|
1375
|
-
if (!state.inFlight) throw new Error("not admitted");
|
|
1376
|
-
const seedAssembler = new TerminalDeliveryAssembler(state.inFlight);
|
|
1377
|
-
for (const chunk of splitTerminalDeliveryChunks(tx, bytes)) {
|
|
1378
|
-
state = admitTerminalDeliveryChunk(state, chunk);
|
|
1379
|
-
seedAssembler.write(chunk);
|
|
1380
|
-
}
|
|
1381
|
-
state = commitTerminalDelivery(state, completeTerminalDelivery(state, seedAssembler)).state;
|
|
1382
|
-
const snapshot = blankTerminalReplicaSnapshot(2, 1);
|
|
1383
|
-
const patchBytes = encodeSemanticTerminalUpdate({
|
|
1384
|
-
frame: "seed",
|
|
1385
|
-
revision: 1,
|
|
1386
|
-
snapshot,
|
|
1387
|
-
});
|
|
1388
|
-
const confused = TerminalDeliveryEnvelopeSchemaZ.parse({
|
|
1389
|
-
...envelope,
|
|
1390
|
-
transactionId: "00000000-0000-4000-8000-000000000004",
|
|
1391
|
-
frame: "patch",
|
|
1392
|
-
baseRevision: 0,
|
|
1393
|
-
canonicalRevision: 1,
|
|
1394
|
-
representationHash: hashTerminalDeliveryRepresentation(patchBytes),
|
|
1395
|
-
representationBytes: patchBytes.byteLength,
|
|
1396
|
-
});
|
|
1397
|
-
state = admitTerminalDeliveryEnvelope(state, confused);
|
|
1398
|
-
if (!state.inFlight) throw new Error("confused delivery not admitted");
|
|
1399
|
-
const assembler = new TerminalDeliveryAssembler(state.inFlight);
|
|
1400
|
-
for (const chunk of splitTerminalDeliveryChunks(confused.transactionId, patchBytes)) {
|
|
1401
|
-
state = admitTerminalDeliveryChunk(state, chunk);
|
|
1402
|
-
assembler.write(chunk);
|
|
1403
|
-
}
|
|
1404
|
-
const staged = completeTerminalDelivery(state, assembler);
|
|
1405
|
-
expect(() => commitTerminalDelivery(state, staged)).toThrow(/frame or revision/u);
|
|
1406
|
-
});
|
|
1407
|
-
|
|
1408
|
-
it("bounds allocation at the schema boundary and requires ANSI presentation before ACK", () => {
|
|
1409
|
-
const { envelope } = seedEnvelope();
|
|
1410
|
-
expect(
|
|
1411
|
-
() =>
|
|
1412
|
-
new TerminalDeliveryAssembler({
|
|
1413
|
-
...envelope,
|
|
1414
|
-
representationBytes: Number.NaN,
|
|
1415
|
-
} as TerminalDeliveryEnvelope),
|
|
1416
|
-
).toThrow();
|
|
1417
|
-
const bytes = new TextEncoder().encode("\u001b[2J");
|
|
1418
|
-
const ansiEnvelope = TerminalDeliveryEnvelopeSchemaZ.parse({
|
|
1419
|
-
...envelope,
|
|
1420
|
-
encoding: "ansi-diff-v1",
|
|
1421
|
-
canonicalEquivalent: false,
|
|
1422
|
-
history: "complete",
|
|
1423
|
-
representationHash: hashTerminalDeliveryRepresentation(bytes),
|
|
1424
|
-
representationBytes: bytes.byteLength,
|
|
1425
|
-
});
|
|
1426
|
-
const negotiated = negotiateTerminalDelivery(
|
|
1427
|
-
{ protocolVersions: [1], encodings: ["ansi-diff-v1"], richPlacements: false },
|
|
1428
|
-
generation,
|
|
1429
|
-
nonce,
|
|
1430
|
-
);
|
|
1431
|
-
if (!negotiated.accepted) throw new Error("negotiation failed");
|
|
1432
|
-
let state = admitTerminalDeliveryEnvelope(
|
|
1433
|
-
createTerminalDeliveryClientState(negotiated.negotiated, "workspace", "pane-a"),
|
|
1434
|
-
ansiEnvelope,
|
|
1435
|
-
);
|
|
1436
|
-
if (!state.inFlight) throw new Error("not admitted");
|
|
1437
|
-
const assembler = new TerminalDeliveryAssembler(state.inFlight);
|
|
1438
|
-
for (const chunk of splitTerminalDeliveryChunks(tx, bytes)) {
|
|
1439
|
-
state = admitTerminalDeliveryChunk(state, chunk);
|
|
1440
|
-
assembler.write(chunk);
|
|
1441
|
-
}
|
|
1442
|
-
const staged = completeTerminalDelivery(state, assembler);
|
|
1443
|
-
expect(() => commitTerminalDelivery(state, staged)).toThrow(/presentation/u);
|
|
1444
|
-
expect(
|
|
1445
|
-
commitTerminalDelivery(state, staged, { presentationApplied: true }).ack.canonicalRevision,
|
|
1446
|
-
).toBe(0);
|
|
1447
|
-
});
|
|
1448
|
-
|
|
1449
|
-
it("never advances a baseline on NACK", () => {
|
|
1450
|
-
const negotiated = negotiateTerminalDelivery(
|
|
1451
|
-
{ protocolVersions: [1], encodings: ["semantic-v1"], richPlacements: false },
|
|
1452
|
-
generation,
|
|
1453
|
-
nonce,
|
|
1454
|
-
);
|
|
1455
|
-
if (!negotiated.accepted) throw new Error("negotiation failed");
|
|
1456
|
-
const initial = createTerminalDeliveryClientState(negotiated.negotiated, "workspace", "pane-a");
|
|
1457
|
-
const rejected = nackTerminalDelivery(initial, {
|
|
1458
|
-
type: "terminal.delivery.nack",
|
|
1459
|
-
workspaceName: "workspace",
|
|
1460
|
-
semanticPaneId: "pane-a",
|
|
1461
|
-
generation,
|
|
1462
|
-
incarnation: `${generation}:0`,
|
|
1463
|
-
deliveryNonce: nonce,
|
|
1464
|
-
transactionId: null,
|
|
1465
|
-
reason: "gap",
|
|
1466
|
-
appliedRevision: -1,
|
|
1467
|
-
});
|
|
1468
|
-
expect(rejected.appliedRevision).toBe(-1);
|
|
1469
|
-
expect(rejected.reseedRequired).toBe(true);
|
|
1470
|
-
});
|
|
1471
|
-
});
|