tmux-ide 2.9.2 → 2.9.4

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 (267) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +121 -140
  3. package/bin/cli.js +2457 -727
  4. package/package.json +23 -8
  5. package/packages/contracts/src/desktop-host.ts +3 -0
  6. package/packages/contracts/src/saved-machines.ts +19 -0
  7. package/packages/contracts/src/terminal-replica.ts +1 -1
  8. package/packages/core/package.json +3 -2
  9. package/packages/core/src/terminal-delivery.ts +51 -3
  10. package/packages/core/src/terminal-fnv64-wasm.ts +4 -3
  11. package/packages/core/src/terminal-replica-hash-cache.ts +129 -31
  12. package/packages/core/src/terminal-replica-owned-row.ts +27 -0
  13. package/packages/core/src/terminal-replica.ts +13 -28
  14. package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +96 -10
  15. package/packages/daemon/dist/command-center/resources/saved-machine-route.js +40 -1
  16. package/packages/daemon/dist/command-center/ws-events.js +5 -24
  17. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +8 -0
  18. package/packages/daemon/dist/lib/canonical-daemon.js +28 -2
  19. package/packages/daemon/dist/lib/daemon-embed.js +1 -1
  20. package/packages/daemon/dist/lib/daemon-service-manager.js +122 -0
  21. package/packages/daemon/dist/lib/daemon-service-plan.js +127 -0
  22. package/packages/daemon/dist/lib/daemon-service.js +259 -0
  23. package/packages/daemon/dist/lib/install-origin.js +2 -0
  24. package/packages/daemon/dist/lib/local-fleet-request.js +9 -3
  25. package/packages/daemon/dist/lib/log.js +6 -10
  26. package/packages/daemon/dist/lib/managed-installer-update.js +67 -0
  27. package/packages/daemon/dist/lib/{tui-download-lock.js → private-operation-lock.js} +11 -11
  28. package/packages/daemon/dist/lib/remote-daemon-info.js +3 -2
  29. package/packages/daemon/dist/lib/remote-daemon-lifecycle.js +4 -4
  30. package/packages/daemon/dist/lib/remote-tmux-command.js +10 -0
  31. package/packages/daemon/dist/lib/runtime-namespace.js +28 -16
  32. package/packages/daemon/dist/lib/saved-machines.js +40 -2
  33. package/packages/daemon/dist/lib/ssh-daemon-transport.js +24 -9
  34. package/packages/daemon/dist/lib/tmux-server-owner.js +1 -1
  35. package/packages/daemon/dist/lib/tmux-server-registration.js +21 -3
  36. package/packages/daemon/dist/lib/tui-binary.js +2 -2
  37. package/packages/daemon/dist/lib/update.js +18 -0
  38. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +24 -1
  39. package/packages/daemon/dist/machines.js +40 -4
  40. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  41. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  42. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  43. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  44. package/packages/daemon/dist/terminal/attachments/direct-websocket.js +4 -1
  45. package/packages/daemon/dist/terminal/attachments/native-runtime.js +22 -13
  46. package/packages/daemon/dist/terminal/mirror/mirror-service.js +101 -3
  47. package/packages/daemon/dist/terminal/mirror/registered-window-guard.js +62 -0
  48. package/packages/daemon/dist/terminal/mirror/session-channel.js +75 -4
  49. package/packages/daemon/dist/terminal/mirror/shared-window-index.js +45 -0
  50. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +11 -1
  51. package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +1 -0
  52. package/packages/daemon/dist/terminal/session-runtime/registry.js +56 -7
  53. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
  54. package/packages/daemon/dist/tui/integrations/claude-agent-teams.js +264 -0
  55. package/packages/daemon/dist/tui/mirror/dialog-model.js +2 -35
  56. package/packages/daemon/dist/tui/mirror/runtime/application-daemon-authority-owner.js +8 -0
  57. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +20 -2
  58. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +7 -1
  59. package/packages/daemon/dist/tui/mirror/runtime/application-machine-authority.js +59 -9
  60. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +27 -19
  61. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +23 -18
  62. package/packages/daemon/dist/tui/mirror/runtime/application-machine-startup.js +31 -2
  63. package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +7 -3
  64. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +3 -0
  65. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +146 -2
  66. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +201 -50
  67. package/packages/daemon/dist/tui/mirror/runtime/bounded-performance-record-writer.js +50 -10
  68. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -5
  69. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +3 -0
  70. package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +8 -2
  71. package/packages/daemon/dist/tui/mirror/terminal-text.js +35 -0
  72. package/packages/daemon/dist/tui/mirror/workspace/connection-feedback.js +5 -3
  73. package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +100 -10
  74. package/packages/daemon/src/command-center/resources/saved-machine-route.ts +48 -1
  75. package/packages/daemon/src/command-center/ws-events.ts +5 -25
  76. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +12 -0
  77. package/packages/daemon/src/lib/canonical-daemon.ts +44 -2
  78. package/packages/daemon/src/lib/daemon-embed.ts +1 -0
  79. package/packages/daemon/src/lib/daemon-service-manager.ts +152 -0
  80. package/packages/daemon/src/lib/daemon-service-plan.ts +153 -0
  81. package/packages/daemon/src/lib/daemon-service.ts +301 -0
  82. package/packages/daemon/src/lib/install-origin.ts +5 -0
  83. package/packages/daemon/src/lib/local-fleet-request.ts +19 -3
  84. package/packages/daemon/src/lib/log.ts +13 -9
  85. package/packages/daemon/src/lib/managed-installer-update.ts +79 -0
  86. package/packages/daemon/src/lib/{tui-download-lock.ts → private-operation-lock.ts} +15 -11
  87. package/packages/daemon/src/lib/remote-daemon-info.ts +3 -3
  88. package/packages/daemon/src/lib/remote-daemon-lifecycle.ts +4 -4
  89. package/packages/daemon/src/lib/remote-tmux-command.ts +12 -0
  90. package/packages/daemon/src/lib/runtime-namespace.ts +27 -18
  91. package/packages/daemon/src/lib/saved-machines.ts +40 -1
  92. package/packages/daemon/src/lib/ssh-daemon-transport.ts +25 -7
  93. package/packages/daemon/src/lib/tmux-server-owner.ts +1 -0
  94. package/packages/daemon/src/lib/tmux-server-registration.ts +21 -3
  95. package/packages/daemon/src/lib/tui-binary.ts +2 -2
  96. package/packages/daemon/src/lib/update.ts +24 -0
  97. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +22 -0
  98. package/packages/daemon/src/machines.ts +53 -5
  99. package/packages/daemon/src/terminal/attachments/direct-websocket.ts +8 -2
  100. package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
  101. package/packages/daemon/src/terminal/mirror/mirror-service.ts +105 -3
  102. package/packages/daemon/src/terminal/mirror/registered-window-guard.ts +66 -0
  103. package/packages/daemon/src/terminal/mirror/session-channel.ts +92 -4
  104. package/packages/daemon/src/terminal/mirror/shared-window-index.ts +42 -0
  105. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -1
  106. package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +2 -0
  107. package/packages/daemon/src/terminal/session-runtime/registry.ts +70 -8
  108. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +3 -1
  109. package/packages/daemon/src/tui/integrations/claude-agent-teams.ts +334 -0
  110. package/packages/daemon/src/tui/mirror/dialog-model.ts +2 -32
  111. package/packages/daemon/src/tui/mirror/runtime/application-daemon-authority-owner.ts +8 -0
  112. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +19 -2
  113. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +6 -1
  114. package/packages/daemon/src/tui/mirror/runtime/application-machine-authority.ts +60 -11
  115. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +39 -33
  116. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +31 -23
  117. package/packages/daemon/src/tui/mirror/runtime/application-machine-startup.ts +38 -2
  118. package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +7 -4
  119. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +3 -0
  120. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +1 -1
  121. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +47 -46
  122. package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +165 -2
  123. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +207 -53
  124. package/packages/daemon/src/tui/mirror/runtime/bounded-performance-record-writer.ts +49 -9
  125. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +6 -5
  126. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +4 -0
  127. package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +8 -3
  128. package/packages/daemon/src/tui/mirror/terminal-text.ts +32 -0
  129. package/packages/daemon/src/tui/mirror/workspace/connection-feedback.ts +6 -3
  130. package/packages/daemon-client/src/fleet-connection-status.ts +10 -1
  131. package/packages/daemon-client/src/workspace-client.ts +4 -1
  132. package/skill/SKILL.md +30 -30
  133. package/packages/contracts/src/__tests__/actions-contract.test.ts +0 -119
  134. package/packages/contracts/src/__tests__/agent-graph-overlay.test.ts +0 -428
  135. package/packages/contracts/src/__tests__/app-window-mutation.test.ts +0 -51
  136. package/packages/contracts/src/__tests__/app-window-state.test.ts +0 -34
  137. package/packages/contracts/src/__tests__/application-shell.test.ts +0 -645
  138. package/packages/contracts/src/__tests__/automation-operations.test.ts +0 -78
  139. package/packages/contracts/src/__tests__/causal-cell.test.ts +0 -47
  140. package/packages/contracts/src/__tests__/client-view-state.test.ts +0 -115
  141. package/packages/contracts/src/__tests__/cohesion-fixture.test.ts +0 -106
  142. package/packages/contracts/src/__tests__/commands.test.ts +0 -86
  143. package/packages/contracts/src/__tests__/control.test.ts +0 -154
  144. package/packages/contracts/src/__tests__/daemon-events.test.ts +0 -405
  145. package/packages/contracts/src/__tests__/daemon-resources.test.ts +0 -143
  146. package/packages/contracts/src/__tests__/daemon-wire.test.ts +0 -149
  147. package/packages/contracts/src/__tests__/desktop-host.test.ts +0 -269
  148. package/packages/contracts/src/__tests__/desktop-missions.test.ts +0 -215
  149. package/packages/contracts/src/__tests__/experience-conformance.test.ts +0 -153
  150. package/packages/contracts/src/__tests__/experience-kernel-boundary.test.ts +0 -113
  151. package/packages/contracts/src/__tests__/experience-shell.test.ts +0 -65
  152. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v2.json +0 -35
  153. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +0 -35
  154. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v4.json +0 -35
  155. package/packages/contracts/src/__tests__/fixtures/scoped-receipt.ts +0 -62
  156. package/packages/contracts/src/__tests__/fleet-agent-graph.test.ts +0 -231
  157. package/packages/contracts/src/__tests__/fleet-catalog.test.ts +0 -270
  158. package/packages/contracts/src/__tests__/fleet-lifecycle.test.ts +0 -62
  159. package/packages/contracts/src/__tests__/focus-overlay.test.ts +0 -110
  160. package/packages/contracts/src/__tests__/ide-config.test.ts +0 -64
  161. package/packages/contracts/src/__tests__/interaction-evidence.test.ts +0 -288
  162. package/packages/contracts/src/__tests__/interaction-receipt-v2.test.ts +0 -72
  163. package/packages/contracts/src/__tests__/issue-error.test.ts +0 -78
  164. package/packages/contracts/src/__tests__/mission-projections.test.ts +0 -186
  165. package/packages/contracts/src/__tests__/missions.test.ts +0 -272
  166. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +0 -242
  167. package/packages/contracts/src/__tests__/pane-appearance.test.ts +0 -146
  168. package/packages/contracts/src/__tests__/pane-read-result.test.ts +0 -55
  169. package/packages/contracts/src/__tests__/pane-stream.test.ts +0 -536
  170. package/packages/contracts/src/__tests__/pane-widget-marker.test.ts +0 -327
  171. package/packages/contracts/src/__tests__/performance-metrics.test.ts +0 -73
  172. package/packages/contracts/src/__tests__/performance-qualification.test.ts +0 -137
  173. package/packages/contracts/src/__tests__/rich-card-widget.test.ts +0 -29
  174. package/packages/contracts/src/__tests__/session-runtime.test.ts +0 -512
  175. package/packages/contracts/src/__tests__/startup-readiness.test.ts +0 -409
  176. package/packages/contracts/src/__tests__/terminal-attachment-stream.test.ts +0 -72
  177. package/packages/contracts/src/__tests__/terminal-attachments.test.ts +0 -329
  178. package/packages/contracts/src/__tests__/terminal-delivery.test.ts +0 -88
  179. package/packages/contracts/src/__tests__/terminal-runtime-inventory.test.ts +0 -68
  180. package/packages/contracts/src/__tests__/terminals.test.ts +0 -87
  181. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +0 -166
  182. package/packages/contracts/src/__tests__/transport-contract-loading.test.ts +0 -91
  183. package/packages/contracts/src/__tests__/visual-tokens.test.ts +0 -224
  184. package/packages/contracts/src/__tests__/window-links.test.ts +0 -243
  185. package/packages/contracts/src/__tests__/workspace-changes-resource.test.ts +0 -317
  186. package/packages/contracts/src/__tests__/workspace-changes-view.test.ts +0 -122
  187. package/packages/contracts/src/__tests__/workspace-config.test.ts +0 -294
  188. package/packages/contracts/src/__tests__/workspace-files-resource.test.ts +0 -261
  189. package/packages/contracts/src/__tests__/workspace-files-tree.test.ts +0 -147
  190. package/packages/contracts/src/__tests__/workspace-pane-creation.test.ts +0 -249
  191. package/packages/contracts/src/__tests__/workspace-promotion.test.ts +0 -94
  192. package/packages/contracts/src/__tests__/workspace-resource-identity.test.ts +0 -90
  193. package/packages/contracts/src/daemon-resource-request.test.ts +0 -192
  194. package/packages/contracts/src/native-operation-identity.test.ts +0 -23
  195. package/packages/contracts/src/workspace-admission.test.ts +0 -21
  196. package/packages/contracts/src/workspace-catalog-resource.test.ts +0 -146
  197. package/packages/core/src/agent-provisioning.test.ts +0 -42
  198. package/packages/core/src/application-shell-session.test.ts +0 -105
  199. package/packages/core/src/interaction-evidence.test.ts +0 -134
  200. package/packages/core/src/interaction-flow.test.ts +0 -66
  201. package/packages/core/src/interaction-receipts.test.ts +0 -671
  202. package/packages/core/src/navigator.test.ts +0 -34
  203. package/packages/core/src/optimistic-projection-conformance.test.ts +0 -75
  204. package/packages/core/src/optimistic-projection.test.ts +0 -118
  205. package/packages/core/src/performance-metrics.test.ts +0 -121
  206. package/packages/core/src/performance-qualification.test.ts +0 -283
  207. package/packages/core/src/saved-machines.test.ts +0 -80
  208. package/packages/core/src/terminal-compact-cold.test.ts +0 -87
  209. package/packages/core/src/terminal-conformance.test.ts +0 -48
  210. package/packages/core/src/terminal-delivery-cache-lifetime.test.ts +0 -167
  211. package/packages/core/src/terminal-delivery-encode-cooperative.test.ts +0 -140
  212. package/packages/core/src/terminal-delivery-representation-hash.test.ts +0 -77
  213. package/packages/core/src/terminal-delivery.test.ts +0 -1471
  214. package/packages/core/src/terminal-fnv64-wasm.test.ts +0 -61
  215. package/packages/core/src/terminal-replica-cold.test.ts +0 -31
  216. package/packages/core/src/terminal-replica-cooperative.test.ts +0 -170
  217. package/packages/core/src/terminal-replica-hash-cache.test.ts +0 -283
  218. package/packages/core/src/terminal-replica.test.ts +0 -347
  219. package/packages/core/src/workspace-selection.test.ts +0 -63
  220. package/packages/daemon/dist/__tests__/support.js +0 -33
  221. package/packages/daemon/dist/lib/__tests__/fixtures/workspace-state-writer.js +0 -58
  222. package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +0 -400
  223. package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +0 -182
  224. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +0 -98
  225. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +0 -156
  226. package/packages/daemon/src/tui/mirror/__snapshots__/activity-surface-renderer.test.tsx.snap +0 -42
  227. package/packages/daemon/src/tui/mirror/__snapshots__/changes-terminal-surface-renderer.test.tsx.snap +0 -267
  228. package/packages/daemon/src/tui/mirror/__snapshots__/home-files-surface-renderer.test.tsx.snap +0 -267
  229. package/packages/daemon/src/tui/mirror/__snapshots__/missions-surface-renderer.test.tsx.snap +0 -134
  230. package/packages/daemon/src/tui/mirror/__snapshots__/recipes-gallery-renderer.test.tsx.snap +0 -141
  231. package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +0 -134
  232. package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +0 -87
  233. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/agent-terminal-canvas-renderer.test.tsx.snap +0 -348
  234. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +0 -134
  235. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +0 -255
  236. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/pane-frame-renderer.test.tsx.snap +0 -525
  237. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +0 -31
  238. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/workbench-shell-renderer.test.tsx.snap +0 -134
  239. package/packages/daemon-client/src/application-shell-session.test.ts +0 -196
  240. package/packages/daemon-client/src/automation-client.test.ts +0 -214
  241. package/packages/daemon-client/src/bootstrap-coordinator.test.ts +0 -145
  242. package/packages/daemon-client/src/connection-supervisor.test.ts +0 -109
  243. package/packages/daemon-client/src/direct-application-shell-transport.test.ts +0 -941
  244. package/packages/daemon-client/src/first-latest-coordinator.test.ts +0 -175
  245. package/packages/daemon-client/src/fleet-dial-scheduler.test.ts +0 -114
  246. package/packages/daemon-client/src/fleet-environments.test.ts +0 -40
  247. package/packages/daemon-client/src/generation-bound-store-timers.test.ts +0 -74
  248. package/packages/daemon-client/src/owner-action-client.test.ts +0 -245
  249. package/packages/daemon-client/src/pane-stream-client.test.ts +0 -2841
  250. package/packages/daemon-client/src/pane-stream-clock-calibration.test.ts +0 -207
  251. package/packages/daemon-client/src/pane-stream-loopback-benchmark.test.ts +0 -51
  252. package/packages/daemon-client/src/push-resource-session.test.ts +0 -509
  253. package/packages/daemon-client/src/resource-replica.test.ts +0 -186
  254. package/packages/daemon-client/src/runtime-resource-ledger.test.ts +0 -18
  255. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +0 -114
  256. package/packages/daemon-client/src/terminal-fast-lane.test.ts +0 -835
  257. package/packages/daemon-client/src/tmux-server-client.test.ts +0 -279
  258. package/packages/daemon-client/src/tmux-server-interaction-events.test.ts +0 -230
  259. package/packages/daemon-client/src/workspace-catalog-v2.test.ts +0 -109
  260. package/packages/daemon-client/src/workspace-client-boundaries.test.ts +0 -59
  261. package/packages/daemon-client/src/workspace-client-operations.test.ts +0 -96
  262. package/packages/daemon-client/src/workspace-client.test.ts +0 -2912
  263. package/packages/daemon-client/src/workspace-event-supervisor.test.ts +0 -1086
  264. package/packages/daemon-client/src/workspace-pane-client.test.ts +0 -80
  265. package/packages/presentation/src/presentation-contract.test.ts +0 -63
  266. package/packages/tmux-bridge/src/runner.test.ts +0 -614
  267. package/packages/tmux-bridge/src/targeting.test.ts +0 -107
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tmux-ide",
3
- "version": "2.9.2",
4
- "description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
3
+ "version": "2.9.4",
4
+ "description": "The open-source workspace for coding agents: run Claude Code, Codex and other agents in tmux with live status",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "tmux-ide": "bin/cli.js"
@@ -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,9 @@
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
+ "demo:font": "node docs/scripts/tui-demo-font/build.mjs",
76
+ "readme:media": "node docs/scripts/readme-media.mjs",
77
+ "test:tui-renderer": "bun test --preload @opentui/solid/preload --preload ./packages/daemon/test-support/opentui-renderer-preload.ts ./packages/daemon/src/tui/mirror/automatic-contrast-renderer.test.tsx ./packages/daemon/src/tui/mirror/pane-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/native-grid-projection-renderer.test.tsx ./packages/daemon/src/tui/mirror/widget-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/missions-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/recipes-gallery-renderer.test.tsx ./packages/daemon/src/tui/mirror/shell-chrome-renderer.test.tsx ./packages/daemon/src/tui/mirror/sidebar-renderer.test.tsx ./packages/daemon/src/tui/mirror/home-files-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/changes-terminal-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/activity-surface-renderer.test.tsx ./packages/daemon/src/tui/mirror/features/files/session-renderer.test.tsx ./packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-renderer.test.tsx ./packages/daemon/src/tui/mirror/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
78
  "test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
69
79
  "test:tui-live": "node scripts/tui-testdrive.mjs smoke",
70
80
  "test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
@@ -104,7 +114,11 @@
104
114
  },
105
115
  "keywords": [
106
116
  "tmux",
107
- "ide",
117
+ "coding-agents",
118
+ "claude-code",
119
+ "codex",
120
+ "ai-agents",
121
+ "agent-teams",
108
122
  "terminal",
109
123
  "workspace",
110
124
  "developer-tools"
@@ -116,7 +130,7 @@
116
130
  "type": "git",
117
131
  "url": "git+https://github.com/wavyrai/tmux-ide.git"
118
132
  },
119
- "homepage": "https://github.com/wavyrai/tmux-ide#readme",
133
+ "homepage": "https://tmux-ide.com",
120
134
  "bugs": {
121
135
  "url": "https://github.com/wavyrai/tmux-ide/issues"
122
136
  },
@@ -139,6 +153,7 @@
139
153
  "@eslint/js": "^10.0.1",
140
154
  "@opentui/core": "^0.5.1",
141
155
  "@opentui/solid": "^0.5.1",
156
+ "@tmux-ide/contracts": "workspace:*",
142
157
  "@tsconfig/bun": "^1.0.10",
143
158
  "@types/node": "^25.5.0",
144
159
  "@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
+ }