tmux-ide 2.9.3 → 2.9.5
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/LICENSE +1 -1
- package/README.md +120 -139
- package/bin/cli.js +4847 -3585
- package/package.json +16 -9
- package/packages/contracts/src/session-runtime.ts +2 -0
- package/packages/contracts/src/terminal-delivery.ts +4 -0
- package/packages/core/src/terminal-compact-capability.ts +31 -1
- package/packages/core/src/terminal-delivery.ts +234 -32
- package/packages/core/src/terminal-replica-hash-cache.ts +162 -21
- package/packages/core/src/terminal-replica-owned-row.ts +165 -10
- package/packages/core/src/terminal-replica.ts +43 -21
- package/packages/daemon/dist/lib/daemon-embed.js +19 -34
- package/packages/daemon/dist/lib/runtime-trace-writer.js +133 -0
- package/packages/daemon/dist/lib/tmux-client-execution.js +37 -2
- package/packages/daemon/dist/lib/tmux-server-owner.js +20 -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/native/tmux/darwin-arm64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/darwin-arm64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/darwin-x64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/darwin-x64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/linux-arm64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/linux-arm64/tmux +0 -0
- package/packages/daemon/dist/native/tmux/linux-x64/licenses/libevent_core-2.1.so.7.txt +0 -0
- package/packages/daemon/dist/native/tmux/linux-x64/manifest.json +5 -1
- package/packages/daemon/dist/native/tmux/linux-x64/tmux +0 -0
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +6 -6
- package/packages/daemon/dist/terminal/mirror/control-channel.js +379 -75
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +43 -3
- package/packages/daemon/dist/terminal/mirror/native-grid-projection.js +20 -11
- package/packages/daemon/dist/terminal/mirror/session-channel.js +639 -811
- package/packages/daemon/dist/terminal/mirror/stock-pane-snapshot.js +177 -0
- package/packages/daemon/dist/terminal/session-runtime/causal-cell-ledger.js +1 -1
- package/packages/daemon/dist/terminal/session-runtime/registry.js +145 -15
- package/packages/daemon/dist/terminal/session-runtime/stock-capture-fidelity.js +12 -0
- package/packages/daemon/dist/terminal/session-runtime/terminal-delivery-hub.js +128 -10
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-interpreter.js +21 -4
- package/packages/daemon/dist/terminal/session-runtime/terminal-replica-owner.js +19 -2
- package/packages/daemon/dist/terminal/session-runtime/xterm-terminal-interpreter-backend.js +17 -19
- package/packages/daemon/dist/tui/integrations/claude-agent-teams.js +264 -0
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +64 -4
- package/packages/daemon/dist/tui/mirror/pane-surface.jsx +10 -1
- package/packages/daemon/dist/tui/mirror/reference-performance-trace.js +117 -71
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +27 -3
- package/packages/daemon/dist/tui/mirror/runtime/pane-scoped-terminal-surface.jsx +17 -4
- package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +19 -0
- package/packages/daemon/dist/tui/mirror/runtime/terminal-input-adapter.js +10 -2
- package/packages/daemon/dist/tui/mirror/testing/styled-frame-oracle.js +156 -0
- package/packages/daemon/src/lib/daemon-embed.ts +22 -34
- package/packages/daemon/src/lib/runtime-trace-writer.ts +132 -0
- package/packages/daemon/src/lib/tmux-client-execution.ts +47 -2
- package/packages/daemon/src/lib/tmux-server-owner.ts +19 -3
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +6 -0
- package/packages/daemon/src/terminal/mirror/control-channel.ts +428 -84
- package/packages/daemon/src/terminal/mirror/events.ts +7 -7
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +49 -3
- package/packages/daemon/src/terminal/mirror/native-grid-projection.ts +21 -11
- package/packages/daemon/src/terminal/mirror/pane-feed.ts +9 -4
- package/packages/daemon/src/terminal/mirror/session-channel.ts +705 -859
- package/packages/daemon/src/terminal/mirror/stock-pane-snapshot.ts +228 -0
- package/packages/daemon/src/terminal/session-runtime/causal-cell-ledger.ts +1 -1
- package/packages/daemon/src/terminal/session-runtime/registry.ts +203 -32
- package/packages/daemon/src/terminal/session-runtime/runtime-observability.ts +7 -0
- package/packages/daemon/src/terminal/session-runtime/stock-capture-fidelity.ts +13 -0
- package/packages/daemon/src/terminal/session-runtime/terminal-delivery-hub.ts +142 -8
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-interpreter.ts +26 -3
- package/packages/daemon/src/terminal/session-runtime/terminal-replica-owner.ts +25 -1
- package/packages/daemon/src/terminal/session-runtime/xterm-terminal-interpreter-backend.ts +26 -20
- package/packages/daemon/src/tui/integrations/claude-agent-teams.ts +334 -0
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +63 -4
- package/packages/daemon/src/tui/mirror/pane-surface.tsx +9 -1
- package/packages/daemon/src/tui/mirror/reference-performance-trace.ts +141 -90
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +27 -4
- package/packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface.tsx +61 -31
- package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +31 -3
- package/packages/daemon/src/tui/mirror/runtime/terminal-input-adapter.ts +9 -6
- package/packages/daemon/src/tui/mirror/testing/styled-frame-oracle.ts +209 -0
- package/packages/daemon-client/src/terminal-fast-lane.ts +53 -4
- package/packages/daemon-client/src/workspace-client-types.ts +1 -0
- package/packages/daemon-client/src/workspace-client.ts +78 -5
- package/packages/tmux-bridge/src/index.ts +1 -0
- package/packages/tmux-bridge/src/runner.ts +25 -1
- package/skill/SKILL.md +30 -30
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmux-ide",
|
|
3
|
-
"version": "2.9.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "2.9.5",
|
|
4
|
+
"description": "The open-source workspace for coding agents: run Claude Code, Codex and other agents in tmux with live status",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"tmux-ide": "bin/cli.js"
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"!packages/daemon/dist/**/__snapshots__/**"
|
|
37
37
|
],
|
|
38
38
|
"scripts": {
|
|
39
|
+
"test:tmux-boundary": "node scripts/test-tmux-boundary.mjs",
|
|
39
40
|
"build": "pnpm build:cli",
|
|
40
41
|
"build:web": "pnpm --filter @tmux-ide/desktop-renderer run build:web",
|
|
41
42
|
"build:cli": "node scripts/build-cli.mjs",
|
|
@@ -64,7 +65,6 @@
|
|
|
64
65
|
"typecheck:workspace": "turbo run typecheck",
|
|
65
66
|
"docs:build": "turbo run build --filter=@tmux-ide/docs",
|
|
66
67
|
"pack:check": "pnpm test:installer && node --test scripts/lib/npm-release-tag.test.mjs && node scripts/pack-tui-check.mjs",
|
|
67
|
-
"pack:web:check": "pnpm build:web && node scripts/pack-web-check.mjs",
|
|
68
68
|
"test:pack-installed": "node scripts/pack-check-run.mjs",
|
|
69
69
|
"release:opentui:check": "node scripts/opentui-release-check.mjs",
|
|
70
70
|
"check:native-deps": "node packages/daemon/scripts/check-native-deps.mjs",
|
|
@@ -72,7 +72,9 @@
|
|
|
72
72
|
"postinstall": "node scripts/postinstall.js",
|
|
73
73
|
"docs": "turbo run dev --filter=@tmux-ide/docs",
|
|
74
74
|
"demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
|
|
75
|
-
"
|
|
75
|
+
"demo:font": "node docs/scripts/tui-demo-font/build.mjs",
|
|
76
|
+
"readme:media": "node docs/scripts/readme-media.mjs",
|
|
77
|
+
"test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/testing/styled-frame-oracle.test.tsx ./packages/daemon/src/tui/mirror/runtime/terminal-transient-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/terminal-draw-failure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-view-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-root-error-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-backpressure-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-demand-cadence-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/files-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/changes-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/missions-activity-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/dialogs-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/optional-feature-registry-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/palette-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/pane-scoped-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/rich-preview/feature.test.ts ./packages/daemon/src/tui/mirror/runtime/rich-preview-optional-feature-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/application-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/pane-frame-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-chrome-view.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-window-strip-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-shell-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/workbench-dock-dual-host-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/agent-terminal-canvas-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/command-palette-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/opentui-insertion-stability-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-home-renderer.test.tsx ./packages/daemon/src/tui/mirror/workspace/terminal-pane-header-polish-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-roster-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-home-agent-flow-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/guided-tour-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/pane-interaction-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-machine-sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-add-machine-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-new-agent-dialog-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-switcher-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-palette-preview-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-palette-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/agent-team-renderer.test.tsx ./scripts/solid-conditional-props.test.tsx",
|
|
76
78
|
"test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
|
|
77
79
|
"test:tui-live": "node scripts/tui-testdrive.mjs smoke",
|
|
78
80
|
"test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
|
|
@@ -80,9 +82,9 @@
|
|
|
80
82
|
"qualify:installed-latency": "node packages/daemon/scripts/qualify-installed-latency.mjs",
|
|
81
83
|
"benchmark:comparative": "node scripts/comparative-terminal.mjs",
|
|
82
84
|
"test:contracts-initializer-purity": "node --test scripts/lib/contracts-initializer-purity.test.mjs scripts/lib/owner-evidence-bundle.test.mjs",
|
|
83
|
-
"test:benchmark-comparative": "node --test scripts/comparative-terminal-support.test.mjs scripts/lib/terminal-scroll-observations.test.mjs scripts/lib/comparative-terminal-resources.test.mjs scripts/lib/comparative-terminal-report.test.mjs",
|
|
85
|
+
"test:benchmark-comparative": "node --test scripts/comparative-terminal-scenario.test.mjs scripts/comparative-terminal-support.test.mjs scripts/lib/terminal-scroll-observations.test.mjs scripts/lib/comparative-terminal-resources.test.mjs scripts/lib/comparative-terminal-report.test.mjs scripts/lib/comparative-terminal-trace.test.mjs",
|
|
84
86
|
"measure:performance-portable": "node scripts/performance-portable-evidence.mjs",
|
|
85
|
-
"test:portable-release-contract": "node --experimental-strip-types --test scripts/qualify-divider-gestures.test.mjs scripts/lib/product-keyboard-pointer-resize.test.mjs scripts/lib/startup-launch-diagnostic.test.mjs scripts/lib/coherence-delivery-client.test.ts scripts/lib/coherence-trace.test.mjs scripts/lib/spark-qualification-descriptor.test.mjs scripts/lib/spark-qualification-ssh.test.mjs scripts/lib/performance-reference-target.test.mjs scripts/lib/performance-reference-workspace.test.mjs scripts/lib/performance-reference-runtime.test.mjs scripts/lib/performance-reference-report.test.mjs scripts/lib/product-rig-reference-environment.test.mjs && pnpm exec vitest run scripts/lib/portable-performance-evidence.test.mjs",
|
|
87
|
+
"test:portable-release-contract": "node --experimental-strip-types --test scripts/lib/styled-ssh-qualification.test.mjs scripts/lib/boundary-results.test.mjs scripts/lib/packed-cli-resolution.test.mjs scripts/qualify-divider-gestures.test.mjs scripts/lib/product-keyboard-pointer-resize.test.mjs scripts/lib/startup-launch-diagnostic.test.mjs scripts/lib/coherence-delivery-client.test.ts scripts/lib/coherence-trace.test.mjs scripts/lib/spark-qualification-descriptor.test.mjs scripts/lib/spark-qualification-ssh.test.mjs scripts/lib/performance-reference-target.test.mjs scripts/lib/performance-reference-workspace.test.mjs scripts/lib/performance-reference-runtime.test.mjs scripts/lib/performance-reference-report.test.mjs scripts/lib/performance-reference-input-mapping.test.mjs scripts/lib/transport-clock-evidence.test.mjs scripts/lib/performance-reference-output-content.test.mjs scripts/lib/daemon-trace-admission.test.mjs scripts/lib/owned-reference-capture.test.mjs scripts/lib/product-rig-reference-environment.test.mjs && pnpm exec vitest run scripts/lib/portable-performance-evidence.test.mjs",
|
|
86
88
|
"measure:performance-reference": "node scripts/performance-reference.mjs",
|
|
87
89
|
"test:performance-reference": "node scripts/performance-reference.mjs --require-complete",
|
|
88
90
|
"test:tui-perf": "pnpm test:performance-qualification",
|
|
@@ -108,11 +110,16 @@
|
|
|
108
110
|
"test:tui-gallery": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./scripts/tui-gallery/gallery.test.tsx",
|
|
109
111
|
"typecheck:tui-gallery": "tsc --noEmit -p scripts/tsconfig.tui-gallery.json",
|
|
110
112
|
"test:spark-qualification": "node --import tsx --test scripts/lib/spark-*.test.ts scripts/lib/spark-*.test.mjs",
|
|
111
|
-
"test:installer": "node --test scripts/install.test.mjs"
|
|
113
|
+
"test:installer": "node --test scripts/install.test.mjs",
|
|
114
|
+
"qualify:styled-ssh": "node scripts/qualify-styled-ssh.mjs"
|
|
112
115
|
},
|
|
113
116
|
"keywords": [
|
|
114
117
|
"tmux",
|
|
115
|
-
"
|
|
118
|
+
"coding-agents",
|
|
119
|
+
"claude-code",
|
|
120
|
+
"codex",
|
|
121
|
+
"ai-agents",
|
|
122
|
+
"agent-teams",
|
|
116
123
|
"terminal",
|
|
117
124
|
"workspace",
|
|
118
125
|
"developer-tools"
|
|
@@ -124,7 +131,7 @@
|
|
|
124
131
|
"type": "git",
|
|
125
132
|
"url": "git+https://github.com/wavyrai/tmux-ide.git"
|
|
126
133
|
},
|
|
127
|
-
"homepage": "https://
|
|
134
|
+
"homepage": "https://tmux-ide.com",
|
|
128
135
|
"bugs": {
|
|
129
136
|
"url": "https://github.com/wavyrai/tmux-ide/issues"
|
|
130
137
|
},
|
|
@@ -374,6 +374,8 @@ export interface SessionRuntimeTerminalSubscription<
|
|
|
374
374
|
Tombstone = unknown,
|
|
375
375
|
> {
|
|
376
376
|
readonly generation: SessionRuntimeGeneration;
|
|
377
|
+
/** Pane-local terminal capability failure, replayed to late listeners. */
|
|
378
|
+
onUnavailable?(listener: (message: string) => void): () => void;
|
|
377
379
|
close(): Promise<void>;
|
|
378
380
|
freeze(): void;
|
|
379
381
|
thaw(): void;
|
|
@@ -9,6 +9,10 @@ import {
|
|
|
9
9
|
TerminalReplicaTombstonePayloadSchemaZ,
|
|
10
10
|
} from "./terminal-replica.ts";
|
|
11
11
|
|
|
12
|
+
/** Stable capability diagnosis carried by the existing pane-scoped source-closed fault. */
|
|
13
|
+
export const STOCK_CAPTURE_TAB_UNAVAILABLE =
|
|
14
|
+
"This tmux server cannot provide an exact snapshot of saved tab cells. Continue this session in native tmux, or use a new session started with tmux-ide's bundled server.";
|
|
15
|
+
|
|
12
16
|
export const TERMINAL_DELIVERY_PROTOCOL_VERSION = 1 as const;
|
|
13
17
|
export const TERMINAL_DELIVERY_CHUNK_BYTES = 256 * 1024;
|
|
14
18
|
export const TERMINAL_DELIVERY_PATCH_TO_SEED_BYTES = 512 * 1024;
|
|
@@ -4,6 +4,7 @@ interface CompactReplicaCapability {
|
|
|
4
4
|
readonly baseline: TerminalReplicaSnapshot | null;
|
|
5
5
|
readonly snapshot: TerminalReplicaSnapshot | null;
|
|
6
6
|
readonly hash: string;
|
|
7
|
+
readonly tombstoneInterval?: Readonly<{ baseRevision: number; revision: number }>;
|
|
7
8
|
}
|
|
8
9
|
|
|
9
10
|
const compactReplicaCapabilities = new WeakMap<object, CompactReplicaCapability>();
|
|
@@ -14,8 +15,17 @@ export function grantCompactReplicaCapability(
|
|
|
14
15
|
baseline: TerminalReplicaSnapshot | null,
|
|
15
16
|
snapshot: TerminalReplicaSnapshot | null,
|
|
16
17
|
hash: string,
|
|
18
|
+
tombstoneInterval?: Readonly<{ baseRevision: number; revision: number }>,
|
|
17
19
|
): void {
|
|
18
|
-
compactReplicaCapabilities.set(
|
|
20
|
+
compactReplicaCapabilities.set(
|
|
21
|
+
owner,
|
|
22
|
+
Object.freeze({
|
|
23
|
+
baseline,
|
|
24
|
+
snapshot,
|
|
25
|
+
hash,
|
|
26
|
+
...(tombstoneInterval ? { tombstoneInterval: Object.freeze({ ...tombstoneInterval }) } : {}),
|
|
27
|
+
}),
|
|
28
|
+
);
|
|
19
29
|
}
|
|
20
30
|
|
|
21
31
|
/** Package-private one-shot adoption: ordinary objects cannot manufacture this grant. */
|
|
@@ -29,3 +39,23 @@ export function consumeCompactReplicaCapability(
|
|
|
29
39
|
if (!capability || capability.baseline !== baseline || capability.hash !== hash) return undefined;
|
|
30
40
|
return capability.snapshot;
|
|
31
41
|
}
|
|
42
|
+
|
|
43
|
+
/** Only an exact hash-verified decoded tombstone interval can bridge canonical revisions. */
|
|
44
|
+
export function consumeVerifiedTombstoneInterval(
|
|
45
|
+
owner: object,
|
|
46
|
+
baseline: TerminalReplicaSnapshot | null,
|
|
47
|
+
hash: string,
|
|
48
|
+
baseRevision: number,
|
|
49
|
+
revision: number,
|
|
50
|
+
): boolean {
|
|
51
|
+
const capability = compactReplicaCapabilities.get(owner);
|
|
52
|
+
compactReplicaCapabilities.delete(owner);
|
|
53
|
+
return (
|
|
54
|
+
capability !== undefined &&
|
|
55
|
+
capability.baseline === baseline &&
|
|
56
|
+
capability.snapshot === null &&
|
|
57
|
+
capability.hash === hash &&
|
|
58
|
+
capability.tombstoneInterval?.baseRevision === baseRevision &&
|
|
59
|
+
capability.tombstoneInterval.revision === revision
|
|
60
|
+
);
|
|
61
|
+
}
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
isOwnedTerminalReplicaRow,
|
|
4
|
+
isSchemaValidProjectedTerminalReplicaRow,
|
|
5
|
+
TERMINAL_REPLICA_EMPTY_CELL,
|
|
6
|
+
TERMINAL_REPLICA_SPACE_CELL,
|
|
7
|
+
} from "./terminal-replica-owned-row.ts";
|
|
3
8
|
import {
|
|
4
9
|
TERMINAL_DELIVERY_CHUNK_BYTES,
|
|
5
10
|
TERMINAL_DELIVERY_MAX_REPRESENTATION_BYTES,
|
|
@@ -11,6 +16,7 @@ import {
|
|
|
11
16
|
TerminalSemanticDeliveryPayloadSchemaZ,
|
|
12
17
|
TerminalReplicaSnapshotSchemaZ,
|
|
13
18
|
TerminalReplicaRowSchemaZ,
|
|
19
|
+
TerminalReplicaPatchPayloadSchemaZ,
|
|
14
20
|
TerminalReplicaCellSchemaZ,
|
|
15
21
|
TerminalReplicaPlacementSchemaZ,
|
|
16
22
|
type TerminalDeliveryAck,
|
|
@@ -38,6 +44,7 @@ import {
|
|
|
38
44
|
} from "./terminal-replica.ts";
|
|
39
45
|
import { grantCompactReplicaCapability } from "./terminal-compact-capability.ts";
|
|
40
46
|
import {
|
|
47
|
+
createDecodedTerminalReplicaRowBuilder,
|
|
41
48
|
hashTerminalReplicaRowCooperatively,
|
|
42
49
|
hashTerminalReplicaRowRunsCooperatively,
|
|
43
50
|
isTerminalReplicaRowDeeplyFrozen,
|
|
@@ -215,16 +222,124 @@ type CompactRow = readonly [0 | 1, readonly CompactCellRun[]];
|
|
|
215
222
|
// with the strict contract before remembering it; ownership alone is not enough.
|
|
216
223
|
// External rows still use the ordinary schema and detached parse result.
|
|
217
224
|
const validatedOwnedEncodingRows = new WeakSet<TerminalReplicaRow>();
|
|
225
|
+
// Only these exact deeply immutable constants may reuse strict cell validation.
|
|
226
|
+
// Ownership alone never qualifies arbitrary cells, including frozen lookalikes.
|
|
227
|
+
const CompactCellSliceSchema = /* @__PURE__ */ TerminalReplicaCellSchemaZ.array();
|
|
228
|
+
let trustedBlankCellsValidated = false;
|
|
229
|
+
function validateTrustedBlankCells(): void {
|
|
230
|
+
if (trustedBlankCellsValidated) return;
|
|
231
|
+
CompactCellSliceSchema.parse([TERMINAL_REPLICA_EMPTY_CELL, TERMINAL_REPLICA_SPACE_CELL]);
|
|
232
|
+
trustedBlankCellsValidated = true;
|
|
233
|
+
}
|
|
234
|
+
const CompactOwnedCellSchema = z.union([
|
|
235
|
+
z.custom<TerminalReplicaCell>((cell) => {
|
|
236
|
+
if (cell !== TERMINAL_REPLICA_EMPTY_CELL && cell !== TERMINAL_REPLICA_SPACE_CELL) return false;
|
|
237
|
+
validateTrustedBlankCells();
|
|
238
|
+
return true;
|
|
239
|
+
}),
|
|
240
|
+
TerminalReplicaCellSchemaZ,
|
|
241
|
+
]);
|
|
242
|
+
const CompactOwnedCellSliceSchema = CompactOwnedCellSchema.array();
|
|
243
|
+
const CompactOwnedEncodingRowSchema = TerminalReplicaRowSchemaZ.extend({
|
|
244
|
+
cells: CompactOwnedCellSliceSchema,
|
|
245
|
+
});
|
|
246
|
+
|
|
218
247
|
const CompactEncodingRowSchema = z.union([
|
|
219
248
|
z.custom<TerminalReplicaRow>((input) => {
|
|
220
249
|
if (!isOwnedTerminalReplicaRow(input)) return false;
|
|
221
|
-
if (validatedOwnedEncodingRows.has(input)
|
|
222
|
-
|
|
250
|
+
if (validatedOwnedEncodingRows.has(input) || isSchemaValidProjectedTerminalReplicaRow(input))
|
|
251
|
+
return true;
|
|
252
|
+
const schema = prefersTrustedCellValidation(input.cells)
|
|
253
|
+
? CompactOwnedEncodingRowSchema
|
|
254
|
+
: TerminalReplicaRowSchemaZ;
|
|
255
|
+
if (!schema.safeParse(input).success) return false;
|
|
223
256
|
validatedOwnedEncodingRows.add(input);
|
|
224
257
|
return true;
|
|
225
258
|
}),
|
|
226
259
|
TerminalReplicaRowSchemaZ,
|
|
227
260
|
]);
|
|
261
|
+
// Patch rows keep original strict issues, including their abort/continue state.
|
|
262
|
+
// Catch exposes those issues before Zod formats away that state. The tagged
|
|
263
|
+
// failure is local to this parser; arbitrary foreign getter errors still escape.
|
|
264
|
+
class CompactPatchRowFailure {
|
|
265
|
+
readonly issues: z.RefinementCtx["issues"];
|
|
266
|
+
readonly value: TerminalReplicaRow;
|
|
267
|
+
|
|
268
|
+
constructor(issues: z.RefinementCtx["issues"], value: TerminalReplicaRow) {
|
|
269
|
+
this.issues = issues;
|
|
270
|
+
this.value = value;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
const StrictCompactPatchRowSchema = TerminalReplicaRowSchemaZ.catch((context) => {
|
|
274
|
+
throw new CompactPatchRowFailure(context.issues, context.value as TerminalReplicaRow);
|
|
275
|
+
});
|
|
276
|
+
const CompactPatchEncodingRowSchema = z
|
|
277
|
+
.custom<TerminalReplicaRow>(() => true)
|
|
278
|
+
.transform((input, context) => {
|
|
279
|
+
const owned = isOwnedTerminalReplicaRow(input);
|
|
280
|
+
if (
|
|
281
|
+
owned &&
|
|
282
|
+
(validatedOwnedEncodingRows.has(input) || isSchemaValidProjectedTerminalReplicaRow(input))
|
|
283
|
+
)
|
|
284
|
+
return input;
|
|
285
|
+
if (owned && prefersTrustedCellValidation(input.cells)) {
|
|
286
|
+
const optimized = CompactOwnedEncodingRowSchema.safeParse(input);
|
|
287
|
+
if (optimized.success) {
|
|
288
|
+
validatedOwnedEncodingRows.add(input);
|
|
289
|
+
return input;
|
|
290
|
+
}
|
|
291
|
+
// Only immutable owned data can be read again to recover strict issues.
|
|
292
|
+
}
|
|
293
|
+
try {
|
|
294
|
+
const parsed = StrictCompactPatchRowSchema.parse(input);
|
|
295
|
+
if (!owned) return parsed;
|
|
296
|
+
validatedOwnedEncodingRows.add(input);
|
|
297
|
+
return input;
|
|
298
|
+
} catch (error) {
|
|
299
|
+
if (!(error instanceof CompactPatchRowFailure)) throw error;
|
|
300
|
+
for (const issue of error.issues) context.issues.push(issue);
|
|
301
|
+
return error.value;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
const CompactPatchEncodingPayloadSchema =
|
|
305
|
+
TerminalSemanticDeliveryPayloadSchemaZ.options[1].safeExtend({
|
|
306
|
+
patch: TerminalReplicaPatchPayloadSchemaZ.extend({
|
|
307
|
+
rows: z.array(
|
|
308
|
+
TerminalReplicaPatchPayloadSchemaZ.shape.rows.element.extend({
|
|
309
|
+
row: CompactPatchEncodingRowSchema,
|
|
310
|
+
}),
|
|
311
|
+
),
|
|
312
|
+
history: z.array(CompactPatchEncodingRowSchema).optional(),
|
|
313
|
+
historyDelta: TerminalReplicaPatchPayloadSchemaZ.shape.historyDelta
|
|
314
|
+
.unwrap()
|
|
315
|
+
.extend({ append: z.array(CompactPatchEncodingRowSchema) })
|
|
316
|
+
.optional(),
|
|
317
|
+
}),
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
function hasOwnedPatchRow(input: TerminalSemanticDeliveryPayload): boolean {
|
|
321
|
+
if (Object.getOwnPropertyDescriptor(input, "frame")?.value !== "patch") return false;
|
|
322
|
+
const patch = Object.getOwnPropertyDescriptor(input, "patch")?.value;
|
|
323
|
+
if (typeof patch !== "object" || patch === null) return false;
|
|
324
|
+
const first = (array: unknown): unknown =>
|
|
325
|
+
Array.isArray(array) ? Object.getOwnPropertyDescriptor(array, "0")?.value : undefined;
|
|
326
|
+
const entry = first(Object.getOwnPropertyDescriptor(patch, "rows")?.value);
|
|
327
|
+
if (
|
|
328
|
+
typeof entry === "object" &&
|
|
329
|
+
entry !== null &&
|
|
330
|
+
isOwnedTerminalReplicaRow(Object.getOwnPropertyDescriptor(entry, "row")?.value)
|
|
331
|
+
)
|
|
332
|
+
return true;
|
|
333
|
+
if (isOwnedTerminalReplicaRow(first(Object.getOwnPropertyDescriptor(patch, "history")?.value)))
|
|
334
|
+
return true;
|
|
335
|
+
const delta = Object.getOwnPropertyDescriptor(patch, "historyDelta")?.value;
|
|
336
|
+
return (
|
|
337
|
+
typeof delta === "object" &&
|
|
338
|
+
delta !== null &&
|
|
339
|
+
isOwnedTerminalReplicaRow(first(Object.getOwnPropertyDescriptor(delta, "append")?.value))
|
|
340
|
+
);
|
|
341
|
+
}
|
|
342
|
+
|
|
228
343
|
const CompactEncodingPayloadSchema = z.discriminatedUnion("frame", [
|
|
229
344
|
TerminalSemanticDeliveryPayloadSchemaZ.options[0].extend({
|
|
230
345
|
snapshot: TerminalReplicaSnapshotSchemaZ.extend({
|
|
@@ -256,7 +371,11 @@ export function encodeCompactSemanticTerminalUpdate(
|
|
|
256
371
|
input: TerminalSemanticDeliveryPayload,
|
|
257
372
|
): Uint8Array {
|
|
258
373
|
const update = (
|
|
259
|
-
|
|
374
|
+
hasOwnedPatchRow(input)
|
|
375
|
+
? CompactPatchEncodingPayloadSchema
|
|
376
|
+
: hasOwnedSeedRow(input)
|
|
377
|
+
? CompactEncodingPayloadSchema
|
|
378
|
+
: TerminalSemanticDeliveryPayloadSchemaZ
|
|
260
379
|
).parse(input);
|
|
261
380
|
// Compact payloads contain only arrays and primitives below this object.
|
|
262
381
|
// Insert root keys in canonical order so the native serializer emits exactly
|
|
@@ -303,7 +422,38 @@ const CompactSeedMetadataSchema = /* @__PURE__ */ TerminalReplicaSnapshotSchemaZ
|
|
|
303
422
|
placements: true,
|
|
304
423
|
});
|
|
305
424
|
const CompactRowHeaderSchema = /* @__PURE__ */ TerminalReplicaRowSchemaZ.omit({ cells: true });
|
|
306
|
-
|
|
425
|
+
function isValidatedTrustedBlankSlice(cells: readonly TerminalReplicaCell[]): boolean {
|
|
426
|
+
if (
|
|
427
|
+
!cells.every(
|
|
428
|
+
(cell) => cell === TERMINAL_REPLICA_EMPTY_CELL || cell === TERMINAL_REPLICA_SPACE_CELL,
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
return false;
|
|
432
|
+
validateTrustedBlankCells();
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// This is a conservative optimization choice, never a validation boundary.
|
|
437
|
+
// Low-density owned rows keep the ordinary parser rather than paying a failed
|
|
438
|
+
// union branch for each nontrusted cell. Foreign inputs never enter this scan.
|
|
439
|
+
function prefersTrustedCellValidation(cells: readonly TerminalReplicaCell[]): boolean {
|
|
440
|
+
let nontrusted = 0;
|
|
441
|
+
const limit = Math.floor(cells.length / 4);
|
|
442
|
+
for (const cell of cells) {
|
|
443
|
+
if (cell !== TERMINAL_REPLICA_EMPTY_CELL && cell !== TERMINAL_REPLICA_SPACE_CELL) {
|
|
444
|
+
if (++nontrusted > limit) return false;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
function validateOwnedCellSlice(cells: readonly TerminalReplicaCell[]): TerminalReplicaCell[] {
|
|
451
|
+
if (!prefersTrustedCellValidation(cells)) return CompactCellSliceSchema.parse(cells);
|
|
452
|
+
const parsed = CompactOwnedCellSliceSchema.safeParse(cells);
|
|
453
|
+
// Preserve the original strict parser's issues on invalid owned input.
|
|
454
|
+
// External cells never enter this path, so their getter/copy semantics stay unchanged.
|
|
455
|
+
return parsed.success ? parsed.data : CompactCellSliceSchema.parse(cells);
|
|
456
|
+
}
|
|
307
457
|
|
|
308
458
|
export function terminalSemanticUpdateNeedsCooperativeEncoding(
|
|
309
459
|
input: TerminalSemanticDeliveryPayload,
|
|
@@ -387,7 +537,9 @@ export async function encodeCompactSemanticTerminalUpdateCooperatively(
|
|
|
387
537
|
for (let index = 0; index < rows.length; index++) {
|
|
388
538
|
const row = rows[index]!;
|
|
389
539
|
const owned = isOwnedTerminalReplicaRow(row);
|
|
390
|
-
const validatedRow =
|
|
540
|
+
const validatedRow =
|
|
541
|
+
owned &&
|
|
542
|
+
(validatedOwnedEncodingRows.has(row) || isSchemaValidProjectedTerminalReplicaRow(row));
|
|
391
543
|
const { cells, ...rowHeaderInput } = row;
|
|
392
544
|
const rowHeader = CompactRowHeaderSchema.parse(rowHeaderInput);
|
|
393
545
|
if (++rowCount > COMPACT_MAX_ROWS || !Array.isArray(cells) || cells.length !== metadata.cols)
|
|
@@ -406,7 +558,12 @@ export async function encodeCompactSemanticTerminalUpdateCooperatively(
|
|
|
406
558
|
};
|
|
407
559
|
for (let offset = 0; offset < cells.length; offset += 256) {
|
|
408
560
|
const slice = cells.slice(offset, offset + 256);
|
|
409
|
-
const validated =
|
|
561
|
+
const validated =
|
|
562
|
+
validatedRow || (owned && isValidatedTrustedBlankSlice(slice))
|
|
563
|
+
? slice
|
|
564
|
+
: owned
|
|
565
|
+
? validateOwnedCellSlice(slice)
|
|
566
|
+
: CompactCellSliceSchema.parse(slice);
|
|
410
567
|
for (const cell of validated) {
|
|
411
568
|
const encoded = compactCell(cell);
|
|
412
569
|
if (prior && compactRunCellEqual(prior, encoded)) prior[0]++;
|
|
@@ -795,6 +952,7 @@ function expandPatch(value: unknown, budget: CompactDecodeBudget): TerminalRepli
|
|
|
795
952
|
}
|
|
796
953
|
|
|
797
954
|
interface CompactDecodeBudget {
|
|
955
|
+
verifiedSyncRows?: boolean;
|
|
798
956
|
rows: number;
|
|
799
957
|
runs: number;
|
|
800
958
|
cells: number;
|
|
@@ -878,6 +1036,7 @@ function expandRow(
|
|
|
878
1036
|
}
|
|
879
1037
|
const runsBefore = budget.runs;
|
|
880
1038
|
const cellsBefore = budget.cells;
|
|
1039
|
+
const builder = budget.verifiedSyncRows ? createDecodedTerminalReplicaRowBuilder() : null;
|
|
881
1040
|
const cells: TerminalReplicaCell[] = [];
|
|
882
1041
|
for (const valueRun of runs) {
|
|
883
1042
|
if (++budget.runs > COMPACT_MAX_RUNS)
|
|
@@ -889,29 +1048,41 @@ function expandRow(
|
|
|
889
1048
|
throw new TypeError("Compact semantic expanded cell budget exceeded");
|
|
890
1049
|
const grapheme = compactString(run[1], "grapheme");
|
|
891
1050
|
const width = compactInteger(run[2], 0, 2, "cell width") as 0 | 1 | 2;
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
background
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1051
|
+
if (builder) {
|
|
1052
|
+
const foreground = expandScalarColor(run[3]);
|
|
1053
|
+
const background = expandScalarColor(run[4]);
|
|
1054
|
+
const attributes = compactInteger(run[5], 0, 0xff, "cell attributes");
|
|
1055
|
+
builder.appendRun(count, grapheme, width, foreground, background, attributes);
|
|
1056
|
+
} else {
|
|
1057
|
+
const cell = Object.freeze({
|
|
1058
|
+
grapheme,
|
|
1059
|
+
width,
|
|
1060
|
+
foreground: expandColor(run[3]),
|
|
1061
|
+
background: expandColor(run[4]),
|
|
1062
|
+
attributes: compactInteger(run[5], 0, 0xff, "cell attributes"),
|
|
1063
|
+
});
|
|
1064
|
+
for (let index = 0; index < count; index += 1) cells.push(cell);
|
|
1065
|
+
}
|
|
900
1066
|
}
|
|
901
|
-
|
|
902
|
-
if (
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
if (
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
1067
|
+
let row: TerminalReplicaRow;
|
|
1068
|
+
if (builder) {
|
|
1069
|
+
row = builder.finish(wrapped, cols);
|
|
1070
|
+
} else {
|
|
1071
|
+
if (cols !== null && cells.length !== cols) throw new TypeError("Compact row width mismatch");
|
|
1072
|
+
if (cells.length < 1 || cells.length > COMPACT_MAX_DIMENSION)
|
|
1073
|
+
throw new TypeError("Compact row width is out of bounds");
|
|
1074
|
+
for (let index = 0; index < cells.length; index += 1) {
|
|
1075
|
+
if (cells[index]!.width === 2 && cells[index + 1]?.width !== 0)
|
|
1076
|
+
throw new TypeError("Malformed compact wide cell");
|
|
1077
|
+
if (cells[index]!.width === 0 && (index === 0 || cells[index - 1]?.width !== 2))
|
|
1078
|
+
throw new TypeError("Malformed compact continuation cell");
|
|
1079
|
+
}
|
|
1080
|
+
row = Object.freeze({
|
|
1081
|
+
cells: Object.freeze(cells),
|
|
1082
|
+
wrapped,
|
|
1083
|
+
}) as unknown as TerminalReplicaRow;
|
|
909
1084
|
}
|
|
910
|
-
|
|
911
|
-
cells: Object.freeze(cells),
|
|
912
|
-
wrapped,
|
|
913
|
-
}) as unknown as TerminalReplicaRow;
|
|
914
|
-
budget.allocatedCells += cells.length;
|
|
1085
|
+
budget.allocatedCells += row.cells.length;
|
|
915
1086
|
if (cacheKey !== null)
|
|
916
1087
|
budget.rowCache?.set(
|
|
917
1088
|
cacheKey,
|
|
@@ -924,6 +1095,17 @@ function expandRow(
|
|
|
924
1095
|
return row;
|
|
925
1096
|
}
|
|
926
1097
|
|
|
1098
|
+
function expandScalarColor(value: unknown): number {
|
|
1099
|
+
if (value === 0) return -1;
|
|
1100
|
+
const pair = compactArray(value, 2, "color");
|
|
1101
|
+
if (pair[0] === 1) return compactInteger(pair[1], 0, 255, "indexed color");
|
|
1102
|
+
if (pair[0] === 2) {
|
|
1103
|
+
const value = compactInteger(pair[1], 0, 0xffffff, "rgb color");
|
|
1104
|
+
return Object.is(value, -0) ? -2 : 0x1000000 + value;
|
|
1105
|
+
}
|
|
1106
|
+
throw new TypeError("Invalid compact color kind");
|
|
1107
|
+
}
|
|
1108
|
+
|
|
927
1109
|
function expandColor(value: unknown): TerminalReplicaColor {
|
|
928
1110
|
if (value === 0) return COMPACT_DEFAULT_COLOR;
|
|
929
1111
|
const pair = compactArray(value, 2, "color");
|
|
@@ -1238,7 +1420,8 @@ export function decodeVerifiedCompactSemanticTerminalUpdate(
|
|
|
1238
1420
|
payload: TerminalSemanticDeliveryPayload;
|
|
1239
1421
|
canonicalSnapshot: TerminalReplicaSnapshot | null;
|
|
1240
1422
|
}> {
|
|
1241
|
-
const decodeBudget =
|
|
1423
|
+
const decodeBudget = compactDecodeBudget(bytes.byteLength);
|
|
1424
|
+
decodeBudget.verifiedSyncRows = true;
|
|
1242
1425
|
const payload = decodeCompactSemanticTerminalUpdateInternal(bytes, decodeBudget);
|
|
1243
1426
|
const snapshot =
|
|
1244
1427
|
payload.frame === "seed"
|
|
@@ -1257,7 +1440,7 @@ export function decodeVerifiedCompactSemanticTerminalUpdate(
|
|
|
1257
1440
|
);
|
|
1258
1441
|
if (hash !== expectedHash) throw new TypeError("Canonical state hash mismatch");
|
|
1259
1442
|
compactCommitCapabilities.set(payload, Object.freeze({ snapshot, hash }));
|
|
1260
|
-
if (options?.grantReducerAdoption)
|
|
1443
|
+
if (options?.grantReducerAdoption) {
|
|
1261
1444
|
grantCompactReplicaCapability(
|
|
1262
1445
|
payload.frame === "seed"
|
|
1263
1446
|
? payload.snapshot
|
|
@@ -1267,7 +1450,13 @@ export function decodeVerifiedCompactSemanticTerminalUpdate(
|
|
|
1267
1450
|
baseline,
|
|
1268
1451
|
snapshot,
|
|
1269
1452
|
hash,
|
|
1453
|
+
payload.frame === "tombstone"
|
|
1454
|
+
? { baseRevision: payload.baseRevision, revision: payload.revision }
|
|
1455
|
+
: undefined,
|
|
1270
1456
|
);
|
|
1457
|
+
if (payload.frame === "patch" && payload.revision > payload.baseRevision + 1 && snapshot)
|
|
1458
|
+
grantCompactReplicaCapability(snapshot, baseline, snapshot, hash);
|
|
1459
|
+
}
|
|
1271
1460
|
if (options?.onComplete && decodeBudget) {
|
|
1272
1461
|
try {
|
|
1273
1462
|
options.onComplete(
|
|
@@ -1430,7 +1619,7 @@ export async function decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
|
1430
1619
|
payload.frame === "tombstone" ? payload.tombstone.reason : "protocol",
|
|
1431
1620
|
);
|
|
1432
1621
|
if (hash !== expectedHash) throw new TypeError("Canonical state hash mismatch");
|
|
1433
|
-
if (options.grantReducerAdoption)
|
|
1622
|
+
if (options.grantReducerAdoption) {
|
|
1434
1623
|
grantCompactReplicaCapability(
|
|
1435
1624
|
payload.frame === "seed"
|
|
1436
1625
|
? payload.snapshot
|
|
@@ -1440,7 +1629,13 @@ export async function decodeVerifiedCompactSemanticTerminalUpdateCooperatively(
|
|
|
1440
1629
|
baseline,
|
|
1441
1630
|
snapshot,
|
|
1442
1631
|
hash,
|
|
1632
|
+
payload.frame === "tombstone"
|
|
1633
|
+
? { baseRevision: payload.baseRevision, revision: payload.revision }
|
|
1634
|
+
: undefined,
|
|
1443
1635
|
);
|
|
1636
|
+
if (payload.frame === "patch" && payload.revision > payload.baseRevision + 1 && snapshot)
|
|
1637
|
+
grantCompactReplicaCapability(snapshot, baseline, snapshot, hash);
|
|
1638
|
+
}
|
|
1444
1639
|
if (options.onComplete) {
|
|
1445
1640
|
try {
|
|
1446
1641
|
options.onComplete(
|
|
@@ -1498,6 +1693,7 @@ interface CompactParsedRowsSlice {
|
|
|
1498
1693
|
|
|
1499
1694
|
class CooperativeJsonSource {
|
|
1500
1695
|
readonly #bytes: Uint8Array;
|
|
1696
|
+
readonly #decoder = new TextDecoder("utf-8", { fatal: true });
|
|
1501
1697
|
readonly length: number;
|
|
1502
1698
|
|
|
1503
1699
|
constructor(bytes: Uint8Array) {
|
|
@@ -1516,7 +1712,8 @@ class CooperativeJsonSource {
|
|
|
1516
1712
|
}
|
|
1517
1713
|
|
|
1518
1714
|
slice(start: number, end: number): string {
|
|
1519
|
-
|
|
1715
|
+
// Non-streaming decode resets UTF-8/BOM state for every independent slice.
|
|
1716
|
+
return this.#decoder.decode(this.#bytes.subarray(start, end));
|
|
1520
1717
|
}
|
|
1521
1718
|
|
|
1522
1719
|
bytes(start: number, end: number): Uint8Array {
|
|
@@ -2434,7 +2631,12 @@ export function decodeVerifiedLegacySemanticTerminalUpdate(
|
|
|
2434
2631
|
baseline,
|
|
2435
2632
|
snapshot,
|
|
2436
2633
|
hash,
|
|
2634
|
+
payload.frame === "tombstone"
|
|
2635
|
+
? { baseRevision: payload.baseRevision, revision: payload.revision }
|
|
2636
|
+
: undefined,
|
|
2437
2637
|
);
|
|
2638
|
+
if (payload.frame === "patch" && payload.revision > payload.baseRevision + 1 && snapshot)
|
|
2639
|
+
grantCompactReplicaCapability(snapshot, baseline, snapshot, hash);
|
|
2438
2640
|
return Object.freeze({ payload, canonicalSnapshot: snapshot });
|
|
2439
2641
|
}
|
|
2440
2642
|
|