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.
Files changed (263) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +2071 -625
  3. package/package.json +14 -5
  4. package/packages/contracts/src/desktop-host.ts +3 -0
  5. package/packages/contracts/src/saved-machines.ts +19 -0
  6. package/packages/contracts/src/terminal-replica.ts +1 -1
  7. package/packages/core/package.json +3 -2
  8. package/packages/core/src/terminal-delivery.ts +51 -3
  9. package/packages/core/src/terminal-fnv64-wasm.ts +4 -3
  10. package/packages/core/src/terminal-replica-hash-cache.ts +129 -31
  11. package/packages/core/src/terminal-replica-owned-row.ts +27 -0
  12. package/packages/core/src/terminal-replica.ts +13 -28
  13. package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +96 -10
  14. package/packages/daemon/dist/command-center/resources/saved-machine-route.js +40 -1
  15. package/packages/daemon/dist/command-center/ws-events.js +5 -24
  16. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +8 -0
  17. package/packages/daemon/dist/lib/canonical-daemon.js +28 -2
  18. package/packages/daemon/dist/lib/daemon-embed.js +1 -1
  19. package/packages/daemon/dist/lib/daemon-service-manager.js +122 -0
  20. package/packages/daemon/dist/lib/daemon-service-plan.js +127 -0
  21. package/packages/daemon/dist/lib/daemon-service.js +259 -0
  22. package/packages/daemon/dist/lib/install-origin.js +2 -0
  23. package/packages/daemon/dist/lib/local-fleet-request.js +9 -3
  24. package/packages/daemon/dist/lib/log.js +6 -10
  25. package/packages/daemon/dist/lib/managed-installer-update.js +67 -0
  26. package/packages/daemon/dist/lib/{tui-download-lock.js → private-operation-lock.js} +11 -11
  27. package/packages/daemon/dist/lib/remote-daemon-info.js +3 -2
  28. package/packages/daemon/dist/lib/remote-daemon-lifecycle.js +4 -4
  29. package/packages/daemon/dist/lib/remote-tmux-command.js +10 -0
  30. package/packages/daemon/dist/lib/runtime-namespace.js +28 -16
  31. package/packages/daemon/dist/lib/saved-machines.js +40 -2
  32. package/packages/daemon/dist/lib/ssh-daemon-transport.js +24 -9
  33. package/packages/daemon/dist/lib/tmux-server-owner.js +1 -1
  34. package/packages/daemon/dist/lib/tmux-server-registration.js +21 -3
  35. package/packages/daemon/dist/lib/tui-binary.js +2 -2
  36. package/packages/daemon/dist/lib/update.js +18 -0
  37. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +24 -1
  38. package/packages/daemon/dist/machines.js +40 -4
  39. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  40. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  41. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  42. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  43. package/packages/daemon/dist/terminal/attachments/direct-websocket.js +4 -1
  44. package/packages/daemon/dist/terminal/attachments/native-runtime.js +22 -13
  45. package/packages/daemon/dist/terminal/mirror/mirror-service.js +101 -3
  46. package/packages/daemon/dist/terminal/mirror/registered-window-guard.js +62 -0
  47. package/packages/daemon/dist/terminal/mirror/session-channel.js +75 -4
  48. package/packages/daemon/dist/terminal/mirror/shared-window-index.js +45 -0
  49. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +11 -1
  50. package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +1 -0
  51. package/packages/daemon/dist/terminal/session-runtime/registry.js +56 -7
  52. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
  53. package/packages/daemon/dist/tui/mirror/dialog-model.js +2 -35
  54. package/packages/daemon/dist/tui/mirror/runtime/application-daemon-authority-owner.js +8 -0
  55. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +20 -2
  56. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +7 -1
  57. package/packages/daemon/dist/tui/mirror/runtime/application-machine-authority.js +59 -9
  58. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +27 -19
  59. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +23 -18
  60. package/packages/daemon/dist/tui/mirror/runtime/application-machine-startup.js +31 -2
  61. package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +7 -3
  62. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +3 -0
  63. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +146 -2
  64. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +201 -50
  65. package/packages/daemon/dist/tui/mirror/runtime/bounded-performance-record-writer.js +50 -10
  66. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -5
  67. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +3 -0
  68. package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +8 -2
  69. package/packages/daemon/dist/tui/mirror/terminal-text.js +35 -0
  70. package/packages/daemon/dist/tui/mirror/workspace/connection-feedback.js +5 -3
  71. package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +100 -10
  72. package/packages/daemon/src/command-center/resources/saved-machine-route.ts +48 -1
  73. package/packages/daemon/src/command-center/ws-events.ts +5 -25
  74. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +12 -0
  75. package/packages/daemon/src/lib/canonical-daemon.ts +44 -2
  76. package/packages/daemon/src/lib/daemon-embed.ts +1 -0
  77. package/packages/daemon/src/lib/daemon-service-manager.ts +152 -0
  78. package/packages/daemon/src/lib/daemon-service-plan.ts +153 -0
  79. package/packages/daemon/src/lib/daemon-service.ts +301 -0
  80. package/packages/daemon/src/lib/install-origin.ts +5 -0
  81. package/packages/daemon/src/lib/local-fleet-request.ts +19 -3
  82. package/packages/daemon/src/lib/log.ts +13 -9
  83. package/packages/daemon/src/lib/managed-installer-update.ts +79 -0
  84. package/packages/daemon/src/lib/{tui-download-lock.ts → private-operation-lock.ts} +15 -11
  85. package/packages/daemon/src/lib/remote-daemon-info.ts +3 -3
  86. package/packages/daemon/src/lib/remote-daemon-lifecycle.ts +4 -4
  87. package/packages/daemon/src/lib/remote-tmux-command.ts +12 -0
  88. package/packages/daemon/src/lib/runtime-namespace.ts +27 -18
  89. package/packages/daemon/src/lib/saved-machines.ts +40 -1
  90. package/packages/daemon/src/lib/ssh-daemon-transport.ts +25 -7
  91. package/packages/daemon/src/lib/tmux-server-owner.ts +1 -0
  92. package/packages/daemon/src/lib/tmux-server-registration.ts +21 -3
  93. package/packages/daemon/src/lib/tui-binary.ts +2 -2
  94. package/packages/daemon/src/lib/update.ts +24 -0
  95. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +22 -0
  96. package/packages/daemon/src/machines.ts +53 -5
  97. package/packages/daemon/src/terminal/attachments/direct-websocket.ts +8 -2
  98. package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
  99. package/packages/daemon/src/terminal/mirror/mirror-service.ts +105 -3
  100. package/packages/daemon/src/terminal/mirror/registered-window-guard.ts +66 -0
  101. package/packages/daemon/src/terminal/mirror/session-channel.ts +92 -4
  102. package/packages/daemon/src/terminal/mirror/shared-window-index.ts +42 -0
  103. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -1
  104. package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +2 -0
  105. package/packages/daemon/src/terminal/session-runtime/registry.ts +70 -8
  106. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +3 -1
  107. package/packages/daemon/src/tui/mirror/dialog-model.ts +2 -32
  108. package/packages/daemon/src/tui/mirror/runtime/application-daemon-authority-owner.ts +8 -0
  109. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +19 -2
  110. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +6 -1
  111. package/packages/daemon/src/tui/mirror/runtime/application-machine-authority.ts +60 -11
  112. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +39 -33
  113. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +31 -23
  114. package/packages/daemon/src/tui/mirror/runtime/application-machine-startup.ts +38 -2
  115. package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +7 -4
  116. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +3 -0
  117. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +1 -1
  118. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +47 -46
  119. package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +165 -2
  120. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +207 -53
  121. package/packages/daemon/src/tui/mirror/runtime/bounded-performance-record-writer.ts +49 -9
  122. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +6 -5
  123. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +4 -0
  124. package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +8 -3
  125. package/packages/daemon/src/tui/mirror/terminal-text.ts +32 -0
  126. package/packages/daemon/src/tui/mirror/workspace/connection-feedback.ts +6 -3
  127. package/packages/daemon-client/src/fleet-connection-status.ts +10 -1
  128. package/packages/daemon-client/src/workspace-client.ts +4 -1
  129. package/packages/contracts/src/__tests__/actions-contract.test.ts +0 -119
  130. package/packages/contracts/src/__tests__/agent-graph-overlay.test.ts +0 -428
  131. package/packages/contracts/src/__tests__/app-window-mutation.test.ts +0 -51
  132. package/packages/contracts/src/__tests__/app-window-state.test.ts +0 -34
  133. package/packages/contracts/src/__tests__/application-shell.test.ts +0 -645
  134. package/packages/contracts/src/__tests__/automation-operations.test.ts +0 -78
  135. package/packages/contracts/src/__tests__/causal-cell.test.ts +0 -47
  136. package/packages/contracts/src/__tests__/client-view-state.test.ts +0 -115
  137. package/packages/contracts/src/__tests__/cohesion-fixture.test.ts +0 -106
  138. package/packages/contracts/src/__tests__/commands.test.ts +0 -86
  139. package/packages/contracts/src/__tests__/control.test.ts +0 -154
  140. package/packages/contracts/src/__tests__/daemon-events.test.ts +0 -405
  141. package/packages/contracts/src/__tests__/daemon-resources.test.ts +0 -143
  142. package/packages/contracts/src/__tests__/daemon-wire.test.ts +0 -149
  143. package/packages/contracts/src/__tests__/desktop-host.test.ts +0 -269
  144. package/packages/contracts/src/__tests__/desktop-missions.test.ts +0 -215
  145. package/packages/contracts/src/__tests__/experience-conformance.test.ts +0 -153
  146. package/packages/contracts/src/__tests__/experience-kernel-boundary.test.ts +0 -113
  147. package/packages/contracts/src/__tests__/experience-shell.test.ts +0 -65
  148. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v2.json +0 -35
  149. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +0 -35
  150. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v4.json +0 -35
  151. package/packages/contracts/src/__tests__/fixtures/scoped-receipt.ts +0 -62
  152. package/packages/contracts/src/__tests__/fleet-agent-graph.test.ts +0 -231
  153. package/packages/contracts/src/__tests__/fleet-catalog.test.ts +0 -270
  154. package/packages/contracts/src/__tests__/fleet-lifecycle.test.ts +0 -62
  155. package/packages/contracts/src/__tests__/focus-overlay.test.ts +0 -110
  156. package/packages/contracts/src/__tests__/ide-config.test.ts +0 -64
  157. package/packages/contracts/src/__tests__/interaction-evidence.test.ts +0 -288
  158. package/packages/contracts/src/__tests__/interaction-receipt-v2.test.ts +0 -72
  159. package/packages/contracts/src/__tests__/issue-error.test.ts +0 -78
  160. package/packages/contracts/src/__tests__/mission-projections.test.ts +0 -186
  161. package/packages/contracts/src/__tests__/missions.test.ts +0 -272
  162. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +0 -242
  163. package/packages/contracts/src/__tests__/pane-appearance.test.ts +0 -146
  164. package/packages/contracts/src/__tests__/pane-read-result.test.ts +0 -55
  165. package/packages/contracts/src/__tests__/pane-stream.test.ts +0 -536
  166. package/packages/contracts/src/__tests__/pane-widget-marker.test.ts +0 -327
  167. package/packages/contracts/src/__tests__/performance-metrics.test.ts +0 -73
  168. package/packages/contracts/src/__tests__/performance-qualification.test.ts +0 -137
  169. package/packages/contracts/src/__tests__/rich-card-widget.test.ts +0 -29
  170. package/packages/contracts/src/__tests__/session-runtime.test.ts +0 -512
  171. package/packages/contracts/src/__tests__/startup-readiness.test.ts +0 -409
  172. package/packages/contracts/src/__tests__/terminal-attachment-stream.test.ts +0 -72
  173. package/packages/contracts/src/__tests__/terminal-attachments.test.ts +0 -329
  174. package/packages/contracts/src/__tests__/terminal-delivery.test.ts +0 -88
  175. package/packages/contracts/src/__tests__/terminal-runtime-inventory.test.ts +0 -68
  176. package/packages/contracts/src/__tests__/terminals.test.ts +0 -87
  177. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +0 -166
  178. package/packages/contracts/src/__tests__/transport-contract-loading.test.ts +0 -91
  179. package/packages/contracts/src/__tests__/visual-tokens.test.ts +0 -224
  180. package/packages/contracts/src/__tests__/window-links.test.ts +0 -243
  181. package/packages/contracts/src/__tests__/workspace-changes-resource.test.ts +0 -317
  182. package/packages/contracts/src/__tests__/workspace-changes-view.test.ts +0 -122
  183. package/packages/contracts/src/__tests__/workspace-config.test.ts +0 -294
  184. package/packages/contracts/src/__tests__/workspace-files-resource.test.ts +0 -261
  185. package/packages/contracts/src/__tests__/workspace-files-tree.test.ts +0 -147
  186. package/packages/contracts/src/__tests__/workspace-pane-creation.test.ts +0 -249
  187. package/packages/contracts/src/__tests__/workspace-promotion.test.ts +0 -94
  188. package/packages/contracts/src/__tests__/workspace-resource-identity.test.ts +0 -90
  189. package/packages/contracts/src/daemon-resource-request.test.ts +0 -192
  190. package/packages/contracts/src/native-operation-identity.test.ts +0 -23
  191. package/packages/contracts/src/workspace-admission.test.ts +0 -21
  192. package/packages/contracts/src/workspace-catalog-resource.test.ts +0 -146
  193. package/packages/core/src/agent-provisioning.test.ts +0 -42
  194. package/packages/core/src/application-shell-session.test.ts +0 -105
  195. package/packages/core/src/interaction-evidence.test.ts +0 -134
  196. package/packages/core/src/interaction-flow.test.ts +0 -66
  197. package/packages/core/src/interaction-receipts.test.ts +0 -671
  198. package/packages/core/src/navigator.test.ts +0 -34
  199. package/packages/core/src/optimistic-projection-conformance.test.ts +0 -75
  200. package/packages/core/src/optimistic-projection.test.ts +0 -118
  201. package/packages/core/src/performance-metrics.test.ts +0 -121
  202. package/packages/core/src/performance-qualification.test.ts +0 -283
  203. package/packages/core/src/saved-machines.test.ts +0 -80
  204. package/packages/core/src/terminal-compact-cold.test.ts +0 -87
  205. package/packages/core/src/terminal-conformance.test.ts +0 -48
  206. package/packages/core/src/terminal-delivery-cache-lifetime.test.ts +0 -167
  207. package/packages/core/src/terminal-delivery-encode-cooperative.test.ts +0 -140
  208. package/packages/core/src/terminal-delivery-representation-hash.test.ts +0 -77
  209. package/packages/core/src/terminal-delivery.test.ts +0 -1471
  210. package/packages/core/src/terminal-fnv64-wasm.test.ts +0 -61
  211. package/packages/core/src/terminal-replica-cold.test.ts +0 -31
  212. package/packages/core/src/terminal-replica-cooperative.test.ts +0 -170
  213. package/packages/core/src/terminal-replica-hash-cache.test.ts +0 -283
  214. package/packages/core/src/terminal-replica.test.ts +0 -347
  215. package/packages/core/src/workspace-selection.test.ts +0 -63
  216. package/packages/daemon/dist/__tests__/support.js +0 -33
  217. package/packages/daemon/dist/lib/__tests__/fixtures/workspace-state-writer.js +0 -58
  218. package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +0 -400
  219. package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +0 -182
  220. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +0 -98
  221. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +0 -156
  222. package/packages/daemon/src/tui/mirror/__snapshots__/activity-surface-renderer.test.tsx.snap +0 -42
  223. package/packages/daemon/src/tui/mirror/__snapshots__/changes-terminal-surface-renderer.test.tsx.snap +0 -267
  224. package/packages/daemon/src/tui/mirror/__snapshots__/home-files-surface-renderer.test.tsx.snap +0 -267
  225. package/packages/daemon/src/tui/mirror/__snapshots__/missions-surface-renderer.test.tsx.snap +0 -134
  226. package/packages/daemon/src/tui/mirror/__snapshots__/recipes-gallery-renderer.test.tsx.snap +0 -141
  227. package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +0 -134
  228. package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +0 -87
  229. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/agent-terminal-canvas-renderer.test.tsx.snap +0 -348
  230. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +0 -134
  231. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +0 -255
  232. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/pane-frame-renderer.test.tsx.snap +0 -525
  233. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +0 -31
  234. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/workbench-shell-renderer.test.tsx.snap +0 -134
  235. package/packages/daemon-client/src/application-shell-session.test.ts +0 -196
  236. package/packages/daemon-client/src/automation-client.test.ts +0 -214
  237. package/packages/daemon-client/src/bootstrap-coordinator.test.ts +0 -145
  238. package/packages/daemon-client/src/connection-supervisor.test.ts +0 -109
  239. package/packages/daemon-client/src/direct-application-shell-transport.test.ts +0 -941
  240. package/packages/daemon-client/src/first-latest-coordinator.test.ts +0 -175
  241. package/packages/daemon-client/src/fleet-dial-scheduler.test.ts +0 -114
  242. package/packages/daemon-client/src/fleet-environments.test.ts +0 -40
  243. package/packages/daemon-client/src/generation-bound-store-timers.test.ts +0 -74
  244. package/packages/daemon-client/src/owner-action-client.test.ts +0 -245
  245. package/packages/daemon-client/src/pane-stream-client.test.ts +0 -2841
  246. package/packages/daemon-client/src/pane-stream-clock-calibration.test.ts +0 -207
  247. package/packages/daemon-client/src/pane-stream-loopback-benchmark.test.ts +0 -51
  248. package/packages/daemon-client/src/push-resource-session.test.ts +0 -509
  249. package/packages/daemon-client/src/resource-replica.test.ts +0 -186
  250. package/packages/daemon-client/src/runtime-resource-ledger.test.ts +0 -18
  251. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +0 -114
  252. package/packages/daemon-client/src/terminal-fast-lane.test.ts +0 -835
  253. package/packages/daemon-client/src/tmux-server-client.test.ts +0 -279
  254. package/packages/daemon-client/src/tmux-server-interaction-events.test.ts +0 -230
  255. package/packages/daemon-client/src/workspace-catalog-v2.test.ts +0 -109
  256. package/packages/daemon-client/src/workspace-client-boundaries.test.ts +0 -59
  257. package/packages/daemon-client/src/workspace-client-operations.test.ts +0 -96
  258. package/packages/daemon-client/src/workspace-client.test.ts +0 -2912
  259. package/packages/daemon-client/src/workspace-event-supervisor.test.ts +0 -1086
  260. package/packages/daemon-client/src/workspace-pane-client.test.ts +0 -80
  261. package/packages/presentation/src/presentation-contract.test.ts +0 -63
  262. package/packages/tmux-bridge/src/runner.test.ts +0 -614
  263. 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.2",
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 -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/web-workspace --filter @tmux-ide/electron-shell run test",
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/web-workspace --filter @tmux-ide/electron-shell run test",
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",
@@ -691,6 +691,9 @@ export const DesktopEnvironmentSummarySchemaZ = z
691
691
  "incompatible",
692
692
  "daemon-missing",
693
693
  "identity-mismatch",
694
+ "ssh-authentication",
695
+ "ssh-host-key",
696
+ "remote-cli-missing",
694
697
  ])
695
698
  .nullable(),
696
699
  })
@@ -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.union([z.literal(0), z.literal(1), z.literal(2)]),
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 = TerminalSemanticDeliveryPayloadSchemaZ.parse(input);
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 { cells, ...rowHeaderInput } = rows[index]!;
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 validated = CompactCellSliceSchema.parse(cells.slice(offset, offset + 256));
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
- * Allocation-free FNV-1a64 writer for the canonical terminal encoding.
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
- for (const byte of value) this.#byte(byte);
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.#byte(0x3b);
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
- for (const byte of bytes) this.#byte(byte);
65
- this.#byte(0x3b);
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 = Object.keys(record).sort();
164
+ const { sorted: keys, tokens } = this.keyOrder(record);
103
165
  this.ascii(`o${keys.length}:`);
104
- for (const key of keys) {
105
- this.string(key);
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 = Object.keys(record).sort();
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 backgrounds = foregrounds.get(foregroundKey) ?? new Map();
248
- foregrounds.set(foregroundKey, backgrounds);
249
- const attributes = backgrounds.get(backgroundKey) ?? new Map();
250
- backgrounds.set(backgroundKey, attributes);
251
- const cached = attributes.get(cell.attributes);
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
- attributes.set(cell.attributes, prepared);
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(row: TerminalReplicaRow, onMiss?: () => void): string {
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
- hash.ascii("a5:");
286
- hash.string(cell.grapheme);
287
- hash.number(cell.width);
288
- writeColor(hash, cell.foreground);
289
- writeColor(hash, cell.background);
290
- hash.number(cell.attributes);
291
- hash.ascii(";");
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
- const frozen = Object.freeze({
874
- wrapped: row.wrapped,
875
- cells: Object.freeze(cells),
876
- }) as unknown as TerminalReplicaRow;
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 + BigInt(`0x${hashTerminalReplicaRow(row)}`),
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 });