tmux-ide 2.9.2 → 2.9.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (263) hide show
  1. package/README.md +1 -1
  2. package/bin/cli.js +2071 -625
  3. package/package.json +14 -5
  4. package/packages/contracts/src/desktop-host.ts +3 -0
  5. package/packages/contracts/src/saved-machines.ts +19 -0
  6. package/packages/contracts/src/terminal-replica.ts +1 -1
  7. package/packages/core/package.json +3 -2
  8. package/packages/core/src/terminal-delivery.ts +51 -3
  9. package/packages/core/src/terminal-fnv64-wasm.ts +4 -3
  10. package/packages/core/src/terminal-replica-hash-cache.ts +129 -31
  11. package/packages/core/src/terminal-replica-owned-row.ts +27 -0
  12. package/packages/core/src/terminal-replica.ts +13 -28
  13. package/packages/daemon/dist/command-center/daemon-fleet-facts-observer.js +96 -10
  14. package/packages/daemon/dist/command-center/resources/saved-machine-route.js +40 -1
  15. package/packages/daemon/dist/command-center/ws-events.js +5 -24
  16. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +8 -0
  17. package/packages/daemon/dist/lib/canonical-daemon.js +28 -2
  18. package/packages/daemon/dist/lib/daemon-embed.js +1 -1
  19. package/packages/daemon/dist/lib/daemon-service-manager.js +122 -0
  20. package/packages/daemon/dist/lib/daemon-service-plan.js +127 -0
  21. package/packages/daemon/dist/lib/daemon-service.js +259 -0
  22. package/packages/daemon/dist/lib/install-origin.js +2 -0
  23. package/packages/daemon/dist/lib/local-fleet-request.js +9 -3
  24. package/packages/daemon/dist/lib/log.js +6 -10
  25. package/packages/daemon/dist/lib/managed-installer-update.js +67 -0
  26. package/packages/daemon/dist/lib/{tui-download-lock.js → private-operation-lock.js} +11 -11
  27. package/packages/daemon/dist/lib/remote-daemon-info.js +3 -2
  28. package/packages/daemon/dist/lib/remote-daemon-lifecycle.js +4 -4
  29. package/packages/daemon/dist/lib/remote-tmux-command.js +10 -0
  30. package/packages/daemon/dist/lib/runtime-namespace.js +28 -16
  31. package/packages/daemon/dist/lib/saved-machines.js +40 -2
  32. package/packages/daemon/dist/lib/ssh-daemon-transport.js +24 -9
  33. package/packages/daemon/dist/lib/tmux-server-owner.js +1 -1
  34. package/packages/daemon/dist/lib/tmux-server-registration.js +21 -3
  35. package/packages/daemon/dist/lib/tui-binary.js +2 -2
  36. package/packages/daemon/dist/lib/update.js +18 -0
  37. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +24 -1
  38. package/packages/daemon/dist/machines.js +40 -4
  39. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Info.plist +2 -2
  40. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/MacOS/tmux-ide-notifier +0 -0
  41. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/Resources/Assets.car +0 -0
  42. package/packages/daemon/dist/native/TmuxIdeNotifier.app/Contents/_CodeSignature/CodeResources +2 -2
  43. package/packages/daemon/dist/terminal/attachments/direct-websocket.js +4 -1
  44. package/packages/daemon/dist/terminal/attachments/native-runtime.js +22 -13
  45. package/packages/daemon/dist/terminal/mirror/mirror-service.js +101 -3
  46. package/packages/daemon/dist/terminal/mirror/registered-window-guard.js +62 -0
  47. package/packages/daemon/dist/terminal/mirror/session-channel.js +75 -4
  48. package/packages/daemon/dist/terminal/mirror/shared-window-index.js +45 -0
  49. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +11 -1
  50. package/packages/daemon/dist/terminal/session-runtime/multiplexer-backend.js +1 -0
  51. package/packages/daemon/dist/terminal/session-runtime/registry.js +56 -7
  52. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +3 -1
  53. package/packages/daemon/dist/tui/mirror/dialog-model.js +2 -35
  54. package/packages/daemon/dist/tui/mirror/runtime/application-daemon-authority-owner.js +8 -0
  55. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +20 -2
  56. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +7 -1
  57. package/packages/daemon/dist/tui/mirror/runtime/application-machine-authority.js +59 -9
  58. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +27 -19
  59. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +23 -18
  60. package/packages/daemon/dist/tui/mirror/runtime/application-machine-startup.js +31 -2
  61. package/packages/daemon/dist/tui/mirror/runtime/application-palette-search-owner.js +7 -3
  62. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +3 -0
  63. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-input-ingress.js +146 -2
  64. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +201 -50
  65. package/packages/daemon/dist/tui/mirror/runtime/bounded-performance-record-writer.js +50 -10
  66. package/packages/daemon/dist/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.js +6 -5
  67. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +3 -0
  68. package/packages/daemon/dist/tui/mirror/runtime/workspace-terminal-fast-lane.js +8 -2
  69. package/packages/daemon/dist/tui/mirror/terminal-text.js +35 -0
  70. package/packages/daemon/dist/tui/mirror/workspace/connection-feedback.js +5 -3
  71. package/packages/daemon/src/command-center/daemon-fleet-facts-observer.ts +100 -10
  72. package/packages/daemon/src/command-center/resources/saved-machine-route.ts +48 -1
  73. package/packages/daemon/src/command-center/ws-events.ts +5 -25
  74. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +12 -0
  75. package/packages/daemon/src/lib/canonical-daemon.ts +44 -2
  76. package/packages/daemon/src/lib/daemon-embed.ts +1 -0
  77. package/packages/daemon/src/lib/daemon-service-manager.ts +152 -0
  78. package/packages/daemon/src/lib/daemon-service-plan.ts +153 -0
  79. package/packages/daemon/src/lib/daemon-service.ts +301 -0
  80. package/packages/daemon/src/lib/install-origin.ts +5 -0
  81. package/packages/daemon/src/lib/local-fleet-request.ts +19 -3
  82. package/packages/daemon/src/lib/log.ts +13 -9
  83. package/packages/daemon/src/lib/managed-installer-update.ts +79 -0
  84. package/packages/daemon/src/lib/{tui-download-lock.ts → private-operation-lock.ts} +15 -11
  85. package/packages/daemon/src/lib/remote-daemon-info.ts +3 -3
  86. package/packages/daemon/src/lib/remote-daemon-lifecycle.ts +4 -4
  87. package/packages/daemon/src/lib/remote-tmux-command.ts +12 -0
  88. package/packages/daemon/src/lib/runtime-namespace.ts +27 -18
  89. package/packages/daemon/src/lib/saved-machines.ts +40 -1
  90. package/packages/daemon/src/lib/ssh-daemon-transport.ts +25 -7
  91. package/packages/daemon/src/lib/tmux-server-owner.ts +1 -0
  92. package/packages/daemon/src/lib/tmux-server-registration.ts +21 -3
  93. package/packages/daemon/src/lib/tui-binary.ts +2 -2
  94. package/packages/daemon/src/lib/update.ts +24 -0
  95. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +22 -0
  96. package/packages/daemon/src/machines.ts +53 -5
  97. package/packages/daemon/src/terminal/attachments/direct-websocket.ts +8 -2
  98. package/packages/daemon/src/terminal/attachments/native-runtime.ts +28 -13
  99. package/packages/daemon/src/terminal/mirror/mirror-service.ts +105 -3
  100. package/packages/daemon/src/terminal/mirror/registered-window-guard.ts +66 -0
  101. package/packages/daemon/src/terminal/mirror/session-channel.ts +92 -4
  102. package/packages/daemon/src/terminal/mirror/shared-window-index.ts +42 -0
  103. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +12 -1
  104. package/packages/daemon/src/terminal/session-runtime/multiplexer-backend.ts +2 -0
  105. package/packages/daemon/src/terminal/session-runtime/registry.ts +70 -8
  106. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +3 -1
  107. package/packages/daemon/src/tui/mirror/dialog-model.ts +2 -32
  108. package/packages/daemon/src/tui/mirror/runtime/application-daemon-authority-owner.ts +8 -0
  109. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +19 -2
  110. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +6 -1
  111. package/packages/daemon/src/tui/mirror/runtime/application-machine-authority.ts +60 -11
  112. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +39 -33
  113. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +31 -23
  114. package/packages/daemon/src/tui/mirror/runtime/application-machine-startup.ts +38 -2
  115. package/packages/daemon/src/tui/mirror/runtime/application-palette-search-owner.ts +7 -4
  116. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +3 -0
  117. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +1 -1
  118. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +47 -46
  119. package/packages/daemon/src/tui/mirror/runtime/application-terminal-input-ingress.ts +165 -2
  120. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +207 -53
  121. package/packages/daemon/src/tui/mirror/runtime/bounded-performance-record-writer.ts +49 -9
  122. package/packages/daemon/src/tui/mirror/runtime/terminal-fast-lane-renderer-adapter.ts +6 -5
  123. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +4 -0
  124. package/packages/daemon/src/tui/mirror/runtime/workspace-terminal-fast-lane.ts +8 -3
  125. package/packages/daemon/src/tui/mirror/terminal-text.ts +32 -0
  126. package/packages/daemon/src/tui/mirror/workspace/connection-feedback.ts +6 -3
  127. package/packages/daemon-client/src/fleet-connection-status.ts +10 -1
  128. package/packages/daemon-client/src/workspace-client.ts +4 -1
  129. package/packages/contracts/src/__tests__/actions-contract.test.ts +0 -119
  130. package/packages/contracts/src/__tests__/agent-graph-overlay.test.ts +0 -428
  131. package/packages/contracts/src/__tests__/app-window-mutation.test.ts +0 -51
  132. package/packages/contracts/src/__tests__/app-window-state.test.ts +0 -34
  133. package/packages/contracts/src/__tests__/application-shell.test.ts +0 -645
  134. package/packages/contracts/src/__tests__/automation-operations.test.ts +0 -78
  135. package/packages/contracts/src/__tests__/causal-cell.test.ts +0 -47
  136. package/packages/contracts/src/__tests__/client-view-state.test.ts +0 -115
  137. package/packages/contracts/src/__tests__/cohesion-fixture.test.ts +0 -106
  138. package/packages/contracts/src/__tests__/commands.test.ts +0 -86
  139. package/packages/contracts/src/__tests__/control.test.ts +0 -154
  140. package/packages/contracts/src/__tests__/daemon-events.test.ts +0 -405
  141. package/packages/contracts/src/__tests__/daemon-resources.test.ts +0 -143
  142. package/packages/contracts/src/__tests__/daemon-wire.test.ts +0 -149
  143. package/packages/contracts/src/__tests__/desktop-host.test.ts +0 -269
  144. package/packages/contracts/src/__tests__/desktop-missions.test.ts +0 -215
  145. package/packages/contracts/src/__tests__/experience-conformance.test.ts +0 -153
  146. package/packages/contracts/src/__tests__/experience-kernel-boundary.test.ts +0 -113
  147. package/packages/contracts/src/__tests__/experience-shell.test.ts +0 -65
  148. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v2.json +0 -35
  149. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +0 -35
  150. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v4.json +0 -35
  151. package/packages/contracts/src/__tests__/fixtures/scoped-receipt.ts +0 -62
  152. package/packages/contracts/src/__tests__/fleet-agent-graph.test.ts +0 -231
  153. package/packages/contracts/src/__tests__/fleet-catalog.test.ts +0 -270
  154. package/packages/contracts/src/__tests__/fleet-lifecycle.test.ts +0 -62
  155. package/packages/contracts/src/__tests__/focus-overlay.test.ts +0 -110
  156. package/packages/contracts/src/__tests__/ide-config.test.ts +0 -64
  157. package/packages/contracts/src/__tests__/interaction-evidence.test.ts +0 -288
  158. package/packages/contracts/src/__tests__/interaction-receipt-v2.test.ts +0 -72
  159. package/packages/contracts/src/__tests__/issue-error.test.ts +0 -78
  160. package/packages/contracts/src/__tests__/mission-projections.test.ts +0 -186
  161. package/packages/contracts/src/__tests__/missions.test.ts +0 -272
  162. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +0 -242
  163. package/packages/contracts/src/__tests__/pane-appearance.test.ts +0 -146
  164. package/packages/contracts/src/__tests__/pane-read-result.test.ts +0 -55
  165. package/packages/contracts/src/__tests__/pane-stream.test.ts +0 -536
  166. package/packages/contracts/src/__tests__/pane-widget-marker.test.ts +0 -327
  167. package/packages/contracts/src/__tests__/performance-metrics.test.ts +0 -73
  168. package/packages/contracts/src/__tests__/performance-qualification.test.ts +0 -137
  169. package/packages/contracts/src/__tests__/rich-card-widget.test.ts +0 -29
  170. package/packages/contracts/src/__tests__/session-runtime.test.ts +0 -512
  171. package/packages/contracts/src/__tests__/startup-readiness.test.ts +0 -409
  172. package/packages/contracts/src/__tests__/terminal-attachment-stream.test.ts +0 -72
  173. package/packages/contracts/src/__tests__/terminal-attachments.test.ts +0 -329
  174. package/packages/contracts/src/__tests__/terminal-delivery.test.ts +0 -88
  175. package/packages/contracts/src/__tests__/terminal-runtime-inventory.test.ts +0 -68
  176. package/packages/contracts/src/__tests__/terminals.test.ts +0 -87
  177. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +0 -166
  178. package/packages/contracts/src/__tests__/transport-contract-loading.test.ts +0 -91
  179. package/packages/contracts/src/__tests__/visual-tokens.test.ts +0 -224
  180. package/packages/contracts/src/__tests__/window-links.test.ts +0 -243
  181. package/packages/contracts/src/__tests__/workspace-changes-resource.test.ts +0 -317
  182. package/packages/contracts/src/__tests__/workspace-changes-view.test.ts +0 -122
  183. package/packages/contracts/src/__tests__/workspace-config.test.ts +0 -294
  184. package/packages/contracts/src/__tests__/workspace-files-resource.test.ts +0 -261
  185. package/packages/contracts/src/__tests__/workspace-files-tree.test.ts +0 -147
  186. package/packages/contracts/src/__tests__/workspace-pane-creation.test.ts +0 -249
  187. package/packages/contracts/src/__tests__/workspace-promotion.test.ts +0 -94
  188. package/packages/contracts/src/__tests__/workspace-resource-identity.test.ts +0 -90
  189. package/packages/contracts/src/daemon-resource-request.test.ts +0 -192
  190. package/packages/contracts/src/native-operation-identity.test.ts +0 -23
  191. package/packages/contracts/src/workspace-admission.test.ts +0 -21
  192. package/packages/contracts/src/workspace-catalog-resource.test.ts +0 -146
  193. package/packages/core/src/agent-provisioning.test.ts +0 -42
  194. package/packages/core/src/application-shell-session.test.ts +0 -105
  195. package/packages/core/src/interaction-evidence.test.ts +0 -134
  196. package/packages/core/src/interaction-flow.test.ts +0 -66
  197. package/packages/core/src/interaction-receipts.test.ts +0 -671
  198. package/packages/core/src/navigator.test.ts +0 -34
  199. package/packages/core/src/optimistic-projection-conformance.test.ts +0 -75
  200. package/packages/core/src/optimistic-projection.test.ts +0 -118
  201. package/packages/core/src/performance-metrics.test.ts +0 -121
  202. package/packages/core/src/performance-qualification.test.ts +0 -283
  203. package/packages/core/src/saved-machines.test.ts +0 -80
  204. package/packages/core/src/terminal-compact-cold.test.ts +0 -87
  205. package/packages/core/src/terminal-conformance.test.ts +0 -48
  206. package/packages/core/src/terminal-delivery-cache-lifetime.test.ts +0 -167
  207. package/packages/core/src/terminal-delivery-encode-cooperative.test.ts +0 -140
  208. package/packages/core/src/terminal-delivery-representation-hash.test.ts +0 -77
  209. package/packages/core/src/terminal-delivery.test.ts +0 -1471
  210. package/packages/core/src/terminal-fnv64-wasm.test.ts +0 -61
  211. package/packages/core/src/terminal-replica-cold.test.ts +0 -31
  212. package/packages/core/src/terminal-replica-cooperative.test.ts +0 -170
  213. package/packages/core/src/terminal-replica-hash-cache.test.ts +0 -283
  214. package/packages/core/src/terminal-replica.test.ts +0 -347
  215. package/packages/core/src/workspace-selection.test.ts +0 -63
  216. package/packages/daemon/dist/__tests__/support.js +0 -33
  217. package/packages/daemon/dist/lib/__tests__/fixtures/workspace-state-writer.js +0 -58
  218. package/packages/daemon/dist/lib/__tests__/installed-recovery-fixture.js +0 -400
  219. package/packages/daemon/dist/terminal/__tests__/MockPtyAdapter.js +0 -182
  220. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +0 -98
  221. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +0 -156
  222. package/packages/daemon/src/tui/mirror/__snapshots__/activity-surface-renderer.test.tsx.snap +0 -42
  223. package/packages/daemon/src/tui/mirror/__snapshots__/changes-terminal-surface-renderer.test.tsx.snap +0 -267
  224. package/packages/daemon/src/tui/mirror/__snapshots__/home-files-surface-renderer.test.tsx.snap +0 -267
  225. package/packages/daemon/src/tui/mirror/__snapshots__/missions-surface-renderer.test.tsx.snap +0 -134
  226. package/packages/daemon/src/tui/mirror/__snapshots__/recipes-gallery-renderer.test.tsx.snap +0 -141
  227. package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +0 -134
  228. package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +0 -87
  229. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/agent-terminal-canvas-renderer.test.tsx.snap +0 -348
  230. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +0 -134
  231. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/command-palette-surface-renderer.test.tsx.snap +0 -255
  232. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/pane-frame-renderer.test.tsx.snap +0 -525
  233. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/terminal-pane-header-polish-renderer.test.tsx.snap +0 -31
  234. package/packages/daemon/src/tui/mirror/workspace/__snapshots__/workbench-shell-renderer.test.tsx.snap +0 -134
  235. package/packages/daemon-client/src/application-shell-session.test.ts +0 -196
  236. package/packages/daemon-client/src/automation-client.test.ts +0 -214
  237. package/packages/daemon-client/src/bootstrap-coordinator.test.ts +0 -145
  238. package/packages/daemon-client/src/connection-supervisor.test.ts +0 -109
  239. package/packages/daemon-client/src/direct-application-shell-transport.test.ts +0 -941
  240. package/packages/daemon-client/src/first-latest-coordinator.test.ts +0 -175
  241. package/packages/daemon-client/src/fleet-dial-scheduler.test.ts +0 -114
  242. package/packages/daemon-client/src/fleet-environments.test.ts +0 -40
  243. package/packages/daemon-client/src/generation-bound-store-timers.test.ts +0 -74
  244. package/packages/daemon-client/src/owner-action-client.test.ts +0 -245
  245. package/packages/daemon-client/src/pane-stream-client.test.ts +0 -2841
  246. package/packages/daemon-client/src/pane-stream-clock-calibration.test.ts +0 -207
  247. package/packages/daemon-client/src/pane-stream-loopback-benchmark.test.ts +0 -51
  248. package/packages/daemon-client/src/push-resource-session.test.ts +0 -509
  249. package/packages/daemon-client/src/resource-replica.test.ts +0 -186
  250. package/packages/daemon-client/src/runtime-resource-ledger.test.ts +0 -18
  251. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +0 -114
  252. package/packages/daemon-client/src/terminal-fast-lane.test.ts +0 -835
  253. package/packages/daemon-client/src/tmux-server-client.test.ts +0 -279
  254. package/packages/daemon-client/src/tmux-server-interaction-events.test.ts +0 -230
  255. package/packages/daemon-client/src/workspace-catalog-v2.test.ts +0 -109
  256. package/packages/daemon-client/src/workspace-client-boundaries.test.ts +0 -59
  257. package/packages/daemon-client/src/workspace-client-operations.test.ts +0 -96
  258. package/packages/daemon-client/src/workspace-client.test.ts +0 -2912
  259. package/packages/daemon-client/src/workspace-event-supervisor.test.ts +0 -1086
  260. package/packages/daemon-client/src/workspace-pane-client.test.ts +0 -80
  261. package/packages/presentation/src/presentation-contract.test.ts +0 -63
  262. package/packages/tmux-bridge/src/runner.test.ts +0 -614
  263. package/packages/tmux-bridge/src/targeting.test.ts +0 -107
@@ -29,6 +29,154 @@ export function createApplicationTerminalInputIngress(
29
29
  ) {
30
30
  const pending = createApplicationPendingTerminalInputOwner();
31
31
  let ownsNote = false;
32
+ type RecoveryScope = {
33
+ owner: OpenTuiSessionOwner;
34
+ connection: NonNullable<OpenTuiGenerationHostSnapshot["connection"]>;
35
+ client: NonNullable<OpenTuiGenerationHostSnapshot["client"]>;
36
+ daemon: string;
37
+ clientGeneration: number;
38
+ session: string;
39
+ pane: string;
40
+ };
41
+ let recovery: {
42
+ scope: RecoveryScope;
43
+ inputs: Array<() => Promise<boolean>>;
44
+ weight: number;
45
+ admitted: number;
46
+ draining: boolean;
47
+ timer: ReturnType<typeof setTimeout>;
48
+ } | null = null;
49
+ const scope = (): RecoveryScope | null => {
50
+ const active = generation();
51
+ const owner = sessionOwner();
52
+ const pane = focusedPane();
53
+ const session = owner?.sessionName();
54
+ if (
55
+ !active ||
56
+ !["live", "rebinding"].includes(active.status) ||
57
+ !active.connection ||
58
+ !active.client ||
59
+ !active.daemonGeneration ||
60
+ !owner ||
61
+ !session ||
62
+ !pane
63
+ )
64
+ return null;
65
+ try {
66
+ const clientGeneration = active.client.getSnapshot().generation;
67
+ if (!Number.isSafeInteger(clientGeneration)) return null;
68
+ return {
69
+ owner,
70
+ connection: active.connection,
71
+ client: active.client,
72
+ daemon: active.daemonGeneration,
73
+ clientGeneration: clientGeneration!,
74
+ session: session!,
75
+ pane,
76
+ };
77
+ } catch {
78
+ return null;
79
+ }
80
+ };
81
+ const sameScope = (left: RecoveryScope, right: RecoveryScope | null): boolean =>
82
+ right !== null &&
83
+ left.owner === right.owner &&
84
+ left.connection === right.connection &&
85
+ left.client === right.client &&
86
+ left.daemon === right.daemon &&
87
+ left.clientGeneration === right.clientGeneration &&
88
+ left.session === right.session &&
89
+ left.pane === right.pane;
90
+ let recoveryVersion = 0;
91
+ const cancelRecovery = () => {
92
+ recoveryVersion++;
93
+ if (recovery) clearTimeout(recovery.timer);
94
+ recovery = null;
95
+ };
96
+ const recover = (
97
+ inputs: ReturnType<typeof terminalInputsForPaste>,
98
+ bytes: Uint8Array,
99
+ kind: "input" | "paste",
100
+ ): boolean => {
101
+ if (
102
+ pending.snapshot().sessionName !== null ||
103
+ (!recovery && generation()?.status !== "rebinding")
104
+ )
105
+ return false;
106
+ const target = scope();
107
+ if (!target) return false;
108
+ if (recovery && !sameScope(recovery.scope, target)) cancelRecovery();
109
+ if (!recovery) {
110
+ const timer = setTimeout(() => {
111
+ cancelRecovery();
112
+ setNote("terminal recovery timed out · queued input was not sent");
113
+ }, 5_000);
114
+ timer.unref?.();
115
+ recovery = { scope: target, inputs: [], weight: 0, admitted: 0, draining: false, timer };
116
+ }
117
+ const weight = bytes.byteLength;
118
+ if (recovery.admitted >= 64 || recovery.weight + weight > 1024 * 1024) {
119
+ setNote("terminal input queue full · wait for the session to connect");
120
+ return true;
121
+ }
122
+ recovery.weight += weight;
123
+ recovery.admitted++;
124
+ const version = recoveryVersion;
125
+ const parserOrigin = captureOrigin()
126
+ ? {
127
+ origin: kind === "paste" ? ("bracketed-paste" as const) : ("keyboard" as const),
128
+ payload: Buffer.from(bytes),
129
+ }
130
+ : undefined;
131
+ recovery.inputs.push(async () => {
132
+ for (const [index, input] of inputs.entries()) {
133
+ if (
134
+ version !== recoveryVersion ||
135
+ !sameScope(target, scope()) ||
136
+ generation()?.status !== "live" ||
137
+ !(await interaction.sendInputToPane(
138
+ target.pane,
139
+ input,
140
+ index === 0 ? parserOrigin : undefined,
141
+ ))
142
+ )
143
+ return false;
144
+ }
145
+ return true;
146
+ });
147
+ setNote(`${target.session} · terminal ${kind} queued`);
148
+ return true;
149
+ };
150
+ const flushRecovery = () => {
151
+ const waiting = recovery;
152
+ if (!waiting || waiting.draining) return;
153
+ if (!sameScope(waiting.scope, scope())) {
154
+ cancelRecovery();
155
+ setNote("terminal changed · queued input was not sent");
156
+ return;
157
+ }
158
+ if (generation()?.status !== "live" || !generation()?.fastLane) return;
159
+ waiting.draining = true;
160
+ const version = recoveryVersion;
161
+ void (async () => {
162
+ while (version === recoveryVersion && waiting.inputs.length > 0) {
163
+ const send = waiting.inputs.shift()!;
164
+ try {
165
+ if (await send()) continue;
166
+ } catch {
167
+ /* An uncertain send is never retried. */
168
+ }
169
+ if (version !== recoveryVersion) return;
170
+ cancelRecovery();
171
+ setNote("terminal unavailable · queued input was not sent");
172
+ return;
173
+ }
174
+ if (version === recoveryVersion) {
175
+ cancelRecovery();
176
+ setNote(null);
177
+ }
178
+ })();
179
+ };
32
180
  const captureOrigin = (): boolean =>
33
181
  Boolean(currentTuiPerformanceEventSink()?.terminalInputOrigin);
34
182
  const flush = (): void => {
@@ -60,9 +208,14 @@ export function createApplicationTerminalInputIngress(
60
208
  else setNote(`terminal unavailable · ${kind} was not sent`);
61
209
  };
62
210
 
211
+ const cancelInteractionInput = () => {
212
+ cancelRecovery();
213
+ interaction.cancelPendingInput();
214
+ };
63
215
  return {
64
216
  wrapStarter(starter: GenerationStarter): GenerationStarter {
65
217
  const start = async (...args: Parameters<GenerationStarter>) => {
218
+ cancelInteractionInput();
66
219
  const [sessionName] = args;
67
220
  const identity = pending.begin(sessionName);
68
221
  const result = await starter(...args);
@@ -76,18 +229,22 @@ export function createApplicationTerminalInputIngress(
76
229
  };
77
230
  return Object.assign(start, {
78
231
  cancel() {
232
+ cancelInteractionInput();
79
233
  pending.dispose();
80
234
  starter.cancel();
81
235
  },
82
236
  });
83
237
  },
84
- adopt(): void {
238
+ adopt(terminalsActive = true): void {
239
+ if (!terminalsActive) cancelInteractionInput();
85
240
  flush();
241
+ flushRecovery();
86
242
  },
87
243
  routeKey(event: OpenTuiKeyEvent): void {
88
244
  const active = generation();
89
245
  if (
90
246
  pending.snapshot().sessionName === null &&
247
+ recovery === null &&
91
248
  active?.status === "live" &&
92
249
  active.fastLane &&
93
250
  focusedPane()
@@ -95,7 +252,9 @@ export function createApplicationTerminalInputIngress(
95
252
  sendApplicationTerminalKey(interaction, event, captureOrigin());
96
253
  return;
97
254
  }
98
- if (!terminalInputForOpenTuiKey(event)) return;
255
+ const recoveryInput = terminalInputForOpenTuiKey(event);
256
+ if (!recoveryInput) return;
257
+ if (recover([recoveryInput], Buffer.from(recoveryInput.data, "utf8"), "input")) return;
99
258
  const copy = { name: event.name, ctrl: event.ctrl, meta: event.meta, shift: event.shift };
100
259
  noteAdmission(
101
260
  pending.enqueue(
@@ -117,6 +276,7 @@ export function createApplicationTerminalInputIngress(
117
276
  const active = generation();
118
277
  if (
119
278
  pending.snapshot().sessionName === null &&
279
+ recovery === null &&
120
280
  active?.status === "live" &&
121
281
  active.fastLane &&
122
282
  focusedPane()
@@ -124,6 +284,8 @@ export function createApplicationTerminalInputIngress(
124
284
  sendApplicationTerminalPaste(interaction, copy, captureOrigin());
125
285
  return;
126
286
  }
287
+ if (recover(terminalInputsForPaste(Buffer.from(copy).toString("utf8")), copy, "paste"))
288
+ return;
127
289
  noteAdmission(
128
290
  pending.enqueue(
129
291
  () => sendApplicationTerminalPaste(interaction, copy, captureOrigin()),
@@ -133,6 +295,7 @@ export function createApplicationTerminalInputIngress(
133
295
  );
134
296
  },
135
297
  dispose(): void {
298
+ cancelInteractionInput();
136
299
  pending.dispose();
137
300
  },
138
301
  };
@@ -79,6 +79,7 @@ export interface ApplicationTerminalInteractionControllerOptions {
79
79
 
80
80
  export interface ApplicationTerminalInteractionController {
81
81
  adoptGeneration(snapshot: OpenTuiGenerationHostSnapshot | null): void;
82
+ cancelPendingInput(): void;
82
83
  adoptLayout(snapshot: OpenTuiWorkspaceLayoutSnapshot): void;
83
84
  selectPane(paneId: string): void;
84
85
  selectWindowLink(target: WindowLinkTarget): Promise<void>;
@@ -173,7 +174,9 @@ export function createApplicationTerminalInteractionController(
173
174
  // Critical diagnostic retention still cannot own semantic interaction.
174
175
  }
175
176
  };
176
- let pendingWindowSwitch: {
177
+ type PendingWindowSwitch = {
178
+ readonly selectionKind: "pane" | "window-link";
179
+ readonly windowLink?: WindowLinkTarget;
177
180
  readonly traceId: string;
178
181
  readonly target: string;
179
182
  readonly paneId: string;
@@ -197,7 +200,8 @@ export function createApplicationTerminalInteractionController(
197
200
  layoutAtMicros: number | null;
198
201
  presentationAtMicros: number | null;
199
202
  };
200
- } | null = null;
203
+ };
204
+ let pendingWindowSwitch: PendingWindowSwitch | null = null;
201
205
  let pendingWindowRename: {
202
206
  readonly traceId: string;
203
207
  readonly target: string;
@@ -311,6 +315,70 @@ export function createApplicationTerminalInteractionController(
311
315
  adapter: NonNullable<OpenTuiGenerationHostSnapshot["adapter"]>;
312
316
  }> | null = null;
313
317
 
318
+ // Pane selection belongs to the control connection, which can outlive a
319
+ // terminal renderer rebuild. Never carry selection or input across reconnects.
320
+ const controlIdentity = (snapshot: OpenTuiGenerationHostSnapshot | null) => {
321
+ if (
322
+ !snapshot ||
323
+ !["live", "rebinding"].includes(snapshot.status) ||
324
+ !snapshot.connection ||
325
+ !snapshot.client ||
326
+ !snapshot.daemonGeneration
327
+ )
328
+ return null;
329
+ try {
330
+ const generation = snapshot.client.getSnapshot().generation;
331
+ if (!Number.isSafeInteger(generation)) return null;
332
+ return {
333
+ connection: snapshot.connection,
334
+ client: snapshot.client,
335
+ daemon: snapshot.daemonGeneration,
336
+ generation,
337
+ workspace: snapshot.connection.workspaceName,
338
+ };
339
+ } catch {
340
+ return null;
341
+ }
342
+ };
343
+ type ControlIdentity = NonNullable<ReturnType<typeof controlIdentity>>;
344
+ const sameControl = (left: ControlIdentity | null, right: ControlIdentity | null) =>
345
+ left !== null &&
346
+ right !== null &&
347
+ left.connection === right.connection &&
348
+ left.client === right.client &&
349
+ left.daemon === right.daemon &&
350
+ left.generation === right.generation &&
351
+ left.workspace === right.workspace;
352
+ let selectedControl: ControlIdentity | null = null;
353
+ const waitingInput = new Set<{
354
+ control: ControlIdentity;
355
+ weight: number;
356
+ settle: (ready: boolean) => void;
357
+ }>();
358
+ let waitingInputBytes = 0;
359
+ let releasingInput: Promise<void> | null = null;
360
+ const awaitInputRuntime = (weight: number): Promise<boolean> => {
361
+ const control = controlIdentity(options.generation());
362
+ if (!control || waitingInput.size >= 64 || waitingInputBytes + weight > 1024 * 1024)
363
+ return Promise.resolve(false);
364
+ return new Promise((resolve) => {
365
+ const entry = {
366
+ control,
367
+ weight,
368
+ settle: (ready: boolean) => {
369
+ if (!waitingInput.delete(entry)) return;
370
+ waitingInputBytes -= weight;
371
+ clearTimeout(timer);
372
+ resolve(ready);
373
+ },
374
+ };
375
+ const timer = setTimeout(() => entry.settle(false), 5000);
376
+ timer.unref?.();
377
+ waitingInput.add(entry);
378
+ waitingInputBytes += weight;
379
+ });
380
+ };
381
+
314
382
  const armDiagnosticWindowFrame = (pending: DiagnosticWindowFrameContext): void => {
315
383
  diagnosticWindowFrame = pending;
316
384
  diagnosticWindowFrameExpiresAtMicros = pending.startedAtMicros + 6_000_000;
@@ -633,11 +701,78 @@ export function createApplicationTerminalInteractionController(
633
701
  }
634
702
  };
635
703
 
704
+ const beginWindowSwitch = (pane: string, target: string, windowLink?: WindowLinkTarget): void => {
705
+ pendingWindowSwitch = null;
706
+ if (options.diagnosticsEnabled) {
707
+ try {
708
+ const active = options.generation();
709
+ const identity = active?.adapter?.paneCanonicalIdentity(pane);
710
+ const clientGeneration = active?.client?.getSnapshot().generation;
711
+ const startedAtMicros = diagnosticNowMicros();
712
+ if (
713
+ active?.status === "live" &&
714
+ active.daemonGeneration &&
715
+ identity &&
716
+ Number.isSafeInteger(clientGeneration) &&
717
+ startedAtMicros !== null
718
+ ) {
719
+ pendingWindowSwitch = {
720
+ selectionKind: windowLink ? "window-link" : "pane",
721
+ ...(windowLink ? { windowLink } : {}),
722
+ traceId: createTraceId(),
723
+ target,
724
+ paneId: pane,
725
+ startedAtMicros,
726
+ daemonGeneration: active.daemonGeneration,
727
+ clientGeneration: clientGeneration!,
728
+ rendererEpoch: active.rendererEpoch,
729
+ sourceEpoch: identity.sourceEpoch,
730
+ generation: identity.generation,
731
+ incarnation: identity.incarnation,
732
+ revision: identity.revision,
733
+ stateHash: identity.stateHash,
734
+ cols: identity.cols,
735
+ rows: identity.rows,
736
+ layoutPublished: false,
737
+ selectionApplied: false,
738
+ presentationPublished: false,
739
+ followUpRequested: false,
740
+ ...(options.detailedWindowSwitchTiming
741
+ ? {
742
+ timing: {
743
+ receiptAtMicros: null,
744
+ layoutAtMicros: null,
745
+ presentationAtMicros: null,
746
+ },
747
+ }
748
+ : {}),
749
+ };
750
+ armDiagnosticWindowFrame({ kind: "window-switch", ...pendingWindowSwitch });
751
+ diagnose("window-switch-start", { ...pendingWindowSwitch });
752
+ } else {
753
+ diagnose("window-switch-untracked", {
754
+ paneId: pane,
755
+ target,
756
+ status: active?.status ?? null,
757
+ daemonGeneration: active?.daemonGeneration ?? null,
758
+ rendererEpoch: active?.rendererEpoch ?? null,
759
+ clientGeneration: clientGeneration ?? null,
760
+ identity: identity ?? null,
761
+ laneCounters: active?.fastLane?.lane.counters() ?? null,
762
+ });
763
+ }
764
+ } catch {
765
+ pendingWindowSwitch = null;
766
+ }
767
+ }
768
+ };
769
+
636
770
  let linkSelection: {
637
771
  target: WindowLinkTarget;
638
772
  paneId: string;
639
773
  layout: OpenTuiWorkspaceLayoutSnapshot;
640
774
  received: boolean;
775
+ diagnostic: PendingWindowSwitch | null;
641
776
  resolve: (selected: boolean) => void;
642
777
  timer: ReturnType<typeof setTimeout>;
643
778
  } | null = null;
@@ -646,6 +781,19 @@ export function createApplicationTerminalInteractionController(
646
781
  if (!pending) return;
647
782
  linkSelection = null;
648
783
  clearTimeout(pending.timer);
784
+ const diagnostic = pending.diagnostic;
785
+ if (diagnostic && pendingWindowSwitch === diagnostic) {
786
+ if (selected) {
787
+ diagnostic.selectionApplied = true;
788
+ maybeRequestWindowSwitchFrame();
789
+ } else {
790
+ diagnoseCritical(`window-switch:${diagnostic.traceId}:failed`, "window-switch-failed", {
791
+ ...diagnostic,
792
+ reason: "window-link-selection-not-applied",
793
+ });
794
+ pendingWindowSwitch = null;
795
+ }
796
+ }
649
797
  pending.resolve(selected);
650
798
  };
651
799
  const reconcileLinkSelection = (snapshot: OpenTuiWorkspaceLayoutSnapshot) => {
@@ -678,13 +826,19 @@ export function createApplicationTerminalInteractionController(
678
826
  select: async (paneId) => {
679
827
  finishLinkSelection(false);
680
828
  const expected = liveSelectionTarget();
829
+ const selectionControl = controlIdentity(options.generation());
681
830
  const selecting = pendingWindowSwitch;
682
831
  const operationId = selecting?.traceId;
683
832
  const selectionFailure: { current: PaneSelectionFailure | null } = { current: null };
684
833
  const receipt = expected
685
834
  ? await selectTerminalPane(
686
835
  expected,
687
- liveSelectionTarget,
836
+ () => {
837
+ if (!selectionControl) return liveSelectionTarget();
838
+ return sameControl(selectionControl, controlIdentity(options.generation()))
839
+ ? expected
840
+ : null;
841
+ },
688
842
  paneId,
689
843
  operationId,
690
844
  (failure) => {
@@ -757,6 +911,14 @@ export function createApplicationTerminalInteractionController(
757
911
  },
758
912
  send: async (paneId, routed) => {
759
913
  const { input, parserOrigin } = routed;
914
+ const selectionVersion = paneInput.selectionVersion;
915
+ if (options.generation()?.status === "rebinding") {
916
+ if (!(await awaitInputRuntime(Buffer.byteLength(input.data, "utf8")))) return false;
917
+ } else if (releasingInput) {
918
+ await releasingInput;
919
+ }
920
+ if (paneInput.selectionVersion !== selectionVersion || paneInput.focusedPane !== paneId)
921
+ return false;
760
922
  const active = options.generation();
761
923
  if (active?.status !== "live" || !active.fastLane) return false;
762
924
  let fixtureEnabled: boolean;
@@ -838,7 +1000,31 @@ export function createApplicationTerminalInteractionController(
838
1000
  });
839
1001
 
840
1002
  return {
1003
+ cancelPendingInput() {
1004
+ finishLinkSelection(false);
1005
+ paneInput.invalidateSelection();
1006
+ for (const entry of waitingInput) entry.settle(false);
1007
+ },
841
1008
  adoptGeneration(snapshot) {
1009
+ const control = controlIdentity(snapshot);
1010
+ if (!sameControl(selectedControl, control)) {
1011
+ finishLinkSelection(false);
1012
+ paneInput.invalidateSelection();
1013
+ }
1014
+ selectedControl = control;
1015
+ if (waitingInput.size > 0 && snapshot?.status === "live" && snapshot.fastLane) {
1016
+ // Old waiters resume first; input admitted synchronously by the next
1017
+ // layout publication must not overtake them in this microtask turn.
1018
+ const barrier = Promise.resolve();
1019
+ releasingInput = barrier;
1020
+ queueMicrotask(() => {
1021
+ if (releasingInput === barrier) releasingInput = null;
1022
+ });
1023
+ }
1024
+ for (const entry of waitingInput) {
1025
+ if (!sameControl(entry.control, control)) entry.settle(false);
1026
+ else if (snapshot?.status === "live" && snapshot.fastLane) entry.settle(true);
1027
+ }
842
1028
  let next: typeof inputAuthorityIdentity = null;
843
1029
  if (
844
1030
  snapshot?.status === "live" &&
@@ -876,7 +1062,6 @@ export function createApplicationTerminalInteractionController(
876
1062
  next.adapter !== inputAuthorityIdentity.adapter);
877
1063
  if (next === null || replaced) {
878
1064
  finishLinkSelection(false);
879
- paneInput.invalidateSelection();
880
1065
  pendingWindowSwitch = null;
881
1066
  pendingWindowRename = null;
882
1067
  diagnosticWindowFrame = null;
@@ -1035,6 +1220,7 @@ export function createApplicationTerminalInteractionController(
1035
1220
  )
1036
1221
  return;
1037
1222
  finishLinkSelection(false);
1223
+ beginWindowSwitch(paneId, target.expectedSemanticWindowId, target);
1038
1224
  let resolve!: (selected: boolean) => void;
1039
1225
  const settled = new Promise<boolean>((done) => {
1040
1226
  resolve = done;
@@ -1044,6 +1230,7 @@ export function createApplicationTerminalInteractionController(
1044
1230
  paneId,
1045
1231
  layout: snapshot,
1046
1232
  received: false,
1233
+ diagnostic: pendingWindowSwitch,
1047
1234
  resolve,
1048
1235
  timer: setTimeout(() => {
1049
1236
  if (linkSelection === pending) finishLinkSelection(false);
@@ -1072,6 +1259,7 @@ export function createApplicationTerminalInteractionController(
1072
1259
  }
1073
1260
  const wrapper = (await expected.client.dispatch({
1074
1261
  kind: "semantic-intent",
1262
+ ...(pending.diagnostic ? { operationId: pending.diagnostic.traceId } : {}),
1075
1263
  intent: {
1076
1264
  verb: "workspace.window.link.select",
1077
1265
  workspaceName: expected.workspaceName,
@@ -1084,6 +1272,7 @@ export function createApplicationTerminalInteractionController(
1084
1272
  wrapper.kind !== "semantic-intent" ||
1085
1273
  !result.success ||
1086
1274
  result.data.operationId !== wrapper.operationId ||
1275
+ (pending.diagnostic && wrapper.operationId !== pending.diagnostic.traceId) ||
1087
1276
  result.data.daemonInstanceId !== expected.daemonGeneration ||
1088
1277
  result.data.workspaceName !== expected.workspaceName ||
1089
1278
  result.data.target.linkId !== target.linkId ||
@@ -1094,6 +1283,18 @@ export function createApplicationTerminalInteractionController(
1094
1283
  finishLinkSelection(false);
1095
1284
  return;
1096
1285
  }
1286
+ if (pending.diagnostic) {
1287
+ const receiptAtMicros = diagnosticNowMicros();
1288
+ if (pending.diagnostic.timing)
1289
+ pending.diagnostic.timing.receiptAtMicros = receiptAtMicros;
1290
+ diagnose("window-switch-receipt", {
1291
+ ...pending.diagnostic,
1292
+ operationId: wrapper.operationId,
1293
+ selected: true,
1294
+ applied: true,
1295
+ ...(options.detailedWindowSwitchTiming ? { phaseAtMicros: receiptAtMicros } : {}),
1296
+ });
1297
+ }
1097
1298
  pending.received = true;
1098
1299
  reconcileLinkSelection(pending.layout);
1099
1300
  await settled;
@@ -1335,55 +1536,7 @@ export function createApplicationTerminalInteractionController(
1335
1536
  const pane = next ? paneForWindow(next) : null;
1336
1537
  const target = next?.semanticWindowId ?? next?.windowName;
1337
1538
  if (!pane || !target) return;
1338
- if (options.diagnosticsEnabled) {
1339
- try {
1340
- const active = options.generation();
1341
- const identity = active?.adapter?.paneCanonicalIdentity(pane);
1342
- const clientGeneration = active?.client?.getSnapshot().generation;
1343
- const startedAtMicros = diagnosticNowMicros();
1344
- if (
1345
- active?.status === "live" &&
1346
- active.daemonGeneration &&
1347
- identity &&
1348
- Number.isSafeInteger(clientGeneration) &&
1349
- startedAtMicros !== null
1350
- ) {
1351
- pendingWindowSwitch = {
1352
- traceId: createTraceId(),
1353
- target,
1354
- paneId: pane,
1355
- startedAtMicros,
1356
- daemonGeneration: active.daemonGeneration,
1357
- clientGeneration: clientGeneration!,
1358
- rendererEpoch: active.rendererEpoch,
1359
- sourceEpoch: identity.sourceEpoch,
1360
- generation: identity.generation,
1361
- incarnation: identity.incarnation,
1362
- revision: identity.revision,
1363
- stateHash: identity.stateHash,
1364
- cols: identity.cols,
1365
- rows: identity.rows,
1366
- layoutPublished: false,
1367
- selectionApplied: false,
1368
- presentationPublished: false,
1369
- followUpRequested: false,
1370
- ...(options.detailedWindowSwitchTiming
1371
- ? {
1372
- timing: {
1373
- receiptAtMicros: null,
1374
- layoutAtMicros: null,
1375
- presentationAtMicros: null,
1376
- },
1377
- }
1378
- : {}),
1379
- };
1380
- armDiagnosticWindowFrame({ kind: "window-switch", ...pendingWindowSwitch });
1381
- diagnose("window-switch-start", { ...pendingWindowSwitch });
1382
- }
1383
- } catch {
1384
- pendingWindowSwitch = null;
1385
- }
1386
- }
1539
+ beginWindowSwitch(pane, target);
1387
1540
  paneInput.selectPane(pane, { presentOptimistically: false });
1388
1541
  },
1389
1542
  async zoomPane(targetPane) {
@@ -1583,6 +1736,7 @@ export function createApplicationTerminalInteractionController(
1583
1736
  ? {}
1584
1737
  : { phaseAtMicros: pending.timing.presentationAtMicros }),
1585
1738
  });
1739
+ maybeRequestWindowSwitchFrame();
1586
1740
  },
1587
1741
  observeDiagnosticWindowFrame() {
1588
1742
  if (!options.diagnosticsEnabled) return null;