tmux-ide 2.9.2 → 2.9.4

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