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
@@ -41,7 +41,7 @@ export function createSemanticShellViewportResizeOwner(
41
41
  type ScopedTarget = Readonly<{ cols: number; rows: number; semanticWindowId?: string }>;
42
42
  let desired: Readonly<{ identity: ResizeIdentity; targets: readonly ScopedTarget[] }> | null =
43
43
  null;
44
- let scopedFlight = false;
44
+ let scopedFlight: object | null = null;
45
45
  let scopedEpoch = 0;
46
46
  let authorityClient: OpenTuiGenerationHostSnapshot["authorityClient"] = null;
47
47
  let stopAuthority: (() => void) | null = null;
@@ -53,7 +53,8 @@ export function createSemanticShellViewportResizeOwner(
53
53
  const targetSize = (target: ScopedTarget) => `${target.cols}x${target.rows}`;
54
54
  const drain = async (): Promise<void> => {
55
55
  if (scopedFlight || disposed || authoritySuspended) return;
56
- scopedFlight = true;
56
+ const flight = {};
57
+ scopedFlight = flight;
57
58
  try {
58
59
  while (desired && !disposed && !authoritySuspended) {
59
60
  const owner = desired;
@@ -70,7 +71,7 @@ export function createSemanticShellViewportResizeOwner(
70
71
  .resize(target)
71
72
  .catch(() => ({ status: "failed" as const }));
72
73
  if (disposed || !desired) break;
73
- if (epoch !== scopedEpoch) continue;
74
+ if (epoch !== scopedEpoch) break;
74
75
  const stillDesired = desired.targets.some(
75
76
  (entry) =>
76
77
  targetKey(entry) === targetKey(target) && targetSize(entry) === targetSize(target),
@@ -85,7 +86,7 @@ export function createSemanticShellViewportResizeOwner(
85
86
  scopedApplied.set(targetKey(target), targetSize(target));
86
87
  }
87
88
  } finally {
88
- scopedFlight = false;
89
+ if (scopedFlight === flight) scopedFlight = null;
89
90
  }
90
91
  };
91
92
  const sameAuthority = (left: ResizeIdentity, right: ResizeIdentity): boolean =>
@@ -122,6 +123,7 @@ export function createSemanticShellViewportResizeOwner(
122
123
  pending = null;
123
124
  desired = null;
124
125
  scopedEpoch += 1;
126
+ scopedFlight = null;
125
127
  scopedApplied.clear();
126
128
  return;
127
129
  }
@@ -150,6 +152,7 @@ export function createSemanticShellViewportResizeOwner(
150
152
  pending = null;
151
153
  scopedApplied.clear();
152
154
  scopedEpoch += 1;
155
+ scopedFlight = null;
153
156
  if (!authoritySuspended) retry?.();
154
157
  }) ?? null;
155
158
  }
@@ -174,6 +177,8 @@ export function createSemanticShellViewportResizeOwner(
174
177
  pending = null;
175
178
  if (!desired || !sameAuthority(desired.identity, target)) {
176
179
  scopedEpoch += 1;
180
+ // A retired lane must not hold a replacement lane behind its receipt.
181
+ scopedFlight = null;
177
182
  scopedApplied.clear();
178
183
  }
179
184
  const retainedWindows = new Set(["", ...windows.map((window) => window.semanticWindowId!)]);
@@ -199,6 +204,7 @@ export function createSemanticShellViewportResizeOwner(
199
204
  if (desired) {
200
205
  desired = null;
201
206
  scopedEpoch += 1;
207
+ scopedFlight = null;
202
208
  scopedApplied.clear();
203
209
  applied = null;
204
210
  pending = null;
@@ -228,6 +234,7 @@ export function createSemanticShellViewportResizeOwner(
228
234
  stopAuthority = null;
229
235
  authorityClient = null;
230
236
  desired = null;
237
+ scopedFlight = null;
231
238
  scopedApplied.clear();
232
239
  applied = null;
233
240
  pending = null;
@@ -44,14 +44,25 @@ export class TerminalPaneInputRouter<Input> {
44
44
  if (this.#pending?.selected === true) this.#pending = null;
45
45
  }
46
46
 
47
- selectPane(paneId: string, options: { readonly presentOptimistically?: boolean } = {}): void {
48
- if (this.#pending?.paneId === paneId) return;
49
- if (this.#pending === null && this.#canonicalPane === paneId && this.#focusedPane === paneId)
47
+ selectPane(
48
+ paneId: string,
49
+ options: {
50
+ readonly presentOptimistically?: boolean;
51
+ readonly selection?: () => Promise<boolean>;
52
+ } = {},
53
+ ): void {
54
+ if (!options.selection && this.#pending?.paneId === paneId) return;
55
+ if (
56
+ !options.selection &&
57
+ this.#pending === null &&
58
+ this.#canonicalPane === paneId &&
59
+ this.#focusedPane === paneId
60
+ )
50
61
  return;
51
62
  const token = ++this.#selectionToken;
52
63
  const presentOptimistically = options.presentOptimistically !== false;
53
64
  if (presentOptimistically) this.#setFocusedPane(paneId);
54
- const settled = this.#options.select(paneId);
65
+ const settled = options.selection ? options.selection() : this.#options.select(paneId);
55
66
  this.#pending = { token, paneId, presentOptimistically, settled, selected: null };
56
67
  void settled.then((selected) => {
57
68
  if (this.#pending?.token !== token) return;
@@ -1,7 +1,9 @@
1
+ import type { TmuxServerScope } from "@tmux-ide/contracts";
1
2
  import { fuzzyTermsMatch as commandSearchMatch } from "../../team/fuzzy.ts";
2
3
  export { fuzzyTermsMatch as commandSearchMatch } from "../../team/fuzzy.ts";
3
4
  import { PANE_ACTION_MENU_ITEMS } from "./pane-action-menu-model.ts";
4
5
  export interface FleetPaletteTarget {
6
+ readonly server?: TmuxServerScope;
5
7
  readonly machineId: string;
6
8
  readonly liveSessionId: string;
7
9
  readonly hostLabel: string;
@@ -51,8 +53,14 @@ export type ApplicationPaletteCommand =
51
53
  export function applicationCommandDescription(command: ApplicationPaletteCommand) {
52
54
  if (typeof command === "object" && command.kind === "open-machine")
53
55
  return {
54
- id: JSON.stringify([command.kind, command.fleet.machineId]),
55
- label: `Machine · ${command.label}`,
56
+ id: JSON.stringify([
57
+ command.kind,
58
+ command.fleet.machineId,
59
+ ...(command.fleet.server
60
+ ? [command.fleet.server.serverId, command.fleet.server.generation]
61
+ : []),
62
+ ]),
63
+ label: `${command.fleet.server ? "Server" : "Machine"} · ${command.label}`,
56
64
  detail: command.fleet.disabled ? "Unavailable" : "Browse sessions or create on this host",
57
65
  };
58
66
  if (typeof command === "object") {
@@ -63,6 +71,9 @@ export function applicationCommandDescription(command: ApplicationPaletteCommand
63
71
  ? [
64
72
  command.kind,
65
73
  command.fleet.machineId,
74
+ ...(command.fleet.server
75
+ ? [command.fleet.server.serverId, command.fleet.server.generation]
76
+ : []),
66
77
  command.fleet.liveSessionId,
67
78
  session ? null : command.paneId,
68
79
  ]
@@ -14,6 +14,8 @@ import {
14
14
  } from "../ui/index.ts";
15
15
 
16
16
  export interface TerminalWindowTab {
17
+ linkId?: string;
18
+ disabled?: boolean;
17
19
  index: number;
18
20
  name: string;
19
21
  active: boolean;
@@ -414,10 +416,12 @@ function agentStatus(status: string | undefined): AgentBadgeStatus | undefined {
414
416
  */
415
417
  export function TerminalWindowStrip(props: TerminalWindowStripProps) {
416
418
  const tabs = () => (typeof props.tabs === "function" ? props.tabs() : props.tabs);
417
- const tabId = (tab: TerminalWindowTab) => tab.semanticWindowId ?? `window:${tab.index}`;
419
+ const tabId = (tab: TerminalWindowTab) =>
420
+ tab.linkId ?? tab.semanticWindowId ?? `window:${tab.index}`;
418
421
  const items = createMemo<readonly WindowTabItem[]>(() =>
419
422
  tabs().map((tab) => ({
420
423
  id: tabId(tab),
424
+ disabled: tab.disabled,
421
425
  windowIndex: tab.index,
422
426
  title: tab.name,
423
427
  agentStatus: agentStatus(tab.status),
@@ -26,7 +26,9 @@
26
26
  "./runtime-resource-ledger": "./src/runtime-resource-ledger.ts",
27
27
  "./fleet-dial-scheduler": "./src/fleet-dial-scheduler.ts",
28
28
  "./fleet-connection-status": "./src/fleet-connection-status.ts",
29
- "./fleet-environments": "./src/fleet-environments.ts"
29
+ "./fleet-environments": "./src/fleet-environments.ts",
30
+ "./tmux-server-client": "./src/tmux-server-client.ts",
31
+ "./scoped-tmux-server-transport": "./src/scoped-tmux-server-transport.ts"
30
32
  },
31
33
  "main": "src/index.ts",
32
34
  "scripts": {
@@ -27,3 +27,5 @@ export * from "./first-latest-coordinator.ts";
27
27
  export * from "./fleet-dial-scheduler.ts";
28
28
  export * from "./fleet-connection-status.ts";
29
29
  export * from "./fleet-environments.ts";
30
+ export * from "./tmux-server-client.ts";
31
+ export * from "./scoped-tmux-server-transport.ts";
@@ -13,7 +13,7 @@ const INSTANCE = "11111111-1111-4111-8111-111111111111";
13
13
  const REQUEST = "22222222-2222-4222-8222-222222222222";
14
14
  const TRANSACTION = "33333333-3333-4333-8333-333333333333";
15
15
  const OPERATION = "44444444-4444-4444-8444-444444444444";
16
- const TICKET = `ps1_${"a".repeat(43)}`;
16
+ const TICKET = `ps2_${"a".repeat(43)}`;
17
17
 
18
18
  class FakeSocket implements PaneStreamClientSocket {
19
19
  readyState = 1;
@@ -51,9 +51,9 @@ class FakeSocket implements PaneStreamClientSocket {
51
51
 
52
52
  function descriptor(viewerMode: "interactive" | "read-only" = "interactive") {
53
53
  return {
54
- protocolVersion: 1,
55
- webSocketUrl: "ws://127.0.0.1:6060/v1/terminal/pane-streams/redeem",
56
- subprotocol: "tmux-ide-pane-stream.v1",
54
+ protocolVersion: 2,
55
+ webSocketUrl: "ws://127.0.0.1:6060/v2/terminal/pane-streams/redeem",
56
+ subprotocol: "tmux-ide-pane-stream.v2",
57
57
  redemptionTicket: TICKET,
58
58
  daemonInstanceId: INSTANCE,
59
59
  requestId: REQUEST,
@@ -72,7 +72,7 @@ function options(socket: FakeSocket, overrides: Record<string, unknown> = {}) {
72
72
  hostClientId: "tui:one",
73
73
  requestId: REQUEST,
74
74
  stream: {
75
- protocolVersion: 1 as const,
75
+ protocolVersion: 2 as const,
76
76
  workspaceName: "alpha",
77
77
  panes: ["pane.editor"],
78
78
  viewerMode: "interactive" as const,
@@ -114,7 +114,7 @@ function acceptInteractiveHandshake(socket: FakeSocket): void {
114
114
  queueMicrotask(() =>
115
115
  socket.message({
116
116
  type: "ready",
117
- protocolVersion: 1,
117
+ protocolVersion: 2,
118
118
  daemonInstanceId: INSTANCE,
119
119
  requestId: REQUEST,
120
120
  panes: ["pane.editor"],
@@ -172,6 +172,18 @@ describe("semantic pane-stream runtime client", () => {
172
172
  const frame = {
173
173
  type: "layout-snapshot",
174
174
  topologyEpoch: 4,
175
+ windowLinks: {
176
+ liveSessionId: "live-session." + "a".repeat(20),
177
+ linkRevision: 1,
178
+ activeLinkId: "window-link." + "a".repeat(32),
179
+ links: [
180
+ {
181
+ linkId: "window-link." + "a".repeat(32),
182
+ semanticWindowId: "window.one",
183
+ displayIndex: 0,
184
+ },
185
+ ],
186
+ },
175
187
  layouts: [
176
188
  {
177
189
  type: "layout",
@@ -203,6 +215,64 @@ describe("semantic pane-stream runtime client", () => {
203
215
  expect(socket.closed).toMatchObject({ code: 1008, reason: "protocol-error" });
204
216
  });
205
217
 
218
+ it.each(["session", "revision", "membership", "index"])(
219
+ "rejects %s link authority changes before publication",
220
+ async (change) => {
221
+ const socket = new FakeSocket();
222
+ acceptInteractiveHandshake(socket);
223
+ const published = mock();
224
+ const faults = mock();
225
+ const client = await openPaneStreamRuntimeClient(
226
+ options(socket, { onLayoutSnapshot: published, onFault: faults }),
227
+ );
228
+ const firstId = `window-link.${"a".repeat(32)}`;
229
+ const secondId = `window-link.${"b".repeat(32)}`;
230
+ const windowLinks = {
231
+ liveSessionId: `live-session.${"a".repeat(20)}`,
232
+ linkRevision: 3,
233
+ activeLinkId: firstId,
234
+ links: [
235
+ { linkId: firstId, semanticWindowId: "window.one", displayIndex: 0 },
236
+ { linkId: secondId, semanticWindowId: "window.one", displayIndex: 1 },
237
+ ],
238
+ };
239
+ const frame = {
240
+ type: "layout-snapshot",
241
+ topologyEpoch: 1,
242
+ windowLinks,
243
+ layouts: [
244
+ {
245
+ type: "layout",
246
+ semanticWindowId: "window.one",
247
+ windowName: "work",
248
+ currentWindow: true,
249
+ cols: 80,
250
+ rows: 24,
251
+ zoomed: false,
252
+ paneBorderStatus: "off",
253
+ panes: [{ pane: "pane.editor", left: 0, top: 0, width: 80, height: 24, active: true }],
254
+ },
255
+ ],
256
+ };
257
+ socket.message(frame);
258
+ socket.message({
259
+ ...frame,
260
+ topologyEpoch: 2,
261
+ windowLinks: { ...windowLinks, activeLinkId: secondId },
262
+ });
263
+ expect(published).toHaveBeenCalledTimes(2);
264
+ const changed = structuredClone(windowLinks);
265
+ if (change === "session") changed.liveSessionId = `live-session.${"b".repeat(20)}`;
266
+ if (change === "revision") changed.linkRevision = 2;
267
+ if (change === "membership") changed.links.pop();
268
+ if (change === "index") changed.links[1]!.displayIndex = 9;
269
+ socket.message({ ...frame, topologyEpoch: 3, windowLinks: changed });
270
+ expect(published).toHaveBeenCalledTimes(2);
271
+ expect(faults).toHaveBeenCalledTimes(1);
272
+ client.close();
273
+ },
274
+ );
275
+
206
276
  it("calibrates five bounded clock probes before readiness and never exposes raw origins", async () => {
207
277
  const socket = new FakeSocket();
208
278
  const calibrations: unknown[] = [];
@@ -213,7 +283,7 @@ describe("semantic pane-stream runtime client", () => {
213
283
  queueMicrotask(() =>
214
284
  socket.message({
215
285
  type: "ready",
216
- protocolVersion: 1,
286
+ protocolVersion: 2,
217
287
  daemonInstanceId: INSTANCE,
218
288
  requestId: REQUEST,
219
289
  connectionClientId: "tui:one",
@@ -311,7 +381,7 @@ describe("semantic pane-stream runtime client", () => {
311
381
  queueMicrotask(() =>
312
382
  socket.message({
313
383
  type: "ready",
314
- protocolVersion: 1,
384
+ protocolVersion: 2,
315
385
  daemonInstanceId: INSTANCE,
316
386
  requestId: REQUEST,
317
387
  panes: ["pane.editor"],
@@ -367,7 +437,7 @@ describe("semantic pane-stream runtime client", () => {
367
437
  queueMicrotask(() =>
368
438
  socket.message({
369
439
  type: "ready",
370
- protocolVersion: 1,
440
+ protocolVersion: 2,
371
441
  daemonInstanceId: INSTANCE,
372
442
  requestId: REQUEST,
373
443
  panes: ["pane.editor"],
@@ -431,7 +501,7 @@ describe("semantic pane-stream runtime client", () => {
431
501
  queueMicrotask(() =>
432
502
  socket.message({
433
503
  type: "ready",
434
- protocolVersion: 1,
504
+ protocolVersion: 2,
435
505
  daemonInstanceId: INSTANCE,
436
506
  requestId: REQUEST,
437
507
  panes: ["pane.editor"],
@@ -478,7 +548,7 @@ describe("semantic pane-stream runtime client", () => {
478
548
  queueMicrotask(() =>
479
549
  socket.message({
480
550
  type: "ready",
481
- protocolVersion: 1,
551
+ protocolVersion: 2,
482
552
  daemonInstanceId: INSTANCE,
483
553
  requestId: REQUEST,
484
554
  panes: ["pane.editor"],
@@ -527,7 +597,7 @@ describe("semantic pane-stream runtime client", () => {
527
597
  queueMicrotask(() =>
528
598
  socket.message({
529
599
  type: "ready",
530
- protocolVersion: 1,
600
+ protocolVersion: 2,
531
601
  daemonInstanceId: INSTANCE,
532
602
  requestId: REQUEST,
533
603
  panes: ["pane.editor"],
@@ -578,7 +648,7 @@ describe("semantic pane-stream runtime client", () => {
578
648
  queueMicrotask(() =>
579
649
  failOpenSocket.message({
580
650
  type: "ready",
581
- protocolVersion: 1,
651
+ protocolVersion: 2,
582
652
  daemonInstanceId: INSTANCE,
583
653
  requestId: REQUEST,
584
654
  panes: ["pane.editor"],
@@ -657,7 +727,7 @@ describe("semantic pane-stream runtime client", () => {
657
727
  queueMicrotask(() =>
658
728
  socket.message({
659
729
  type: "ready",
660
- protocolVersion: 1,
730
+ protocolVersion: 2,
661
731
  daemonInstanceId: INSTANCE,
662
732
  requestId: REQUEST,
663
733
  panes: ["pane.editor"],
@@ -706,7 +776,7 @@ describe("semantic pane-stream runtime client", () => {
706
776
  queueMicrotask(() =>
707
777
  duplicateSocket.message({
708
778
  type: "ready",
709
- protocolVersion: 1,
779
+ protocolVersion: 2,
710
780
  daemonInstanceId: INSTANCE,
711
781
  requestId: REQUEST,
712
782
  panes: ["pane.editor"],
@@ -758,7 +828,7 @@ describe("semantic pane-stream runtime client", () => {
758
828
  queueMicrotask(() =>
759
829
  clockSocket.message({
760
830
  type: "ready",
761
- protocolVersion: 1,
831
+ protocolVersion: 2,
762
832
  daemonInstanceId: INSTANCE,
763
833
  requestId: REQUEST,
764
834
  panes: ["pane.editor"],
@@ -1152,7 +1222,7 @@ describe("semantic pane-stream runtime client", () => {
1152
1222
  queueMicrotask(() =>
1153
1223
  socket.message({
1154
1224
  type: "ready",
1155
- protocolVersion: 1,
1225
+ protocolVersion: 2,
1156
1226
  daemonInstanceId: INSTANCE,
1157
1227
  requestId: REQUEST,
1158
1228
  panes: ["pane.editor"],
@@ -1200,7 +1270,7 @@ describe("semantic pane-stream runtime client", () => {
1200
1270
  queueMicrotask(() =>
1201
1271
  socket.message({
1202
1272
  type: "ready",
1203
- protocolVersion: 1,
1273
+ protocolVersion: 2,
1204
1274
  daemonInstanceId: INSTANCE,
1205
1275
  requestId: REQUEST,
1206
1276
  connectionClientId: "tui:one",
@@ -1278,7 +1348,7 @@ describe("semantic pane-stream runtime client", () => {
1278
1348
  queueMicrotask(() =>
1279
1349
  socket.message({
1280
1350
  type: "ready",
1281
- protocolVersion: 1,
1351
+ protocolVersion: 2,
1282
1352
  daemonInstanceId: INSTANCE,
1283
1353
  requestId: REQUEST,
1284
1354
  panes: ["pane.editor"],
@@ -1324,7 +1394,7 @@ describe("semantic pane-stream runtime client", () => {
1324
1394
  queueMicrotask(() =>
1325
1395
  socket.message({
1326
1396
  type: "ready",
1327
- protocolVersion: 1,
1397
+ protocolVersion: 2,
1328
1398
  daemonInstanceId: INSTANCE,
1329
1399
  requestId: REQUEST,
1330
1400
  panes: ["pane.editor"],
@@ -1428,7 +1498,7 @@ describe("semantic pane-stream runtime client", () => {
1428
1498
  queueMicrotask(() =>
1429
1499
  socket.message({
1430
1500
  type: "ready",
1431
- protocolVersion: 1,
1501
+ protocolVersion: 2,
1432
1502
  daemonInstanceId: INSTANCE,
1433
1503
  requestId: REQUEST,
1434
1504
  panes: ["pane.editor"],
@@ -1498,7 +1568,7 @@ describe("semantic pane-stream runtime client", () => {
1498
1568
  queueMicrotask(() =>
1499
1569
  socket.message({
1500
1570
  type: "ready",
1501
- protocolVersion: 1,
1571
+ protocolVersion: 2,
1502
1572
  daemonInstanceId: INSTANCE,
1503
1573
  requestId: REQUEST,
1504
1574
  panes: ["pane.editor"],
@@ -1587,7 +1657,7 @@ describe("semantic pane-stream runtime client", () => {
1587
1657
  queueMicrotask(() =>
1588
1658
  socket.message({
1589
1659
  type: "ready",
1590
- protocolVersion: 1,
1660
+ protocolVersion: 2,
1591
1661
  daemonInstanceId: INSTANCE,
1592
1662
  requestId: REQUEST,
1593
1663
  panes: ["pane.editor"],
@@ -1628,7 +1698,7 @@ describe("semantic pane-stream runtime client", () => {
1628
1698
  queueMicrotask(() =>
1629
1699
  socket.message({
1630
1700
  type: "ready",
1631
- protocolVersion: 1,
1701
+ protocolVersion: 2,
1632
1702
  daemonInstanceId: INSTANCE,
1633
1703
  requestId: REQUEST,
1634
1704
  panes: ["pane.editor"],
@@ -1691,7 +1761,7 @@ describe("semantic pane-stream runtime client", () => {
1691
1761
  queueMicrotask(() =>
1692
1762
  socket.message({
1693
1763
  type: "ready",
1694
- protocolVersion: 1,
1764
+ protocolVersion: 2,
1695
1765
  daemonInstanceId: INSTANCE,
1696
1766
  requestId: REQUEST,
1697
1767
  panes: ["pane.editor"],
@@ -1733,7 +1803,7 @@ describe("semantic pane-stream runtime client", () => {
1733
1803
  queueMicrotask(() =>
1734
1804
  socket.message({
1735
1805
  type: "ready",
1736
- protocolVersion: 1,
1806
+ protocolVersion: 2,
1737
1807
  daemonInstanceId: INSTANCE,
1738
1808
  requestId: REQUEST,
1739
1809
  panes: ["pane.editor"],
@@ -1961,7 +2031,7 @@ describe("semantic pane-stream runtime client", () => {
1961
2031
  queueMicrotask(() =>
1962
2032
  socket.message({
1963
2033
  type: "ready",
1964
- protocolVersion: 1,
2034
+ protocolVersion: 2,
1965
2035
  daemonInstanceId: INSTANCE,
1966
2036
  requestId: REQUEST,
1967
2037
  panes: ["pane.editor"],
@@ -2055,7 +2125,7 @@ describe("semantic pane-stream runtime client", () => {
2055
2125
  queueMicrotask(() =>
2056
2126
  socket.message({
2057
2127
  type: "ready",
2058
- protocolVersion: 1,
2128
+ protocolVersion: 2,
2059
2129
  daemonInstanceId: INSTANCE,
2060
2130
  requestId: REQUEST,
2061
2131
  panes: ["pane.editor"],
@@ -2202,7 +2272,7 @@ describe("semantic pane-stream runtime client", () => {
2202
2272
  queueMicrotask(() =>
2203
2273
  socket.message({
2204
2274
  type: "ready",
2205
- protocolVersion: 1,
2275
+ protocolVersion: 2,
2206
2276
  daemonInstanceId: INSTANCE,
2207
2277
  requestId: REQUEST,
2208
2278
  panes: ["pane.editor"],
@@ -2279,7 +2349,7 @@ describe("semantic pane-stream runtime client", () => {
2279
2349
  queueMicrotask(() =>
2280
2350
  socket.message({
2281
2351
  type: "ready",
2282
- protocolVersion: 1,
2352
+ protocolVersion: 2,
2283
2353
  daemonInstanceId: INSTANCE,
2284
2354
  requestId: REQUEST,
2285
2355
  panes: ["pane.editor"],
@@ -2340,7 +2410,7 @@ describe("semantic pane-stream runtime client", () => {
2340
2410
  queueMicrotask(() =>
2341
2411
  socket.message({
2342
2412
  type: "ready",
2343
- protocolVersion: 1,
2413
+ protocolVersion: 2,
2344
2414
  daemonInstanceId: INSTANCE,
2345
2415
  requestId: REQUEST,
2346
2416
  panes: ["pane.editor"],
@@ -2420,7 +2490,7 @@ describe("semantic pane-stream runtime client", () => {
2420
2490
  queueMicrotask(() =>
2421
2491
  socket.message({
2422
2492
  type: "ready",
2423
- protocolVersion: 1,
2493
+ protocolVersion: 2,
2424
2494
  daemonInstanceId: INSTANCE,
2425
2495
  requestId: REQUEST,
2426
2496
  panes: ["pane.editor"],
@@ -2493,7 +2563,7 @@ describe("semantic pane-stream runtime client", () => {
2493
2563
  queueMicrotask(() =>
2494
2564
  socket.message({
2495
2565
  type: "ready",
2496
- protocolVersion: 1,
2566
+ protocolVersion: 2,
2497
2567
  daemonInstanceId: INSTANCE,
2498
2568
  requestId: REQUEST,
2499
2569
  panes: ["pane.editor"],
@@ -2546,7 +2616,7 @@ describe("semantic pane-stream runtime client", () => {
2546
2616
  queueMicrotask(() =>
2547
2617
  socket.message({
2548
2618
  type: "ready",
2549
- protocolVersion: 1,
2619
+ protocolVersion: 2,
2550
2620
  daemonInstanceId: INSTANCE,
2551
2621
  requestId: REQUEST,
2552
2622
  panes: ["pane.editor"],
@@ -2567,7 +2637,7 @@ describe("semantic pane-stream runtime client", () => {
2567
2637
  queueMicrotask(() =>
2568
2638
  socket.message({
2569
2639
  type: "ready",
2570
- protocolVersion: 1,
2640
+ protocolVersion: 2,
2571
2641
  daemonInstanceId: INSTANCE,
2572
2642
  requestId: REQUEST,
2573
2643
  connectionClientId: "web:foreign",
@@ -2589,7 +2659,7 @@ describe("semantic pane-stream runtime client", () => {
2589
2659
  queueMicrotask(() =>
2590
2660
  socket.message({
2591
2661
  type: "ready",
2592
- protocolVersion: 1,
2662
+ protocolVersion: 2,
2593
2663
  daemonInstanceId: INSTANCE,
2594
2664
  requestId: REQUEST,
2595
2665
  panes: ["pane.editor"],
@@ -2648,7 +2718,7 @@ describe("semantic pane-stream runtime client", () => {
2648
2718
 
2649
2719
  socket.message({
2650
2720
  type: "error",
2651
- protocolVersion: 1,
2721
+ protocolVersion: 2,
2652
2722
  code: "topology-changed",
2653
2723
  retryable: true,
2654
2724
  });
@@ -2669,7 +2739,7 @@ describe("semantic pane-stream runtime client", () => {
2669
2739
  queueMicrotask(() =>
2670
2740
  socket.message({
2671
2741
  type: "ready",
2672
- protocolVersion: 1,
2742
+ protocolVersion: 2,
2673
2743
  daemonInstanceId: INSTANCE,
2674
2744
  requestId: REQUEST,
2675
2745
  panes: ["pane.editor"],
@@ -32,6 +32,7 @@ import {
32
32
  type TerminalDeliveryServerMessage,
33
33
  type TerminalDeliveryVisibility,
34
34
  type WorkspaceMultiplexerMutationResult,
35
+ type WindowLinkTopology,
35
36
  } from "@tmux-ide/contracts";
36
37
  import {
37
38
  calibratePaneStreamClocks,
@@ -449,6 +450,7 @@ export async function connectIssuedPaneStreamRuntimeClient(
449
450
  let closed = false;
450
451
  let verified = false;
451
452
  let layoutTopologyEpoch = -1;
453
+ let windowLinks: WindowLinkTopology | null = null;
452
454
  let resolveReady!: (client: PaneStreamRuntimeClient) => void;
453
455
  let rejectReady!: (error: Error) => void;
454
456
  const ready = new Promise<PaneStreamRuntimeClient>((resolve, reject) => {
@@ -1446,6 +1448,30 @@ export async function connectIssuedPaneStreamRuntimeClient(
1446
1448
  if (frame.type === "layout-snapshot") {
1447
1449
  if (frame.topologyEpoch <= layoutTopologyEpoch)
1448
1450
  return fail("Pane-stream layout topology epoch did not advance");
1451
+ const incoming = frame.windowLinks;
1452
+ if (windowLinks) {
1453
+ if (
1454
+ incoming.liveSessionId !== windowLinks.liveSessionId ||
1455
+ incoming.linkRevision < windowLinks.linkRevision
1456
+ )
1457
+ return fail("Pane-stream window link authority regressed or changed session");
1458
+ if (incoming.linkRevision === windowLinks.linkRevision) {
1459
+ const previous = new Map(windowLinks.links.map((link) => [link.linkId, link]));
1460
+ if (
1461
+ incoming.links.length !== previous.size ||
1462
+ incoming.links.some((link) => {
1463
+ const observed = previous.get(link.linkId);
1464
+ return (
1465
+ !observed ||
1466
+ observed.semanticWindowId !== link.semanticWindowId ||
1467
+ observed.displayIndex !== link.displayIndex
1468
+ );
1469
+ })
1470
+ )
1471
+ return fail("Pane-stream link membership changed without a revision advance");
1472
+ }
1473
+ }
1474
+ windowLinks = incoming;
1449
1475
  layoutTopologyEpoch = frame.topologyEpoch;
1450
1476
  }
1451
1477
  routeFrame(options, frame, fail);