tmux-ide 2.9.0-beta.21 → 2.9.0-beta.26

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 (209) hide show
  1. package/bin/cli.js +46791 -43589
  2. package/package.json +2 -2
  3. package/packages/contracts/src/__tests__/daemon-wire.test.ts +2 -2
  4. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +35 -0
  5. package/packages/contracts/src/__tests__/issue-error.test.ts +1 -1
  6. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +3 -1
  7. package/packages/contracts/src/__tests__/pane-stream.test.ts +17 -5
  8. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +166 -0
  9. package/packages/contracts/src/__tests__/window-links.test.ts +243 -0
  10. package/packages/contracts/src/actions-contract.ts +12 -0
  11. package/packages/contracts/src/daemon-resource-request.test.ts +1 -1
  12. package/packages/contracts/src/daemon-wire.ts +10 -5
  13. package/packages/contracts/src/fleet-client-state.ts +3 -0
  14. package/packages/contracts/src/fleet-lifecycle.ts +5 -0
  15. package/packages/contracts/src/index.ts +2 -0
  16. package/packages/contracts/src/interaction-receipts.ts +39 -0
  17. package/packages/contracts/src/issue-error.ts +1 -1
  18. package/packages/contracts/src/multiplexer-verbs.ts +25 -1
  19. package/packages/contracts/src/pane-stream.ts +51 -8
  20. package/packages/contracts/src/tmux-server-scope.ts +172 -0
  21. package/packages/contracts/src/window-links.ts +76 -0
  22. package/packages/contracts/src/workspace-multiplexer.ts +54 -0
  23. package/packages/core/src/interaction-receipts.ts +4 -0
  24. package/packages/daemon/dist/command-center/actions/command-definitions.js +6 -0
  25. package/packages/daemon/dist/command-center/actions/handlers/workspace-multiplexer.js +2 -0
  26. package/packages/daemon/dist/command-center/actions/registry.js +13 -1
  27. package/packages/daemon/dist/command-center/discovery.js +2 -9
  28. package/packages/daemon/dist/command-center/server.js +21 -1
  29. package/packages/daemon/dist/command-center/tmux-server-native-backing.js +37 -0
  30. package/packages/daemon/dist/command-center/tmux-servers.js +351 -0
  31. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +64 -8
  32. package/packages/daemon/dist/lib/canonical-daemon.js +5 -2
  33. package/packages/daemon/dist/lib/daemon-embed.js +198 -30
  34. package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +147 -0
  35. package/packages/daemon/dist/lib/fleet-lifecycle-authority.js +31 -11
  36. package/packages/daemon/dist/lib/headless-daemon.js +10 -1
  37. package/packages/daemon/dist/lib/runtime-namespace.js +17 -3
  38. package/packages/daemon/dist/lib/tmux-client-execution.js +59 -0
  39. package/packages/daemon/dist/lib/tmux-named-socket-fence.js +4 -0
  40. package/packages/daemon/dist/lib/tmux-server-generation-runner.js +62 -0
  41. package/packages/daemon/dist/lib/tmux-server-owner.js +285 -0
  42. package/packages/daemon/dist/lib/tmux-server-owners.js +192 -0
  43. package/packages/daemon/dist/lib/tmux-server-proof.js +82 -0
  44. package/packages/daemon/dist/lib/tmux-server-registration.js +85 -0
  45. package/packages/daemon/dist/lib/tmux-server-session-create.js +43 -0
  46. package/packages/daemon/dist/lib/tmux-server-session-open.js +101 -0
  47. package/packages/daemon/dist/lib/tmux-servers-cli.js +134 -0
  48. package/packages/daemon/dist/lib/tmux-session-mutation-fence.js +55 -0
  49. package/packages/daemon/dist/lib/tmux-window-link-guard.js +88 -0
  50. package/packages/daemon/dist/lib/workspace-config-loader.js +3 -0
  51. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +82 -1
  52. package/packages/daemon/dist/lib/workspace-pane-creation.js +13 -57
  53. package/packages/daemon/dist/lib/workspace-promotion.js +91 -12
  54. package/packages/daemon/dist/lib/workspace-registry.js +4 -0
  55. package/packages/daemon/dist/server/pane-stream-upgrade.js +20 -6
  56. package/packages/daemon/dist/terminal/attachments/native-runtime.js +61 -17
  57. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +9 -1
  58. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +11 -2
  59. package/packages/daemon/dist/terminal/mirror/control-channel.js +65 -6
  60. package/packages/daemon/dist/terminal/mirror/mirror-service.js +44 -0
  61. package/packages/daemon/dist/terminal/mirror/session-channel.js +210 -50
  62. package/packages/daemon/dist/terminal/mirror/window-link-authority.js +160 -0
  63. package/packages/daemon/dist/terminal/pane-stream/lease-manager.js +3 -3
  64. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +40 -6
  65. package/packages/daemon/dist/terminal/protocol/live-session-identity.js +9 -0
  66. package/packages/daemon/dist/terminal/protocol/native-backing-client.js +2 -1
  67. package/packages/daemon/dist/terminal/session-runtime/interaction-receipt-facts.js +9 -0
  68. package/packages/daemon/dist/terminal/session-runtime/registry.js +6 -0
  69. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +1 -1
  70. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-resource-changes.js +1 -0
  71. package/packages/daemon/dist/tui/mirror/application-shell-daemon-connection.js +7 -2
  72. package/packages/daemon/dist/tui/mirror/application-shell-server-connection.js +114 -0
  73. package/packages/daemon/dist/tui/mirror/menu-model.js +1 -1
  74. package/packages/daemon/dist/tui/mirror/open-tui-verified-routing.js +34 -4
  75. package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +29 -8
  76. package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-integration.jsx +119 -0
  77. package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-owner.js +132 -0
  78. package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +21 -9
  79. package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-transport.js +63 -1
  80. package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +8 -2
  81. package/packages/daemon/dist/tui/mirror/runtime/application-home-agents.js +3 -1
  82. package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog-owner.js +11 -5
  83. package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog.js +140 -75
  84. package/packages/daemon/dist/tui/mirror/runtime/application-home-experience.js +40 -0
  85. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +132 -0
  86. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +9 -0
  87. package/packages/daemon/dist/tui/mirror/runtime/application-machine-catalog.js +12 -2
  88. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +211 -31
  89. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +91 -37
  90. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +20 -7
  91. package/packages/daemon/dist/tui/mirror/runtime/application-root-configuration.js +9 -3
  92. package/packages/daemon/dist/tui/mirror/runtime/application-route-connection.js +95 -2
  93. package/packages/daemon/dist/tui/mirror/runtime/application-shell-home.jsx +6 -2
  94. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +158 -2
  95. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace-policy.js +43 -0
  96. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +78 -25
  97. package/packages/daemon/dist/tui/mirror/runtime/application-theme-repaint.js +9 -0
  98. package/packages/daemon/dist/tui/mirror/runtime/fleet-lifecycle-client.js +44 -1
  99. package/packages/daemon/dist/tui/mirror/runtime/guided-tour-coach.jsx +54 -0
  100. package/packages/daemon/dist/tui/mirror/runtime/guided-tour-storage.js +67 -0
  101. package/packages/daemon/dist/tui/mirror/runtime/guided-tour.js +113 -0
  102. package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +1 -1
  103. package/packages/daemon/dist/tui/mirror/runtime/resize-terminal-pane.js +4 -0
  104. package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +2 -1
  105. package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +12 -4
  106. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +6 -3
  107. package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +11 -2
  108. package/packages/daemon/dist/tui/mirror/workspace/terminal-window-strip.jsx +2 -1
  109. package/packages/daemon/src/command-center/actions/command-definitions.ts +6 -0
  110. package/packages/daemon/src/command-center/actions/errors.ts +4 -0
  111. package/packages/daemon/src/command-center/actions/handlers/workspace-multiplexer.ts +2 -0
  112. package/packages/daemon/src/command-center/actions/registry.ts +14 -0
  113. package/packages/daemon/src/command-center/discovery.ts +2 -10
  114. package/packages/daemon/src/command-center/server.ts +29 -1
  115. package/packages/daemon/src/command-center/tmux-server-native-backing.ts +49 -0
  116. package/packages/daemon/src/command-center/tmux-servers.ts +479 -0
  117. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +111 -4
  118. package/packages/daemon/src/lib/canonical-daemon.ts +15 -3
  119. package/packages/daemon/src/lib/daemon-embed.ts +241 -28
  120. package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +190 -0
  121. package/packages/daemon/src/lib/fleet-lifecycle-authority.ts +33 -9
  122. package/packages/daemon/src/lib/headless-daemon.ts +18 -1
  123. package/packages/daemon/src/lib/runtime-namespace.ts +24 -3
  124. package/packages/daemon/src/lib/tmux-client-execution.ts +56 -0
  125. package/packages/daemon/src/lib/tmux-named-socket-fence.ts +5 -0
  126. package/packages/daemon/src/lib/tmux-server-generation-runner.ts +81 -0
  127. package/packages/daemon/src/lib/tmux-server-owner.ts +336 -0
  128. package/packages/daemon/src/lib/tmux-server-owners.ts +228 -0
  129. package/packages/daemon/src/lib/tmux-server-proof.ts +95 -0
  130. package/packages/daemon/src/lib/tmux-server-registration.ts +105 -0
  131. package/packages/daemon/src/lib/tmux-server-session-create.ts +52 -0
  132. package/packages/daemon/src/lib/tmux-server-session-open.ts +106 -0
  133. package/packages/daemon/src/lib/tmux-servers-cli.ts +186 -0
  134. package/packages/daemon/src/lib/tmux-session-mutation-fence.ts +62 -0
  135. package/packages/daemon/src/lib/tmux-window-link-guard.ts +112 -0
  136. package/packages/daemon/src/lib/workspace-config-loader.ts +3 -0
  137. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +121 -1
  138. package/packages/daemon/src/lib/workspace-pane-creation.ts +16 -55
  139. package/packages/daemon/src/lib/workspace-promotion.ts +98 -12
  140. package/packages/daemon/src/lib/workspace-registry.ts +5 -0
  141. package/packages/daemon/src/server/pane-stream-upgrade.ts +25 -5
  142. package/packages/daemon/src/terminal/attachments/native-runtime.ts +70 -16
  143. package/packages/daemon/src/terminal/mirror/control-channel.ts +64 -7
  144. package/packages/daemon/src/terminal/mirror/events.ts +2 -0
  145. package/packages/daemon/src/terminal/mirror/mirror-service.ts +56 -0
  146. package/packages/daemon/src/terminal/mirror/session-channel.ts +245 -50
  147. package/packages/daemon/src/terminal/mirror/window-link-authority.ts +203 -0
  148. package/packages/daemon/src/terminal/pane-stream/lease-manager.ts +3 -3
  149. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +50 -6
  150. package/packages/daemon/src/terminal/protocol/live-session-identity.ts +14 -0
  151. package/packages/daemon/src/terminal/protocol/native-backing-client.ts +4 -1
  152. package/packages/daemon/src/terminal/session-runtime/interaction-receipt-facts.ts +9 -0
  153. package/packages/daemon/src/terminal/session-runtime/registry.ts +9 -0
  154. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +2 -2
  155. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-resource-changes.ts +1 -0
  156. package/packages/daemon/src/tui/mirror/application-shell-daemon-connection.ts +28 -2
  157. package/packages/daemon/src/tui/mirror/application-shell-server-connection.ts +154 -0
  158. package/packages/daemon/src/tui/mirror/menu-model.ts +1 -1
  159. package/packages/daemon/src/tui/mirror/open-tui-verified-routing.ts +41 -3
  160. package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +47 -6
  161. package/packages/daemon/src/tui/mirror/runtime/application-entry.ts +2 -1
  162. package/packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions.tsx +10 -4
  163. package/packages/daemon/src/tui/mirror/runtime/application-fleet-tabs.ts +2 -0
  164. package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-integration.tsx +199 -0
  165. package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-owner.ts +162 -0
  166. package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +36 -10
  167. package/packages/daemon/src/tui/mirror/runtime/application-home-agent-transport.ts +69 -0
  168. package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +12 -2
  169. package/packages/daemon/src/tui/mirror/runtime/application-home-agents.ts +13 -2
  170. package/packages/daemon/src/tui/mirror/runtime/application-home-catalog-owner.ts +13 -3
  171. package/packages/daemon/src/tui/mirror/runtime/application-home-catalog.ts +172 -100
  172. package/packages/daemon/src/tui/mirror/runtime/application-home-experience.ts +59 -0
  173. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +175 -0
  174. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +9 -1
  175. package/packages/daemon/src/tui/mirror/runtime/application-machine-catalog.ts +19 -7
  176. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +285 -36
  177. package/packages/daemon/src/tui/mirror/runtime/application-machine-overlays.tsx +1 -1
  178. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +124 -40
  179. package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +3 -0
  180. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +18 -6
  181. package/packages/daemon/src/tui/mirror/runtime/application-root-configuration.ts +12 -3
  182. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +40 -40
  183. package/packages/daemon/src/tui/mirror/runtime/application-route-connection.ts +93 -1
  184. package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +4 -0
  185. package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +20 -1
  186. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +10 -0
  187. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +177 -2
  188. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-policy.ts +62 -0
  189. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +87 -30
  190. package/packages/daemon/src/tui/mirror/runtime/application-theme-repaint.ts +19 -0
  191. package/packages/daemon/src/tui/mirror/runtime/fleet-lifecycle-client.ts +55 -0
  192. package/packages/daemon/src/tui/mirror/runtime/guided-tour-coach.tsx +118 -0
  193. package/packages/daemon/src/tui/mirror/runtime/guided-tour-storage.ts +72 -0
  194. package/packages/daemon/src/tui/mirror/runtime/guided-tour.ts +147 -0
  195. package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +1 -1
  196. package/packages/daemon/src/tui/mirror/runtime/resize-terminal-pane.ts +3 -0
  197. package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +3 -0
  198. package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +11 -4
  199. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +15 -4
  200. package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +13 -2
  201. package/packages/daemon/src/tui/mirror/workspace/terminal-window-strip.tsx +5 -1
  202. package/packages/daemon-client/package.json +3 -1
  203. package/packages/daemon-client/src/index.ts +2 -0
  204. package/packages/daemon-client/src/pane-stream-client.test.ts +108 -38
  205. package/packages/daemon-client/src/pane-stream-client.ts +26 -0
  206. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +114 -0
  207. package/packages/daemon-client/src/scoped-tmux-server-transport.ts +385 -0
  208. package/packages/daemon-client/src/tmux-server-client.test.ts +279 -0
  209. package/packages/daemon-client/src/tmux-server-client.ts +287 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tmux-ide",
3
- "version": "2.9.0-beta.21",
3
+ "version": "2.9.0-beta.26",
4
4
  "description": "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
5
5
  "type": "module",
6
6
  "bin": {
@@ -64,7 +64,7 @@
64
64
  "postinstall": "node scripts/postinstall.js",
65
65
  "docs": "turbo run dev --filter=@tmux-ide/docs",
66
66
  "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/application-shell-sidebar-catalog-renderer.test.tsx ./packages/daemon/src/tui/mirror/ui/ui-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-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",
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/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-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",
68
68
  "test:tui-smoke": "bun scripts/smoke-tui-missions.mjs",
69
69
  "test:tui-live": "node scripts/tui-testdrive.mjs smoke",
70
70
  "test:tui-testdrive": "node --test scripts/lib/tui-testdrive-clipboard-hook.test.mjs scripts/lib/tui-testdrive-input.test.mjs",
@@ -13,10 +13,10 @@ import {
13
13
  } from "../daemon-wire.ts";
14
14
  import { WorkspaceCatalogResourceV1SchemaZ } from "../workspace-catalog-resource.ts";
15
15
 
16
- const fixturePath = fileURLToPath(new URL("./fixtures/daemon-wire-v2.json", import.meta.url));
16
+ const fixturePath = fileURLToPath(new URL("./fixtures/daemon-wire-v3.json", import.meta.url));
17
17
 
18
18
  describe("daemon wire protocol", () => {
19
- it("validates the shared desktop/TypeScript v2 fixture", () => {
19
+ it("validates the shared desktop/TypeScript v3 fixture", () => {
20
20
  const fixture = JSON.parse(readFileSync(fixturePath, "utf8")) as Record<string, unknown>;
21
21
 
22
22
  expect(CanonicalDaemonInfoSchema.parse(fixture.canonical).protocolVersion).toBe(
@@ -0,0 +1,35 @@
1
+ {
2
+ "canonical": {
3
+ "pid": 4312,
4
+ "port": 4010,
5
+ "protocolVersion": 3,
6
+ "productVersion": "0.0.1",
7
+ "instanceId": "9bcf33b0-c837-4a94-b5e8-c0977f54464f",
8
+ "startedAt": "2026-07-20T12:34:56.123Z",
9
+ "bindHostname": "127.0.0.1",
10
+ "authToken": "fixture-token"
11
+ },
12
+ "health": {
13
+ "ok": true,
14
+ "protocolVersion": 3,
15
+ "productVersion": "0.0.1",
16
+ "uptime": 42
17
+ },
18
+ "healthz": {
19
+ "ok": true,
20
+ "protocolVersion": 3,
21
+ "productVersion": "0.0.1",
22
+ "uptimeMs": 42000
23
+ },
24
+ "identity": {
25
+ "ok": true,
26
+ "pid": 4312,
27
+ "protocolVersion": 3,
28
+ "productVersion": "0.0.1",
29
+ "instanceId": "9bcf33b0-c837-4a94-b5e8-c0977f54464f",
30
+ "startedAt": "2026-07-20T12:34:56.123Z"
31
+ },
32
+ "sessions": {
33
+ "sessions": [{ "name": "fixture-project", "dir": "/work/fixture-project" }]
34
+ }
35
+ }
@@ -65,7 +65,7 @@ describe("the shared issue-error vocabulary", () => {
65
65
  for (const reason of [
66
66
  "Authorization was Bearer owner-secret",
67
67
  `The redemptionTicket was ta1_${"A".repeat(43)}`,
68
- `The redemptionTicket was ps1_${"A".repeat(43)}`,
68
+ `The redemptionTicket was ps2_${"A".repeat(43)}`,
69
69
  "ownerToken=secret",
70
70
  ]) {
71
71
  for (const schema of [TerminalAttachmentIssueErrorSchemaZ, PaneStreamIssueErrorSchemaZ]) {
@@ -22,6 +22,8 @@ const verb = (id: string): MultiplexerVerbEntry => {
22
22
  describe("the multiplexer verb table", () => {
23
23
  it("carries every verb the milestone declares, and no others", () => {
24
24
  expect([...MULTIPLEXER_VERB_IDS]).toEqual([
25
+ "window.link.select",
26
+ "window.link.unlink",
25
27
  "session.new",
26
28
  "session.kill",
27
29
  "session.rename",
@@ -80,7 +82,7 @@ describe("the multiplexer verb table", () => {
80
82
  const destructive = MULTIPLEXER_VERB_TABLE.filter((entry) => entry.destructive).map(
81
83
  (entry) => entry.id,
82
84
  );
83
- expect(destructive).toEqual(["session.kill", "window.kill", "pane.kill"]);
85
+ expect(destructive).toEqual(["window.link.unlink", "session.kill", "window.kill", "pane.kill"]);
84
86
  });
85
87
 
86
88
  it("leaves every tmux key hint unfilled until the keybinding bridge exists", () => {
@@ -25,7 +25,7 @@ import {
25
25
  PaneStreamServerFrameSchemaZ,
26
26
  } from "../pane-stream.ts";
27
27
 
28
- const TICKET = `ps1_${"A".repeat(43)}`;
28
+ const TICKET = `ps2_${"A".repeat(43)}`;
29
29
  const REQUEST_ID = "7f0f9a7e-9be0-4b6e-a3a3-0a15c9a7e0d1";
30
30
  const INSTANCE_ID = "3d3f8a52-77aa-4b3e-9a44-2f2f7b1c9d10";
31
31
  const WS_URL = `ws://127.0.0.1:6070${PANE_STREAM_REDEEM_PATH}`;
@@ -55,9 +55,9 @@ function descriptor() {
55
55
 
56
56
  describe("pane-stream lease contracts", () => {
57
57
  it("publishes one shared issue and redemption endpoint authority", () => {
58
- expect(PANE_STREAM_ISSUE_PATH).toBe("/api/v1/terminal/pane-streams/issue");
59
- expect(PANE_STREAM_REDEEM_PATH).toBe("/v1/terminal/pane-streams/redeem");
60
- expect(PANE_STREAM_WEBSOCKET_SUBPROTOCOL).toBe("tmux-ide-pane-stream.v1");
58
+ expect(PANE_STREAM_ISSUE_PATH).toBe("/api/v2/terminal/pane-streams/issue");
59
+ expect(PANE_STREAM_REDEEM_PATH).toBe("/v2/terminal/pane-streams/redeem");
60
+ expect(PANE_STREAM_WEBSOCKET_SUBPROTOCOL).toBe("tmux-ide-pane-stream.v2");
61
61
  });
62
62
 
63
63
  it("accepts a viewport-free enumerated pane set", () => {
@@ -115,7 +115,7 @@ describe("pane-stream lease contracts", () => {
115
115
 
116
116
  it("accepts only ps1 tickets", () => {
117
117
  expect(PaneStreamRedemptionTicketSchemaZ.safeParse(TICKET).success).toBe(true);
118
- for (const invalid of [`ta1_${"A".repeat(43)}`, `ps1_${"A".repeat(42)}`, "ps1_", "ps1"]) {
118
+ for (const invalid of [`ta1_${"A".repeat(43)}`, `ps2_${"A".repeat(42)}`, "ps2_", "ps1"]) {
119
119
  expect(PaneStreamRedemptionTicketSchemaZ.safeParse(invalid).success).toBe(false);
120
120
  }
121
121
  });
@@ -184,6 +184,18 @@ describe("pane-stream lease contracts", () => {
184
184
  type: "layout-snapshot" as const,
185
185
  topologyEpoch: 1,
186
186
  layouts: [layout("window.one", "pane.editor", true)],
187
+ windowLinks: {
188
+ liveSessionId: `live-session.${"a".repeat(20)}`,
189
+ linkRevision: 1,
190
+ activeLinkId: `window-link.${"a".repeat(32)}`,
191
+ links: [
192
+ {
193
+ linkId: `window-link.${"a".repeat(32)}`,
194
+ semanticWindowId: "window.one",
195
+ displayIndex: 0,
196
+ },
197
+ ],
198
+ },
187
199
  };
188
200
  expect(PaneStreamServerFrameSchemaZ.safeParse(snapshot).success).toBe(true);
189
201
  for (const layouts of [
@@ -0,0 +1,166 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ TmuxServerScopeSchemaZ,
4
+ tmuxServerPaneStreamPath,
5
+ isTmuxServerPaneStreamPath,
6
+ TmuxServerDescriptorSchemaZ,
7
+ TmuxServersResourceSchemaZ,
8
+ TmuxServerPaneTargetSchemaZ,
9
+ resolveTmuxServerScope,
10
+ tmuxServerScopedResourceKey,
11
+ type TmuxServerScope,
12
+ type TmuxServerDescriptor,
13
+ } from "../tmux-server-scope.ts";
14
+
15
+ import { PaneStreamLoopbackWebSocketUrlSchemaZ, PANE_STREAM_REDEEM_PATH } from "../pane-stream.ts";
16
+
17
+ const a: TmuxServerScope = {
18
+ serverId: `tmux-server.${"a".repeat(32)}`,
19
+ generation: "aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa",
20
+ };
21
+ const b: TmuxServerScope = { ...a, serverId: `tmux-server.${"b".repeat(32)}` };
22
+ const replacement: TmuxServerScope = { ...a, generation: "bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb" };
23
+ const online = (scope: TmuxServerScope): TmuxServerDescriptor => ({
24
+ ...scope,
25
+ label: "default",
26
+ state: "online",
27
+ });
28
+ const offline = (scope: TmuxServerScope): TmuxServerDescriptor => ({
29
+ serverId: scope.serverId,
30
+ label: "default",
31
+ state: "offline",
32
+ generation: null,
33
+ });
34
+
35
+ describe("server-scoped identities", () => {
36
+ it("keeps registration identity separate from each live incarnation", () => {
37
+ expect(TmuxServerScopeSchemaZ.parse(a)).toEqual(a);
38
+ expect(TmuxServerScopeSchemaZ.parse(replacement).serverId).toBe(a.serverId);
39
+ for (const serverId of ["/tmp/tmux/socket", "default", "%0", "tmux-server.short"]) {
40
+ expect(TmuxServerScopeSchemaZ.safeParse({ ...a, serverId }).success).toBe(false);
41
+ }
42
+ expect(TmuxServerScopeSchemaZ.safeParse({ ...a, socketPath: "/tmp/socket" }).success).toBe(
43
+ false,
44
+ );
45
+ expect(TmuxServerScopeSchemaZ.safeParse({ ...a, generation: "1234" }).success).toBe(false);
46
+ });
47
+
48
+ it("publishes no live generation for offline registrations", () => {
49
+ expect(TmuxServerDescriptorSchemaZ.parse(online(a))).toEqual(online(a));
50
+ expect(TmuxServerDescriptorSchemaZ.parse(offline(a))).toEqual(offline(a));
51
+ expect(TmuxServerDescriptorSchemaZ.safeParse({ ...online(a), generation: null }).success).toBe(
52
+ false,
53
+ );
54
+ expect(
55
+ TmuxServerDescriptorSchemaZ.safeParse({ ...offline(a), generation: a.generation }).success,
56
+ ).toBe(false);
57
+ });
58
+
59
+ it("versions the new resource explicitly and refuses duplicate registrations", () => {
60
+ expect(
61
+ TmuxServersResourceSchemaZ.safeParse({ version: 1, servers: [online(a), online(b)] }).success,
62
+ ).toBe(true);
63
+ expect(TmuxServersResourceSchemaZ.safeParse({ version: 2, servers: [] }).success).toBe(false);
64
+ expect(
65
+ TmuxServersResourceSchemaZ.safeParse({ version: 1, servers: [online(a), offline(a)] })
66
+ .success,
67
+ ).toBe(false);
68
+ });
69
+
70
+ it("requires complete server scope for copied pane stamps", () => {
71
+ const target = {
72
+ server: a,
73
+ liveSessionId: `live-session.${"a".repeat(20)}`,
74
+ semanticPaneId: "pane.copied",
75
+ };
76
+ expect(TmuxServerPaneTargetSchemaZ.safeParse(target).success).toBe(true);
77
+ expect(TmuxServerPaneTargetSchemaZ.safeParse({ ...target, server: undefined }).success).toBe(
78
+ false,
79
+ );
80
+ expect(TmuxServerPaneTargetSchemaZ.safeParse({ ...target, runtimePaneId: "%0" }).success).toBe(
81
+ false,
82
+ );
83
+ });
84
+
85
+ it("keys copied stamps, native IDs and generations without delimiter collisions", () => {
86
+ const keys = [
87
+ tmuxServerScopedResourceKey(a, "pane", "copied"),
88
+ tmuxServerScopedResourceKey(b, "pane", "copied"),
89
+ tmuxServerScopedResourceKey(replacement, "pane", "copied"),
90
+ tmuxServerScopedResourceKey(a, "window", "copied"),
91
+ tmuxServerScopedResourceKey(a, "pane:copied", ""),
92
+ tmuxServerScopedResourceKey(a, "pane", ":copied"),
93
+ ];
94
+ expect(new Set(keys).size).toBe(keys.length);
95
+ expect(tmuxServerScopedResourceKey(a, "pane", "%0")).not.toBe(
96
+ tmuxServerScopedResourceKey(b, "pane", "%0"),
97
+ );
98
+ });
99
+ });
100
+
101
+ describe("server selection authority", () => {
102
+ it("routes exact scope despite identical display labels", () => {
103
+ expect(resolveTmuxServerScope([online(a), online(b)], b)).toEqual({
104
+ status: "matched",
105
+ scope: b,
106
+ });
107
+ });
108
+
109
+ it("rejects old generations without falling back to another live server", () => {
110
+ expect(resolveTmuxServerScope([online(replacement), online(b)], a)).toEqual({
111
+ status: "stale-generation",
112
+ });
113
+ expect(resolveTmuxServerScope([online(b)], a)).toEqual({ status: "not-found" });
114
+ expect(resolveTmuxServerScope([offline(a), online(b)], a)).toEqual({ status: "offline" });
115
+ });
116
+
117
+ it("allows legacy selection only with one registered live owner", () => {
118
+ expect(resolveTmuxServerScope([online(a)])).toEqual({ status: "matched", scope: a });
119
+ expect(resolveTmuxServerScope([])).toEqual({ status: "not-found" });
120
+ expect(resolveTmuxServerScope([offline(a)])).toEqual({ status: "offline" });
121
+ expect(resolveTmuxServerScope([online(a), online(b)])).toEqual({ status: "ambiguous" });
122
+ expect(resolveTmuxServerScope([offline(a), online(b)])).toEqual({ status: "ambiguous" });
123
+ });
124
+
125
+ it("fails closed if malformed registry repeats an identity", () => {
126
+ expect(resolveTmuxServerScope([online(a), online(a)], a)).toEqual({ status: "ambiguous" });
127
+ });
128
+ });
129
+
130
+ describe("server-scoped pane stream addresses", () => {
131
+ it("separates owners and generations and accepts canonical loopback URLs", () => {
132
+ const paths = [a, b, replacement].map(tmuxServerPaneStreamPath);
133
+ expect(new Set(paths).size).toBe(3);
134
+ for (const path of [...paths, PANE_STREAM_REDEEM_PATH]) {
135
+ expect(
136
+ PaneStreamLoopbackWebSocketUrlSchemaZ.safeParse(`ws://127.0.0.1:1234${path}`).success,
137
+ ).toBe(true);
138
+ }
139
+ });
140
+
141
+ it("refuses unscoped, malformed, encoded and credentialed paths", () => {
142
+ const path = tmuxServerPaneStreamPath(a);
143
+ for (const badPath of [
144
+ path + "/",
145
+ path + "/other",
146
+ path.replace(a.serverId, "default"),
147
+ path.replace(a.generation, "old"),
148
+ path.replace("tmux-server.", "tmux-server%2e"),
149
+ "/v2/tmux-servers/pane-streams/redeem",
150
+ ]) {
151
+ expect(isTmuxServerPaneStreamPath(badPath)).toBe(false);
152
+ expect(
153
+ PaneStreamLoopbackWebSocketUrlSchemaZ.safeParse(`ws://127.0.0.1:1234${badPath}`).success,
154
+ ).toBe(false);
155
+ }
156
+ for (const url of [
157
+ `ws://127.0.0.1:1234${path}?generation=${b.generation}`,
158
+ `ws://127.0.0.1:1234${path}#fragment`,
159
+ `ws://user@127.0.0.1:1234${path}`,
160
+ `ws://example.com:1234${path}`,
161
+ `wss://127.0.0.1:1234${path}`,
162
+ `ws://127.0.0.1${path}`,
163
+ ])
164
+ expect(PaneStreamLoopbackWebSocketUrlSchemaZ.safeParse(url).success).toBe(false);
165
+ });
166
+ });
@@ -0,0 +1,243 @@
1
+ import {
2
+ WorkspaceWindowLinkSelectArgumentsSchemaZ,
3
+ WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
4
+ WorkspacePaneSelectWithWindowLinkArgumentsSchemaZ,
5
+ WorkspacePaneSelectArgumentsSchemaZ,
6
+ WorkspaceMultiplexerIntentSchemaZ,
7
+ } from "../workspace-multiplexer.ts";
8
+ import { describe, expect, it } from "vitest";
9
+ import {
10
+ WINDOW_LINK_MAX_LINKS,
11
+ WindowLinkTargetSchemaZ,
12
+ WindowLinkTopologySchemaZ,
13
+ } from "../window-links.ts";
14
+ import {
15
+ PaneStreamLayoutSnapshotV2FrameSchemaZ,
16
+ PaneStreamLayoutSnapshotFrameSchemaZ,
17
+ PaneStreamLayoutSnapshotV1FrameSchemaZ,
18
+ PaneStreamServerFrameSchemaZ,
19
+ PANE_STREAM_PROTOCOL_VERSION,
20
+ } from "../pane-stream.ts";
21
+
22
+ const linkId = (index: number) => `window-link.${index.toString(16).padStart(32, "0")}`;
23
+ const topology = () => ({
24
+ liveSessionId: `live-session.${"a".repeat(20)}`,
25
+ linkRevision: 1,
26
+ activeLinkId: linkId(1),
27
+ links: [
28
+ { linkId: linkId(1), semanticWindowId: "window.one", displayIndex: 0 },
29
+ { linkId: linkId(2), semanticWindowId: "window.one", displayIndex: 1 },
30
+ ],
31
+ });
32
+ const layout = (window = "window.one", pane: string | null = "pane.one", currentWindow = true) => ({
33
+ type: "layout" as const,
34
+ semanticWindowId: window,
35
+ windowName: "shell",
36
+ currentWindow,
37
+ cols: 80,
38
+ rows: 24,
39
+ zoomed: false,
40
+ panes: [{ pane, left: 0, top: 0, width: 80, height: 24, active: true }],
41
+ });
42
+ const snapshot = () => ({
43
+ type: "layout-snapshot" as const,
44
+ topologyEpoch: 1,
45
+ layouts: [layout()],
46
+ windowLinks: topology(),
47
+ });
48
+
49
+ describe("window link contracts", () => {
50
+ it("allows multiple links sharing one unique backing and pane", () => {
51
+ expect(WindowLinkTopologySchemaZ.safeParse(topology()).success).toBe(true);
52
+ expect(PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse(snapshot()).success).toBe(true);
53
+ expect(
54
+ PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse({
55
+ ...snapshot(),
56
+ windowLinks: { ...topology(), activeLinkId: linkId(2) },
57
+ }).success,
58
+ ).toBe(true);
59
+ });
60
+
61
+ it("rejects duplicate handles/indexes and missing active links", () => {
62
+ const original = topology();
63
+ for (const invalid of [
64
+ { ...original, links: [original.links[0], { ...original.links[1], linkId: linkId(1) }] },
65
+ { ...original, links: [original.links[0], { ...original.links[1], displayIndex: 0 }] },
66
+ { ...original, activeLinkId: linkId(3) },
67
+ { ...original, links: [] },
68
+ ])
69
+ expect(WindowLinkTopologySchemaZ.safeParse(invalid).success).toBe(false);
70
+ });
71
+
72
+ it("bounds topology size and integer revisions/indexes", () => {
73
+ const links = Array.from({ length: WINDOW_LINK_MAX_LINKS }, (_, index) => ({
74
+ linkId: linkId(index + 1),
75
+ semanticWindowId: "window.one",
76
+ displayIndex: index,
77
+ }));
78
+ expect(WindowLinkTopologySchemaZ.safeParse({ ...topology(), links }).success).toBe(true);
79
+ expect(
80
+ WindowLinkTopologySchemaZ.safeParse({
81
+ ...topology(),
82
+ links: [
83
+ ...links,
84
+ {
85
+ linkId: linkId(WINDOW_LINK_MAX_LINKS + 1),
86
+ semanticWindowId: "window.one",
87
+ displayIndex: WINDOW_LINK_MAX_LINKS,
88
+ },
89
+ ],
90
+ }).success,
91
+ ).toBe(false);
92
+ for (const value of [-1, 0.5, Number.MAX_SAFE_INTEGER + 1, Infinity]) {
93
+ expect(
94
+ WindowLinkTopologySchemaZ.safeParse({ ...topology(), linkRevision: value }).success,
95
+ ).toBe(false);
96
+ expect(
97
+ WindowLinkTopologySchemaZ.safeParse({
98
+ ...topology(),
99
+ links: [{ ...links[0], displayIndex: value }],
100
+ }).success,
101
+ ).toBe(false);
102
+ }
103
+ });
104
+
105
+ it("accepts only opaque targets and rejects native or display action addresses", () => {
106
+ const target = {
107
+ liveSessionId: topology().liveSessionId,
108
+ linkId: linkId(1),
109
+ expectedSemanticWindowId: "window.one",
110
+ linkRevision: 0,
111
+ };
112
+ expect(WindowLinkTargetSchemaZ.safeParse(target).success).toBe(true);
113
+ for (const invalid of [
114
+ { ...target, linkId: "@0" },
115
+ { ...target, linkId: "/tmp/tmux/socket" },
116
+ { ...target, linkId: "window-link.abc" },
117
+ { ...target, liveSessionId: "$0" },
118
+ { ...target, expectedSemanticWindowId: "@0" },
119
+ { ...target, displayIndex: 0 },
120
+ { ...target, linkRevision: -1 },
121
+ { ...target, linkRevision: Number.MAX_SAFE_INTEGER + 1 },
122
+ ])
123
+ expect(WindowLinkTargetSchemaZ.safeParse(invalid).success).toBe(false);
124
+ });
125
+
126
+ it("requires a complete join and a matching active backing", () => {
127
+ const second = layout("window.two", "pane.two", false);
128
+ for (const invalid of [
129
+ { ...snapshot(), layouts: [layout("window.two")] },
130
+ { ...snapshot(), layouts: [layout(), second] },
131
+ {
132
+ ...snapshot(),
133
+ layouts: [layout("window.one", "pane.one", false), { ...second, currentWindow: true }],
134
+ windowLinks: {
135
+ ...topology(),
136
+ links: [
137
+ ...topology().links,
138
+ { linkId: linkId(3), semanticWindowId: "window.two", displayIndex: 2 },
139
+ ],
140
+ },
141
+ },
142
+ ])
143
+ expect(PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse(invalid).success).toBe(false);
144
+ });
145
+
146
+ it("preserves unique backing/pane and exactly-one-current invariants", () => {
147
+ for (const layouts of [
148
+ [layout(), layout()],
149
+ [layout(), layout("window.two", "pane.one", false)],
150
+ [layout("window.one", null)],
151
+ [{ ...layout(), semanticWindowId: null }],
152
+ [layout("window.one", "pane.one", false)],
153
+ [layout(), layout("window.two", "pane.two", true)],
154
+ ])
155
+ expect(
156
+ PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse({ ...snapshot(), layouts }).success,
157
+ ).toBe(false);
158
+ });
159
+
160
+ it("requires v2 topology and refuses the old v1 shape", () => {
161
+ const v1 = { type: "layout-snapshot", topologyEpoch: 1, layouts: [layout()] };
162
+ expect(PANE_STREAM_PROTOCOL_VERSION).toBe(2);
163
+ expect(PaneStreamLayoutSnapshotV1FrameSchemaZ.safeParse(v1).success).toBe(true);
164
+ expect(PaneStreamLayoutSnapshotFrameSchemaZ.safeParse(v1).success).toBe(false);
165
+ expect(PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse(v1).success).toBe(false);
166
+ expect(PaneStreamLayoutSnapshotFrameSchemaZ.safeParse(snapshot()).success).toBe(true);
167
+ expect(PaneStreamServerFrameSchemaZ.safeParse(snapshot()).success).toBe(true);
168
+ expect(
169
+ PaneStreamLayoutSnapshotV2FrameSchemaZ.safeParse({ ...snapshot(), unexpected: true }).success,
170
+ ).toBe(false);
171
+ });
172
+ });
173
+
174
+ describe("window link action arguments", () => {
175
+ const target = () => ({
176
+ liveSessionId: topology().liveSessionId,
177
+ linkId: linkId(1),
178
+ expectedSemanticWindowId: "window.one",
179
+ linkRevision: 1,
180
+ });
181
+
182
+ it("accepts opaque select/unlink targets and explicit-link pane selection", () => {
183
+ for (const schema of [
184
+ WorkspaceWindowLinkSelectArgumentsSchemaZ,
185
+ WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
186
+ ]) {
187
+ expect(schema.safeParse({ workspaceName: "project", target: target() }).success).toBe(true);
188
+ }
189
+ expect(
190
+ WorkspacePaneSelectWithWindowLinkArgumentsSchemaZ.safeParse({
191
+ workspaceName: "project",
192
+ semanticPaneId: "pane.one",
193
+ windowLink: target(),
194
+ }).success,
195
+ ).toBe(true);
196
+ });
197
+
198
+ it("rejects native addresses, display indexes, command/force flags and unknown fields", () => {
199
+ for (const schema of [
200
+ WorkspaceWindowLinkSelectArgumentsSchemaZ,
201
+ WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
202
+ ]) {
203
+ const args = { workspaceName: "project", target: target() };
204
+ for (const invalid of [
205
+ { ...args, target: "@0" },
206
+ { ...args, target: { ...target(), linkId: "@0" } },
207
+ { ...args, target: { ...target(), expectedSemanticWindowId: "@0" } },
208
+ { ...args, target: { ...target(), displayIndex: 0 } },
209
+ { ...args, target: { ...target(), runtimeSessionId: "$0" } },
210
+ { ...args, displayIndex: 0 },
211
+ { ...args, command: "kill-server" },
212
+ { ...args, force: true },
213
+ { ...args, socketPath: "/tmp/tmux/socket" },
214
+ { ...args, workspaceName: "" },
215
+ ])
216
+ expect(schema.safeParse(invalid).success).toBe(false);
217
+ }
218
+ const args = { workspaceName: "project", semanticPaneId: "pane.one", windowLink: target() };
219
+ for (const invalid of [
220
+ { ...args, semanticPaneId: "%0" },
221
+ { ...args, command: "select-pane -t %0" },
222
+ { ...args, windowLink: { ...target(), linkId: "project:0" } },
223
+ { workspaceName: "project", semanticPaneId: "pane.one" },
224
+ ])
225
+ expect(WorkspacePaneSelectWithWindowLinkArgumentsSchemaZ.safeParse(invalid).success).toBe(
226
+ false,
227
+ );
228
+ });
229
+
230
+ it("accepts registered link verbs and explicit-link pane selection", () => {
231
+ const args = { workspaceName: "project", target: target() };
232
+ for (const verb of ["workspace.window.link.select", "workspace.window.link.unlink"]) {
233
+ expect(WorkspaceMultiplexerIntentSchemaZ.safeParse({ verb, ...args }).success).toBe(true);
234
+ }
235
+ expect(
236
+ WorkspacePaneSelectArgumentsSchemaZ.safeParse({
237
+ workspaceName: "project",
238
+ semanticPaneId: "pane.one",
239
+ windowLink: target(),
240
+ }).success,
241
+ ).toBe(true);
242
+ });
243
+ });
@@ -42,6 +42,10 @@ import {
42
42
  AppWindowMutationResultSchemaZ,
43
43
  } from "./app-window-mutation.ts";
44
44
  import {
45
+ WorkspaceWindowLinkSelectArgumentsSchemaZ,
46
+ WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
47
+ WorkspaceWindowLinkSelectResultSchemaZ,
48
+ WorkspaceWindowLinkUnlinkResultSchemaZ,
45
49
  WorkspacePaneKillArgumentsSchemaZ,
46
50
  WorkspacePaneKillResultSchemaZ,
47
51
  WorkspacePaneResizeArgumentsSchemaZ,
@@ -427,6 +431,14 @@ export const ActionContractsZ = {
427
431
  input: AppWindowMutationInputZ,
428
432
  result: AppWindowMutationResultZ,
429
433
  },
434
+ "workspace.window.link.select": {
435
+ input: WorkspaceWindowLinkSelectArgumentsSchemaZ,
436
+ result: WorkspaceWindowLinkSelectResultSchemaZ,
437
+ },
438
+ "workspace.window.link.unlink": {
439
+ input: WorkspaceWindowLinkUnlinkArgumentsSchemaZ,
440
+ result: WorkspaceWindowLinkUnlinkResultSchemaZ,
441
+ },
430
442
  "workspace.window.split": {
431
443
  input: WorkspaceWindowSplitInputZ,
432
444
  result: WorkspaceWindowSplitResultZ,
@@ -84,7 +84,7 @@ const REQUESTS: readonly DaemonResourceRequest[] = [
84
84
  {
85
85
  resource: "issuePaneStream",
86
86
  request: {
87
- protocolVersion: 1,
87
+ protocolVersion: 2,
88
88
  workspaceName: WORKSPACE,
89
89
  panes: ["pane.worker"],
90
90
  viewerMode: "read-only",
@@ -5,11 +5,8 @@ import { z } from "zod";
5
5
  * health responses, REST resources, and WebSocket transports. This is
6
6
  * intentionally independent from npm/package marketing versions.
7
7
  */
8
- // v2 makes the daemon-minted fleet session identity mandatory on workspace
9
- // catalog v2 rows. A v1 daemon can otherwise pass the bootstrap handshake and
10
- // then fail a current client at the first catalog parse, so this is a real wire
11
- // boundary rather than an additive package-version change.
12
- export const DAEMON_WIRE_PROTOCOL_VERSION = 2 as const;
8
+ // v3 requires link-aware terminal topology and actions; v2 peers must upgrade.
9
+ export const DAEMON_WIRE_PROTOCOL_VERSION = 3 as const;
13
10
 
14
11
  /**
15
12
  * Discovery must retain unknown positive versions so a client can report an
@@ -96,7 +93,14 @@ export const DaemonProvenanceSchema = z.object({
96
93
  });
97
94
  export type DaemonProvenance = z.infer<typeof DaemonProvenanceSchema>;
98
95
 
96
+ export const TmuxServerProofSchema = z.object({
97
+ version: z.literal(1),
98
+ kind: z.enum(["live", "unbound-name"]),
99
+ digest: z.string().regex(/^[a-f0-9]{64}$/u),
100
+ });
101
+
99
102
  export const CanonicalDaemonInfoSchema = z.object({
103
+ tmuxServerProofVersion: z.literal(1).optional(),
100
104
  supervisionId: DaemonSupervisionIdSchema.optional(),
101
105
  pid: z.number().int().positive(),
102
106
  port: z.number().int().min(1).max(65_535),
@@ -133,6 +137,7 @@ export type DaemonHealthz = z.infer<typeof DaemonHealthzSchema>;
133
137
  * daemon.json record is the process instance which published that record.
134
138
  */
135
139
  export const DaemonIdentitySchema = z.object({
140
+ tmuxServerProof: TmuxServerProofSchema.nullable().optional(),
136
141
  ok: z.literal(true),
137
142
  pid: z.number().int().positive(),
138
143
  protocolVersion: DaemonWireProtocolVersionSchema,
@@ -1,3 +1,4 @@
1
+ import { TmuxServerScopeSchemaZ } from "./tmux-server-scope.ts";
1
2
  import { z } from "zod";
2
3
  import { SavedMachineIdSchema } from "./saved-machines.ts";
3
4
  import { DaemonInstanceIdentitySchemaZ } from "./daemon-wire.ts";
@@ -16,6 +17,8 @@ export const FleetCacheRouteIdSchema = z.union([z.literal("local"), SavedMachine
16
17
  export const FleetCachedSessionSchema = z.strictObject({
17
18
  id: key,
18
19
  liveSessionId: key.optional(),
20
+ server: TmuxServerScopeSchemaZ.optional(),
21
+ serverLabel: label.optional(),
19
22
  name: label,
20
23
  paneCount: z.number().int().min(0).max(4096),
21
24
  });