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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tmux-ide",
|
|
3
|
-
"version": "2.9.
|
|
3
|
+
"version": "2.9.3",
|
|
4
4
|
"description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,7 +25,15 @@
|
|
|
25
25
|
"packages/presentation/package.json",
|
|
26
26
|
"packages/tmux-bridge/src",
|
|
27
27
|
"packages/tmux-bridge/package.json",
|
|
28
|
-
"packages/contracts/package.json"
|
|
28
|
+
"packages/contracts/package.json",
|
|
29
|
+
"!packages/*/src/**/*.test.*",
|
|
30
|
+
"!packages/*/src/**/*.spec.*",
|
|
31
|
+
"!packages/*/src/**/__tests__/**",
|
|
32
|
+
"!packages/*/src/**/__snapshots__/**",
|
|
33
|
+
"!packages/daemon/dist/**/*.test.*",
|
|
34
|
+
"!packages/daemon/dist/**/*.spec.*",
|
|
35
|
+
"!packages/daemon/dist/**/__tests__/**",
|
|
36
|
+
"!packages/daemon/dist/**/__snapshots__/**"
|
|
29
37
|
],
|
|
30
38
|
"scripts": {
|
|
31
39
|
"build": "pnpm build:cli",
|
|
@@ -41,8 +49,8 @@
|
|
|
41
49
|
"dev:instance": "node scripts/development-instance.mjs",
|
|
42
50
|
"dev:pilotty": "node scripts/pilotty-dev.mjs",
|
|
43
51
|
"dev:web": "node scripts/dev-web.mjs",
|
|
44
|
-
"test": "pnpm
|
|
45
|
-
"test:unit": "pnpm -r --workspace-concurrency=1 --filter @tmux-ide/daemon --filter @tmux-ide/contracts --filter @tmux-ide/core --filter @tmux-ide/daemon-client --filter @tmux-ide/sdk --filter @tmux-ide/desktop-renderer --filter @tmux-ide/
|
|
52
|
+
"test": "pnpm run test:unit",
|
|
53
|
+
"test:unit": "pnpm run build:cli && pnpm -r --workspace-concurrency=1 --filter @tmux-ide/daemon --filter @tmux-ide/contracts --filter @tmux-ide/core --filter @tmux-ide/daemon-client --filter @tmux-ide/sdk --filter @tmux-ide/desktop-renderer --filter @tmux-ide/electron-shell run test",
|
|
46
54
|
"test:daemon-bun": "bun test ./packages/daemon/src/lib/canonical-daemon-supervision.test.ts ./packages/daemon/src/lib/canonical-daemon.test.ts ./packages/daemon/src/lib/auth/middleware.test.ts ./packages/daemon/src/command-center/actions/handlers/daemon-shutdown.test.ts ./packages/daemon/src/command-center/resources/application-shell.test.ts ./packages/daemon/src/command-center/resources/agent-graph-overlay.test.ts ./packages/daemon/src/tui/mirror/runtime/runtime-layout-presentation.test.ts ./packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.test.ts ./packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.test.ts",
|
|
47
55
|
"lint": "eslint bin scripts packages/contracts/src packages/core/src packages/daemon-client/src packages/sdk/src packages/tmux-bridge/src packages/daemon/src",
|
|
48
56
|
"lint:workspace": "turbo run lint",
|
|
@@ -64,7 +72,7 @@
|
|
|
64
72
|
"postinstall": "node scripts/postinstall.js",
|
|
65
73
|
"docs": "turbo run dev --filter=@tmux-ide/docs",
|
|
66
74
|
"demo:tui": "bun --preload @opentui/solid/preload docs/scripts/render-tui-demo.tsx",
|
|
67
|
-
"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/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",
|
|
75
|
+
"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/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",
|
|
68
76
|
"test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
|
|
69
77
|
"test:tui-live": "node scripts/tui-testdrive.mjs smoke",
|
|
70
78
|
"test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
|
|
@@ -139,6 +147,7 @@
|
|
|
139
147
|
"@eslint/js": "^10.0.1",
|
|
140
148
|
"@opentui/core": "^0.5.1",
|
|
141
149
|
"@opentui/solid": "^0.5.1",
|
|
150
|
+
"@tmux-ide/contracts": "workspace:*",
|
|
142
151
|
"@tsconfig/bun": "^1.0.10",
|
|
143
152
|
"@types/node": "^25.5.0",
|
|
144
153
|
"@types/ws": "^8.18.1",
|
|
@@ -53,3 +53,22 @@ export const SavedMachineRegistrySchema = z
|
|
|
53
53
|
});
|
|
54
54
|
});
|
|
55
55
|
export type SavedMachineRegistry = z.infer<typeof SavedMachineRegistrySchema>;
|
|
56
|
+
|
|
57
|
+
/** Local owner actions; never a request to stop a remote daemon or tmux session. */
|
|
58
|
+
export const SavedMachineMutationSchema = z.discriminatedUnion("operation", [
|
|
59
|
+
z.strictObject({
|
|
60
|
+
id: SavedMachineIdSchema,
|
|
61
|
+
operation: z.enum(["enable", "disable", "remove"]),
|
|
62
|
+
}),
|
|
63
|
+
z.strictObject({
|
|
64
|
+
id: SavedMachineIdSchema,
|
|
65
|
+
operation: z.literal("edit"),
|
|
66
|
+
patch: SavedMachineSchema.pick({ label: true, sshTarget: true })
|
|
67
|
+
.partial()
|
|
68
|
+
.refine(
|
|
69
|
+
(patch) => patch.label !== undefined || patch.sshTarget !== undefined,
|
|
70
|
+
"Provide a label or SSH target",
|
|
71
|
+
),
|
|
72
|
+
}),
|
|
73
|
+
]);
|
|
74
|
+
export type SavedMachineMutation = z.infer<typeof SavedMachineMutationSchema>;
|
|
@@ -13,7 +13,7 @@ export type TerminalReplicaCellAttributes = z.infer<typeof TerminalReplicaCellAt
|
|
|
13
13
|
export const TerminalReplicaCellSchemaZ = z
|
|
14
14
|
.object({
|
|
15
15
|
grapheme: z.string(),
|
|
16
|
-
width: z.
|
|
16
|
+
width: z.literal([0, 1, 2]),
|
|
17
17
|
foreground: TerminalReplicaColorSchemaZ,
|
|
18
18
|
background: TerminalReplicaColorSchemaZ,
|
|
19
19
|
attributes: TerminalReplicaCellAttributesSchemaZ,
|
|
@@ -24,10 +24,11 @@
|
|
|
24
24
|
"scripts": {
|
|
25
25
|
"lint": "eslint src",
|
|
26
26
|
"typecheck": "tsc --noEmit",
|
|
27
|
-
"test": "vitest run"
|
|
27
|
+
"test": "vitest run --exclude src/terminal-compact-cold.test.ts && vitest run src/terminal-compact-cold.test.ts --maxWorkers=1"
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@tmux-ide/contracts": "workspace:*"
|
|
30
|
+
"@tmux-ide/contracts": "workspace:*",
|
|
31
|
+
"zod": "^4.3.6"
|
|
31
32
|
},
|
|
32
33
|
"devDependencies": {
|
|
33
34
|
"typescript": "^5.9.3",
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { isOwnedTerminalReplicaRow } from "./terminal-replica-owned-row.ts";
|
|
1
3
|
import {
|
|
2
4
|
TERMINAL_DELIVERY_CHUNK_BYTES,
|
|
3
5
|
TERMINAL_DELIVERY_MAX_REPRESENTATION_BYTES,
|
|
@@ -209,6 +211,43 @@ type CompactColor = 0 | readonly [1 | 2, number];
|
|
|
209
211
|
type CompactCellRun = [number, string, 0 | 1 | 2, CompactColor, CompactColor, number];
|
|
210
212
|
type CompactRow = readonly [0 | 1, readonly CompactCellRun[]];
|
|
211
213
|
|
|
214
|
+
// Repeated seeds share immutable core-owned rows. Validate each such row
|
|
215
|
+
// with the strict contract before remembering it; ownership alone is not enough.
|
|
216
|
+
// External rows still use the ordinary schema and detached parse result.
|
|
217
|
+
const validatedOwnedEncodingRows = new WeakSet<TerminalReplicaRow>();
|
|
218
|
+
const CompactEncodingRowSchema = z.union([
|
|
219
|
+
z.custom<TerminalReplicaRow>((input) => {
|
|
220
|
+
if (!isOwnedTerminalReplicaRow(input)) return false;
|
|
221
|
+
if (validatedOwnedEncodingRows.has(input)) return true;
|
|
222
|
+
if (!TerminalReplicaRowSchemaZ.safeParse(input).success) return false;
|
|
223
|
+
validatedOwnedEncodingRows.add(input);
|
|
224
|
+
return true;
|
|
225
|
+
}),
|
|
226
|
+
TerminalReplicaRowSchemaZ,
|
|
227
|
+
]);
|
|
228
|
+
const CompactEncodingPayloadSchema = z.discriminatedUnion("frame", [
|
|
229
|
+
TerminalSemanticDeliveryPayloadSchemaZ.options[0].extend({
|
|
230
|
+
snapshot: TerminalReplicaSnapshotSchemaZ.extend({
|
|
231
|
+
grid: z.array(CompactEncodingRowSchema),
|
|
232
|
+
history: z.array(CompactEncodingRowSchema),
|
|
233
|
+
}),
|
|
234
|
+
}),
|
|
235
|
+
TerminalSemanticDeliveryPayloadSchemaZ.options[1],
|
|
236
|
+
TerminalSemanticDeliveryPayloadSchemaZ.options[2],
|
|
237
|
+
]);
|
|
238
|
+
|
|
239
|
+
function hasOwnedSeedRow(input: TerminalSemanticDeliveryPayload): boolean {
|
|
240
|
+
// Select the optimized schema without reading user accessors. Its row union
|
|
241
|
+
// still validates every unowned row if a seed mixes owned and external data.
|
|
242
|
+
const snapshot = Object.getOwnPropertyDescriptor(input, "snapshot")?.value;
|
|
243
|
+
if (typeof snapshot !== "object" || snapshot === null) return false;
|
|
244
|
+
const grid = Object.getOwnPropertyDescriptor(snapshot, "grid")?.value;
|
|
245
|
+
return (
|
|
246
|
+
Array.isArray(grid) &&
|
|
247
|
+
isOwnedTerminalReplicaRow(Object.getOwnPropertyDescriptor(grid, "0")?.value)
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
|
|
212
251
|
/**
|
|
213
252
|
* A negotiated exact semantic representation. It changes only the wire shape:
|
|
214
253
|
* decode always returns the same strict canonical payload and hash authority.
|
|
@@ -216,7 +255,9 @@ type CompactRow = readonly [0 | 1, readonly CompactCellRun[]];
|
|
|
216
255
|
export function encodeCompactSemanticTerminalUpdate(
|
|
217
256
|
input: TerminalSemanticDeliveryPayload,
|
|
218
257
|
): Uint8Array {
|
|
219
|
-
const update =
|
|
258
|
+
const update = (
|
|
259
|
+
hasOwnedSeedRow(input) ? CompactEncodingPayloadSchema : TerminalSemanticDeliveryPayloadSchemaZ
|
|
260
|
+
).parse(input);
|
|
220
261
|
// Compact payloads contain only arrays and primitives below this object.
|
|
221
262
|
// Insert root keys in canonical order so the native serializer emits exactly
|
|
222
263
|
// the same representation without recursively allocating JSON fragments.
|
|
@@ -344,7 +385,10 @@ export async function encodeCompactSemanticTerminalUpdateCooperatively(
|
|
|
344
385
|
for (const rows of [grid, history]) {
|
|
345
386
|
write("[");
|
|
346
387
|
for (let index = 0; index < rows.length; index++) {
|
|
347
|
-
const
|
|
388
|
+
const row = rows[index]!;
|
|
389
|
+
const owned = isOwnedTerminalReplicaRow(row);
|
|
390
|
+
const validatedRow = owned && validatedOwnedEncodingRows.has(row);
|
|
391
|
+
const { cells, ...rowHeaderInput } = row;
|
|
348
392
|
const rowHeader = CompactRowHeaderSchema.parse(rowHeaderInput);
|
|
349
393
|
if (++rowCount > COMPACT_MAX_ROWS || !Array.isArray(cells) || cells.length !== metadata.cols)
|
|
350
394
|
compactEncodingLimit();
|
|
@@ -361,7 +405,8 @@ export async function encodeCompactSemanticTerminalUpdateCooperatively(
|
|
|
361
405
|
wroteRun = true;
|
|
362
406
|
};
|
|
363
407
|
for (let offset = 0; offset < cells.length; offset += 256) {
|
|
364
|
-
const
|
|
408
|
+
const slice = cells.slice(offset, offset + 256);
|
|
409
|
+
const validated = validatedRow ? slice : CompactCellSliceSchema.parse(slice);
|
|
365
410
|
for (const cell of validated) {
|
|
366
411
|
const encoded = compactCell(cell);
|
|
367
412
|
if (prior && compactRunCellEqual(prior, encoded)) prior[0]++;
|
|
@@ -381,6 +426,9 @@ export async function encodeCompactSemanticTerminalUpdateCooperatively(
|
|
|
381
426
|
}
|
|
382
427
|
emitRun();
|
|
383
428
|
write("]]");
|
|
429
|
+
// Header and every cell have passed the strict row contract, in bounded
|
|
430
|
+
// slices. Only a completed immutable row may be reused by either encoder.
|
|
431
|
+
if (owned) validatedOwnedEncodingRows.add(row);
|
|
384
432
|
}
|
|
385
433
|
write("],");
|
|
386
434
|
}
|
|
@@ -50,9 +50,9 @@ function initialize(): NativeHasher | null {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
/** Internal optional accelerator. Canonical serialization remains the caller's responsibility. */
|
|
53
|
-
export function createBufferedFnv64(): BufferedFnv64 | null {
|
|
53
|
+
export function createBufferedFnv64(initialState = OFFSET_BASIS): BufferedFnv64 | null {
|
|
54
54
|
const hasher = initialize();
|
|
55
|
-
return hasher ? new BufferedHasher(hasher) : null;
|
|
55
|
+
return hasher ? new BufferedHasher(hasher, initialState) : null;
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
class BufferedHasher implements BufferedFnv64 {
|
|
@@ -61,8 +61,9 @@ class BufferedHasher implements BufferedFnv64 {
|
|
|
61
61
|
#length = 0;
|
|
62
62
|
#state = OFFSET_BASIS;
|
|
63
63
|
|
|
64
|
-
constructor(hasher: NativeHasher) {
|
|
64
|
+
constructor(hasher: NativeHasher, initialState: bigint) {
|
|
65
65
|
this.#native = hasher;
|
|
66
|
+
this.#state = BigInt.asUintN(64, initialState);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
#flush(): void {
|
|
@@ -1,12 +1,18 @@
|
|
|
1
1
|
import type { TerminalReplicaColor, TerminalReplicaRow } from "@tmux-ide/contracts";
|
|
2
|
-
import { createBufferedFnv64 } from "./terminal-fnv64-wasm.ts";
|
|
2
|
+
import { createBufferedFnv64, type BufferedFnv64 } from "./terminal-fnv64-wasm.ts";
|
|
3
3
|
|
|
4
4
|
const ROW_HASH_CACHE = new WeakMap<object, string>();
|
|
5
5
|
const DEEPLY_FROZEN_ROWS = new WeakSet<object>();
|
|
6
6
|
const UTF8_ENCODER = new TextEncoder();
|
|
7
7
|
|
|
8
|
+
interface CanonicalKeyOrder {
|
|
9
|
+
readonly original: string[];
|
|
10
|
+
readonly sorted: string[];
|
|
11
|
+
readonly tokens: readonly (string | null)[] | null;
|
|
12
|
+
}
|
|
13
|
+
|
|
8
14
|
/**
|
|
9
|
-
*
|
|
15
|
+
* Streaming FNV-1a64 writer for the canonical terminal encoding.
|
|
10
16
|
*
|
|
11
17
|
* Keep the two 32-bit limbs: BigInt per byte made a unique 5k-row compact
|
|
12
18
|
* delivery monopolize the OpenTUI event loop for almost a second.
|
|
@@ -14,6 +20,58 @@ const UTF8_ENCODER = new TextEncoder();
|
|
|
14
20
|
class CanonicalFnv64 {
|
|
15
21
|
#high = 0xcbf29ce4;
|
|
16
22
|
#low = 0x84222325;
|
|
23
|
+
#remainingJsBytes: number;
|
|
24
|
+
#accelerator: BufferedFnv64 | null = null;
|
|
25
|
+
|
|
26
|
+
constructor(accelerateLargeFrame = false) {
|
|
27
|
+
this.#remainingJsBytes = accelerateLargeFrame ? 64 * 1024 : 0;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
#considerAcceleration(bytes: number): void {
|
|
31
|
+
if (this.#remainingJsBytes <= 0) return;
|
|
32
|
+
this.#remainingJsBytes -= bytes;
|
|
33
|
+
if (this.#remainingJsBytes <= 0) {
|
|
34
|
+
// Transfer the exact rolling state once; never restart or reserialize.
|
|
35
|
+
// Each writer owns its buffer across cooperative yields. Blocked WASM
|
|
36
|
+
// keeps the original JS path, without repeated initialization attempts.
|
|
37
|
+
this.#accelerator = createBufferedFnv64((BigInt(this.#high) << 32n) | BigInt(this.#low));
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Per-hash only: terminal objects repeat a handful of small field layouts.
|
|
41
|
+
// Reuse sorted keys and short ASCII key tokens without retaining records,
|
|
42
|
+
// values, or future calls. Tokens share the 16-layout/32-key cache bound.
|
|
43
|
+
#keyOrders: CanonicalKeyOrder[] | null = null;
|
|
44
|
+
|
|
45
|
+
keyOrder(record: Record<string, unknown>): CanonicalKeyOrder {
|
|
46
|
+
const keys = Object.keys(record);
|
|
47
|
+
if (keys.length > 32) return { original: keys, sorted: keys.sort(), tokens: null };
|
|
48
|
+
for (const order of this.#keyOrders ?? []) {
|
|
49
|
+
if (order.original.length !== keys.length) continue;
|
|
50
|
+
let matches = true;
|
|
51
|
+
for (let index = 0; index < keys.length; index++) {
|
|
52
|
+
if (order.original[index] !== keys[index]) {
|
|
53
|
+
matches = false;
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
if (matches) return order;
|
|
58
|
+
}
|
|
59
|
+
if ((this.#keyOrders?.length ?? 0) >= 16)
|
|
60
|
+
return { original: keys, sorted: keys.sort(), tokens: null };
|
|
61
|
+
const sorted = keys.length < 2 ? keys : keys.slice().sort();
|
|
62
|
+
const order = {
|
|
63
|
+
original: keys,
|
|
64
|
+
sorted,
|
|
65
|
+
tokens: sorted.map((key) => {
|
|
66
|
+
if (key.length > 32) return null;
|
|
67
|
+
for (let index = 0; index < key.length; index++)
|
|
68
|
+
if (key.charCodeAt(index) > 0x7f) return null;
|
|
69
|
+
return `s${key.length}:${key};`;
|
|
70
|
+
}),
|
|
71
|
+
};
|
|
72
|
+
(this.#keyOrders ??= []).push(order);
|
|
73
|
+
return order;
|
|
74
|
+
}
|
|
17
75
|
|
|
18
76
|
#byte(value: number): void {
|
|
19
77
|
const low = (this.#low ^ value) >>> 0;
|
|
@@ -24,6 +82,8 @@ class CanonicalFnv64 {
|
|
|
24
82
|
}
|
|
25
83
|
|
|
26
84
|
ascii(value: string): number {
|
|
85
|
+
this.#considerAcceleration(value.length);
|
|
86
|
+
if (this.#accelerator) return this.#accelerator.ascii(value);
|
|
27
87
|
// Canonical color/field tags dominate full-row hashing. Keep the limbs
|
|
28
88
|
// local through each fragment instead of reading/writing fields per byte.
|
|
29
89
|
let high = this.#high;
|
|
@@ -40,7 +100,9 @@ class CanonicalFnv64 {
|
|
|
40
100
|
}
|
|
41
101
|
|
|
42
102
|
bytes(value: Uint8Array): void {
|
|
43
|
-
|
|
103
|
+
this.#considerAcceleration(value.byteLength);
|
|
104
|
+
if (this.#accelerator) this.#accelerator.bytes(value);
|
|
105
|
+
else for (const byte of value) this.#byte(byte);
|
|
44
106
|
}
|
|
45
107
|
|
|
46
108
|
string(value: string): number {
|
|
@@ -56,13 +118,13 @@ class CanonicalFnv64 {
|
|
|
56
118
|
if (ascii) {
|
|
57
119
|
this.ascii(`s${value.length}:`);
|
|
58
120
|
this.ascii(value);
|
|
59
|
-
this
|
|
121
|
+
this.ascii(";");
|
|
60
122
|
return value.length;
|
|
61
123
|
}
|
|
62
124
|
const bytes = UTF8_ENCODER.encode(value);
|
|
63
125
|
this.ascii(`s${bytes.byteLength}:`);
|
|
64
|
-
|
|
65
|
-
this
|
|
126
|
+
this.bytes(bytes);
|
|
127
|
+
this.ascii(";");
|
|
66
128
|
return bytes.byteLength;
|
|
67
129
|
}
|
|
68
130
|
|
|
@@ -99,22 +161,26 @@ class CanonicalFnv64 {
|
|
|
99
161
|
return;
|
|
100
162
|
}
|
|
101
163
|
const record = value as Record<string, unknown>;
|
|
102
|
-
const keys =
|
|
164
|
+
const { sorted: keys, tokens } = this.keyOrder(record);
|
|
103
165
|
this.ascii(`o${keys.length}:`);
|
|
104
|
-
for (
|
|
105
|
-
|
|
166
|
+
for (let index = 0; index < keys.length; index++) {
|
|
167
|
+
const key = keys[index]!;
|
|
168
|
+
const token = tokens?.[index];
|
|
169
|
+
if (token !== null && token !== undefined) this.ascii(token);
|
|
170
|
+
else this.string(key);
|
|
106
171
|
this.value(record[key]);
|
|
107
172
|
}
|
|
108
173
|
this.ascii(";");
|
|
109
174
|
}
|
|
110
175
|
|
|
111
176
|
digest(): string {
|
|
177
|
+
if (this.#accelerator) return this.#accelerator.digest();
|
|
112
178
|
return `${this.#high.toString(16).padStart(8, "0")}${this.#low.toString(16).padStart(8, "0")}`;
|
|
113
179
|
}
|
|
114
180
|
}
|
|
115
181
|
|
|
116
182
|
export function hashCanonicalTerminalValue(value: unknown): string {
|
|
117
|
-
const hash = new CanonicalFnv64();
|
|
183
|
+
const hash = new CanonicalFnv64(true);
|
|
118
184
|
hash.value(value);
|
|
119
185
|
return hash.digest();
|
|
120
186
|
}
|
|
@@ -124,7 +190,7 @@ export async function hashCanonicalTerminalValueCooperatively(
|
|
|
124
190
|
yieldControl: () => Promise<void>,
|
|
125
191
|
workPerSlice = 4 * 1_024,
|
|
126
192
|
): Promise<string> {
|
|
127
|
-
const hash = new CanonicalFnv64();
|
|
193
|
+
const hash = new CanonicalFnv64(true);
|
|
128
194
|
let work = 0;
|
|
129
195
|
const checkpoint = (amount: number): boolean => {
|
|
130
196
|
work += amount;
|
|
@@ -175,7 +241,7 @@ export async function hashCanonicalTerminalValueCooperatively(
|
|
|
175
241
|
continue;
|
|
176
242
|
}
|
|
177
243
|
const record = entry as Record<string, unknown>;
|
|
178
|
-
const keys =
|
|
244
|
+
const keys = hash.keyOrder(record).sorted;
|
|
179
245
|
if (checkpoint(hash.ascii(`o${keys.length}:`) + keys.length)) await yieldControl();
|
|
180
246
|
stack.push({ kind: "ascii", value: ";" });
|
|
181
247
|
for (let index = keys.length - 1; index >= 0; index -= 1) {
|
|
@@ -215,6 +281,8 @@ const compactColorKey = (color: TerminalReplicaColor): number =>
|
|
|
215
281
|
/** Package-private per-decode cache for exact canonical cell byte segments. */
|
|
216
282
|
export class TerminalReplicaRunEncodingCache {
|
|
217
283
|
#canonicalBytes = 0;
|
|
284
|
+
#cachedCells = 0;
|
|
285
|
+
#retainedEncodingBytes = 0;
|
|
218
286
|
|
|
219
287
|
createHash(): CanonicalFnv64 | NonNullable<ReturnType<typeof createBufferedFnv64>> {
|
|
220
288
|
// Small deliveries benefit more from row reuse than from crossing into
|
|
@@ -238,17 +306,14 @@ export class TerminalReplicaRunEncodingCache {
|
|
|
238
306
|
readonly allocatedBytes: number;
|
|
239
307
|
readonly cacheMiss: boolean;
|
|
240
308
|
} {
|
|
241
|
-
const widths = this.#entries.get(cell.grapheme) ?? new Map();
|
|
242
|
-
this.#entries.set(cell.grapheme, widths);
|
|
243
309
|
const foregroundKey = compactColorKey(cell.foreground);
|
|
244
|
-
const foregrounds = widths.get(cell.width) ?? new Map();
|
|
245
|
-
widths.set(cell.width, foregrounds);
|
|
246
310
|
const backgroundKey = compactColorKey(cell.background);
|
|
247
|
-
const
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
311
|
+
const cached = this.#entries
|
|
312
|
+
.get(cell.grapheme)
|
|
313
|
+
?.get(cell.width)
|
|
314
|
+
?.get(foregroundKey)
|
|
315
|
+
?.get(backgroundKey)
|
|
316
|
+
?.get(cell.attributes);
|
|
252
317
|
if (cached) return { prepared: cached, allocatedBytes: 0, cacheMiss: false };
|
|
253
318
|
const graphemeBytes = UTF8_ENCODER.encode(cell.grapheme);
|
|
254
319
|
const numberText = (value: number): string => {
|
|
@@ -268,30 +333,63 @@ export class TerminalReplicaRunEncodingCache {
|
|
|
268
333
|
cell.background,
|
|
269
334
|
)}${numberText(cell.attributes)};`,
|
|
270
335
|
});
|
|
271
|
-
|
|
336
|
+
// A transaction may contain arbitrary text and RGB values. Bound both
|
|
337
|
+
// entry count and retained string/byte payload; uncached cells hash identically.
|
|
338
|
+
const retainedBytes =
|
|
339
|
+
graphemeBytes.byteLength +
|
|
340
|
+
2 * (cell.grapheme.length + prepared.prefix.length + prepared.suffix.length);
|
|
341
|
+
if (this.#cachedCells < 1_024 && this.#retainedEncodingBytes + retainedBytes <= 64 * 1_024) {
|
|
342
|
+
const widths = this.#entries.get(cell.grapheme) ?? new Map();
|
|
343
|
+
this.#entries.set(cell.grapheme, widths);
|
|
344
|
+
const foregrounds = widths.get(cell.width) ?? new Map();
|
|
345
|
+
widths.set(cell.width, foregrounds);
|
|
346
|
+
const backgrounds = foregrounds.get(foregroundKey) ?? new Map();
|
|
347
|
+
foregrounds.set(foregroundKey, backgrounds);
|
|
348
|
+
const attributes = backgrounds.get(backgroundKey) ?? new Map();
|
|
349
|
+
backgrounds.set(backgroundKey, attributes);
|
|
350
|
+
attributes.set(cell.attributes, prepared);
|
|
351
|
+
this.#cachedCells++;
|
|
352
|
+
this.#retainedEncodingBytes += retainedBytes;
|
|
353
|
+
}
|
|
272
354
|
return { prepared, allocatedBytes: graphemeBytes.byteLength, cacheMiss: true };
|
|
273
355
|
}
|
|
274
356
|
}
|
|
275
357
|
|
|
276
|
-
export function hashTerminalReplicaRowCached(
|
|
358
|
+
export function hashTerminalReplicaRowCached(
|
|
359
|
+
row: TerminalReplicaRow,
|
|
360
|
+
onMiss?: () => void,
|
|
361
|
+
encodingCache?: TerminalReplicaRunEncodingCache,
|
|
362
|
+
): string {
|
|
277
363
|
const cached = ROW_HASH_CACHE.get(row);
|
|
278
364
|
if (cached) return cached;
|
|
279
365
|
onMiss?.();
|
|
280
|
-
const hash = new CanonicalFnv64();
|
|
366
|
+
const hash = encodingCache?.createHash() ?? new CanonicalFnv64();
|
|
367
|
+
let canonicalBytes = 10 + String(row.cells.length).length;
|
|
281
368
|
hash.ascii("a2:");
|
|
282
369
|
hash.boolean(row.wrapped);
|
|
283
370
|
hash.ascii(`a${row.cells.length}:`);
|
|
284
371
|
for (const cell of row.cells) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
372
|
+
if (encodingCache) {
|
|
373
|
+
const { prepared } = encodingCache.prepare(cell);
|
|
374
|
+
hash.ascii(prepared.prefix);
|
|
375
|
+
hash.bytes(prepared.graphemeBytes);
|
|
376
|
+
hash.ascii(prepared.suffix);
|
|
377
|
+
canonicalBytes +=
|
|
378
|
+
prepared.prefix.length + prepared.graphemeBytes.length + prepared.suffix.length;
|
|
379
|
+
} else {
|
|
380
|
+
const direct = hash as CanonicalFnv64;
|
|
381
|
+
direct.ascii("a5:");
|
|
382
|
+
direct.string(cell.grapheme);
|
|
383
|
+
direct.number(cell.width);
|
|
384
|
+
writeColor(direct, cell.foreground);
|
|
385
|
+
writeColor(direct, cell.background);
|
|
386
|
+
direct.number(cell.attributes);
|
|
387
|
+
direct.ascii(";");
|
|
388
|
+
}
|
|
292
389
|
}
|
|
293
390
|
hash.ascii(";;");
|
|
294
391
|
const digest = hash.digest();
|
|
392
|
+
encodingCache?.recordCanonicalBytes(canonicalBytes);
|
|
295
393
|
if (isTerminalReplicaRowDeeplyFrozen(row)) {
|
|
296
394
|
DEEPLY_FROZEN_ROWS.add(row);
|
|
297
395
|
ROW_HASH_CACHE.set(row, digest);
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { TerminalReplicaRow } from "@tmux-ide/contracts";
|
|
2
|
+
|
|
3
|
+
// Ownership proves immutable, ordinary data properties, not schema validity.
|
|
4
|
+
// There is no registration API: callers can only obtain ownership by copying.
|
|
5
|
+
const OWNED_ROWS = new WeakSet<TerminalReplicaRow>();
|
|
6
|
+
|
|
7
|
+
export function isOwnedTerminalReplicaRow(row: unknown): row is TerminalReplicaRow {
|
|
8
|
+
return typeof row === "object" && row !== null && OWNED_ROWS.has(row as TerminalReplicaRow);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function freezeOwnedTerminalReplicaRow(row: TerminalReplicaRow): TerminalReplicaRow {
|
|
12
|
+
if (OWNED_ROWS.has(row)) return row;
|
|
13
|
+
const frozen = Object.freeze({
|
|
14
|
+
wrapped: row.wrapped,
|
|
15
|
+
cells: Object.freeze(
|
|
16
|
+
Array.from(row.cells, (cell) =>
|
|
17
|
+
Object.freeze({
|
|
18
|
+
...cell,
|
|
19
|
+
foreground: Object.freeze({ ...cell.foreground }),
|
|
20
|
+
background: Object.freeze({ ...cell.background }),
|
|
21
|
+
}),
|
|
22
|
+
),
|
|
23
|
+
),
|
|
24
|
+
}) as unknown as TerminalReplicaRow;
|
|
25
|
+
OWNED_ROWS.add(frozen);
|
|
26
|
+
return frozen;
|
|
27
|
+
}
|
|
@@ -10,12 +10,14 @@ import type {
|
|
|
10
10
|
TerminalReplicaTombstonePayload,
|
|
11
11
|
} from "@tmux-ide/contracts";
|
|
12
12
|
import { consumeCompactReplicaCapability } from "./terminal-compact-capability.ts";
|
|
13
|
+
import { freezeOwnedTerminalReplicaRow as freezeRow } from "./terminal-replica-owned-row.ts";
|
|
13
14
|
import {
|
|
14
15
|
hashCanonicalTerminalValue,
|
|
15
16
|
hashCanonicalTerminalValueCooperatively,
|
|
16
17
|
hashTerminalReplicaRowCached,
|
|
17
18
|
hashTerminalReplicaRowCooperatively,
|
|
18
19
|
isTerminalReplicaRowDeeplyFrozen,
|
|
20
|
+
TerminalReplicaRunEncodingCache,
|
|
19
21
|
} from "./terminal-replica-hash-cache.ts";
|
|
20
22
|
|
|
21
23
|
// Only rows validated and copied by this module can bypass a repeated copy.
|
|
@@ -835,27 +837,11 @@ function blankRow(cols: number): TerminalReplicaRow {
|
|
|
835
837
|
}) as unknown as TerminalReplicaRow;
|
|
836
838
|
}
|
|
837
839
|
|
|
838
|
-
function freezeRow(row: TerminalReplicaRow): TerminalReplicaRow {
|
|
839
|
-
return Object.freeze({
|
|
840
|
-
wrapped: row.wrapped,
|
|
841
|
-
cells: Object.freeze(
|
|
842
|
-
row.cells.map((cell) =>
|
|
843
|
-
Object.freeze({
|
|
844
|
-
...cell,
|
|
845
|
-
foreground: Object.freeze({ ...cell.foreground }),
|
|
846
|
-
background: Object.freeze({ ...cell.background }),
|
|
847
|
-
}),
|
|
848
|
-
),
|
|
849
|
-
),
|
|
850
|
-
}) as unknown as TerminalReplicaRow;
|
|
851
|
-
}
|
|
852
|
-
|
|
853
840
|
function validateAndFreezeRow(
|
|
854
841
|
row: TerminalReplicaRow,
|
|
855
842
|
profile?: MutableTerminalReplicaApplyProfile,
|
|
856
843
|
): TerminalReplicaRow {
|
|
857
844
|
if (VALIDATED_PATCH_ROWS.has(row)) return row;
|
|
858
|
-
const cells = new Array(row.cells.length);
|
|
859
845
|
for (let index = 0; index < row.cells.length; index += 1) {
|
|
860
846
|
const cell = row.cells[index]!;
|
|
861
847
|
if (profile) profile.counts.validatedCells += 1;
|
|
@@ -863,17 +849,11 @@ function validateAndFreezeRow(
|
|
|
863
849
|
throw new TypeError("Malformed terminal replica row");
|
|
864
850
|
if (cell.width === 0 && (index === 0 || row.cells[index - 1]?.width !== 2))
|
|
865
851
|
throw new TypeError("Malformed terminal replica row");
|
|
866
|
-
cells[index] = Object.freeze({
|
|
867
|
-
...cell,
|
|
868
|
-
foreground: Object.freeze({ ...cell.foreground }),
|
|
869
|
-
background: Object.freeze({ ...cell.background }),
|
|
870
|
-
});
|
|
871
|
-
if (profile) profile.counts.frozenCells += 1;
|
|
872
852
|
}
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
853
|
+
// Use the same copy owner as seed admission and encoding. Already owned rows
|
|
854
|
+
// retain identity; external rows still receive an immutable detached copy.
|
|
855
|
+
const frozen = freezeRow(row);
|
|
856
|
+
if (profile && frozen !== row) profile.counts.frozenCells += row.cells.length;
|
|
877
857
|
VALIDATED_PATCH_ROWS.add(frozen);
|
|
878
858
|
return frozen;
|
|
879
859
|
}
|
|
@@ -1034,11 +1014,13 @@ function hashTerminalReplicaRow(
|
|
|
1034
1014
|
row: TerminalReplicaRow,
|
|
1035
1015
|
profile?: MutableTerminalReplicaApplyProfile,
|
|
1036
1016
|
instrumentation?: TerminalReplicaApplyOptions["instrumentation"],
|
|
1017
|
+
encodingCache?: TerminalReplicaRunEncodingCache,
|
|
1037
1018
|
): string {
|
|
1038
1019
|
const started = readProfileClock(instrumentation);
|
|
1039
1020
|
const hash = hashTerminalReplicaRowCached(
|
|
1040
1021
|
row,
|
|
1041
1022
|
profile ? () => (profile.counts.rowHashMisses += 1) : undefined,
|
|
1023
|
+
encodingCache,
|
|
1042
1024
|
);
|
|
1043
1025
|
addProfileDuration(profile, "rowHash", started, instrumentation);
|
|
1044
1026
|
return hash;
|
|
@@ -1051,12 +1033,13 @@ function hashTerminalReplicaRows(
|
|
|
1051
1033
|
): string {
|
|
1052
1034
|
const cached = ROW_ARRAY_HASH_CACHE.get(rows);
|
|
1053
1035
|
if (cached) return cached.hash.toString(16).padStart(16, "0");
|
|
1036
|
+
const encodingCache = rows.length >= 64 ? new TerminalReplicaRunEncodingCache() : undefined;
|
|
1054
1037
|
let hash = 0n;
|
|
1055
1038
|
for (const row of rows)
|
|
1056
1039
|
hash = BigInt.asUintN(
|
|
1057
1040
|
64,
|
|
1058
1041
|
hash * ROW_SEQUENCE_BASE +
|
|
1059
|
-
BigInt(`0x${hashTerminalReplicaRow(row, profile, instrumentation)}`),
|
|
1042
|
+
BigInt(`0x${hashTerminalReplicaRow(row, profile, instrumentation, encodingCache)}`),
|
|
1060
1043
|
);
|
|
1061
1044
|
if (Object.isFrozen(rows) && rows.every(isTerminalReplicaRowDeeplyFrozen))
|
|
1062
1045
|
ROW_ARRAY_HASH_CACHE.set(rows, { hash, length: rows.length });
|
|
@@ -1099,10 +1082,12 @@ export function registerTerminalReplicaRowsDeltaHash(
|
|
|
1099
1082
|
);
|
|
1100
1083
|
hash = BigInt.asUintN(64, hash - contribution);
|
|
1101
1084
|
}
|
|
1085
|
+
const encodingCache = append.length >= 64 ? new TerminalReplicaRunEncodingCache() : undefined;
|
|
1102
1086
|
for (const row of append)
|
|
1103
1087
|
hash = BigInt.asUintN(
|
|
1104
1088
|
64,
|
|
1105
|
-
hash * ROW_SEQUENCE_BASE +
|
|
1089
|
+
hash * ROW_SEQUENCE_BASE +
|
|
1090
|
+
BigInt(`0x${hashTerminalReplicaRow(row, undefined, undefined, encodingCache)}`),
|
|
1106
1091
|
);
|
|
1107
1092
|
if (Object.isFrozen(next) && next.every(isTerminalReplicaRowDeeplyFrozen))
|
|
1108
1093
|
ROW_ARRAY_HASH_CACHE.set(next, { hash, length: next.length });
|