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
@@ -1,8 +1,9 @@
1
1
  import { resolveRuntimeNamespace } from "../../../lib/runtime-namespace.js";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { SavedMachineSchema } from "@tmux-ide/contracts";
4
- import { loadSavedMachines } from "../../../lib/saved-machines.js";
4
+ import { loadSavedMachines, watchSavedMachines } from "../../../lib/saved-machines.js";
5
5
  import { applicationMachineAuthorityManager } from "./application-machine-authority.js";
6
+ import { tuiPerfMark } from "./application-performance-log.js";
6
7
  /** Labels describe access routes; they never become routing identities. */
7
8
  export function ephemeralMachineProfile(alias, existing) {
8
9
  const target = SavedMachineSchema.shape.sshTarget.parse(alias);
@@ -20,17 +21,45 @@ export function ephemeralMachineProfile(alias, existing) {
20
21
  }
21
22
  /** Local discovery and rendering never wait for any remote handshake. */
22
23
  export function initializeApplicationMachines(aliases) {
23
- const profiles = resolveRuntimeNamespace().development ? [] : [...loadSavedMachines().machines];
24
+ const development = resolveRuntimeNamespace().development;
25
+ const profiles = development ? [] : [...loadSavedMachines().machines];
26
+ const ephemeral = [];
24
27
  let preferred = null;
25
28
  for (const alias of aliases) {
26
29
  let profile = profiles.find((p) => p.sshTarget === alias && p.enabled);
27
30
  if (!profile) {
28
31
  profile = ephemeralMachineProfile(alias, profiles);
29
32
  profiles.push(profile);
33
+ ephemeral.push(profile);
30
34
  }
31
35
  preferred ??= profile.id;
32
36
  }
33
37
  applicationMachineAuthorityManager.initialize(profiles);
34
38
  if (preferred)
35
39
  applicationMachineAuthorityManager.select(preferred);
40
+ if (!development) {
41
+ const failed = () => tuiPerfMark("machine-registry-error", { reason: "registry-unavailable-or-invalid" });
42
+ try {
43
+ applicationMachineAuthorityManager.followProfiles((onChange) => watchSavedMachines((registry) => {
44
+ const next = [...registry.machines];
45
+ for (let index = 0; index < ephemeral.length; index++) {
46
+ let profile = ephemeral[index];
47
+ // A newly saved route may claim an ephemeral display label. Keep the
48
+ // explicit connection's identity while making the label unambiguous.
49
+ if (next.some((entry) => entry.label.normalize("NFKC").toLowerCase() ===
50
+ profile.label.normalize("NFKC").toLowerCase()))
51
+ profile = {
52
+ ...profile,
53
+ label: ephemeralMachineProfile(profile.sshTarget, next).label,
54
+ };
55
+ ephemeral[index] = profile;
56
+ next.push(profile);
57
+ }
58
+ onChange(next);
59
+ }, failed));
60
+ }
61
+ catch {
62
+ failed();
63
+ }
64
+ }
36
65
  }
@@ -1,4 +1,4 @@
1
- import { createMemo, createSignal } from "solid-js";
1
+ import { createMemo, createSignal, untrack } from "solid-js";
2
2
  import { applicationCommandDescription, filterApplicationCommands, } from "../workspace/application-command-description.js";
3
3
  import { applicationPaletteKeyboardDisposition, } from "./application-palette-input.js";
4
4
  import { applicationPaneRenameKeyAction, applicationPaneRenamePaste, } from "./application-pane-rename-input.js";
@@ -11,9 +11,13 @@ export function createApplicationPaletteSearchOwner(options) {
11
11
  const [help, setHelp] = createSignal(false);
12
12
  const [query, setQuery] = createSignal("");
13
13
  const [selectedId, setSelectedId] = createSignal(null);
14
- const commands = createMemo(() => filterApplicationCommands(options
14
+ const readCommands = () => filterApplicationCommands(options
15
15
  .commands()
16
- .filter((c) => !localOnly() || typeof c === "string" || !c.fleet || c.fleet.machineId === "local"), query()));
16
+ .filter((c) => !localOnly() || typeof c === "string" || !c.fleet || c.fleet.machineId === "local"), query());
17
+ // Closed overlays must not subscribe to catalog churn. Explicit reads still
18
+ // resolve current commands, including reset() before the open action commits.
19
+ const activeCommands = createMemo(() => (options.open() ? readCommands() : null));
20
+ const commands = () => activeCommands() ?? untrack(readCommands);
17
21
  const selection = () => Math.max(0, commands().findIndex((command) => applicationCommandDescription(command).id === selectedId()));
18
22
  const select = (index) => {
19
23
  const command = commands()[index];
@@ -29,6 +29,8 @@ stream?.on("drain", drain);
29
29
  export const tuiLifecycleStream = stream ? Object.freeze({ enabled: true }) : null;
30
30
  export const tuiPerfStream = TUI_PERF_LOG ? tuiLifecycleStream : null;
31
31
  const LIFECYCLE_PHASES = new Set([
32
+ "machine-connection-status",
33
+ "machine-registry-error",
32
34
  "generation-status",
33
35
  "generation-connection-start",
34
36
  "generation-connection-resolved",
@@ -132,6 +134,7 @@ export function closeTuiPerfMarks() {
132
134
  closePromise = (async () => {
133
135
  if (!stream)
134
136
  return;
137
+ await writer?.flush();
135
138
  await flushTuiPerfMarks();
136
139
  // No drain callback may enqueue another critical record after end().
137
140
  stream.off("drain", drain);
@@ -7,6 +7,133 @@ import { terminalInputForOpenTuiKey, terminalInputsForPaste, } from "./terminal-
7
7
  export function createApplicationTerminalInputIngress(interaction, generation, sessionOwner, focusedPane, setNote) {
8
8
  const pending = createApplicationPendingTerminalInputOwner();
9
9
  let ownsNote = false;
10
+ let recovery = null;
11
+ const scope = () => {
12
+ const active = generation();
13
+ const owner = sessionOwner();
14
+ const pane = focusedPane();
15
+ const session = owner?.sessionName();
16
+ if (!active ||
17
+ !["live", "rebinding"].includes(active.status) ||
18
+ !active.connection ||
19
+ !active.client ||
20
+ !active.daemonGeneration ||
21
+ !owner ||
22
+ !session ||
23
+ !pane)
24
+ return null;
25
+ try {
26
+ const clientGeneration = active.client.getSnapshot().generation;
27
+ if (!Number.isSafeInteger(clientGeneration))
28
+ return null;
29
+ return {
30
+ owner,
31
+ connection: active.connection,
32
+ client: active.client,
33
+ daemon: active.daemonGeneration,
34
+ clientGeneration: clientGeneration,
35
+ session: session,
36
+ pane,
37
+ };
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ };
43
+ const sameScope = (left, right) => right !== null &&
44
+ left.owner === right.owner &&
45
+ left.connection === right.connection &&
46
+ left.client === right.client &&
47
+ left.daemon === right.daemon &&
48
+ left.clientGeneration === right.clientGeneration &&
49
+ left.session === right.session &&
50
+ left.pane === right.pane;
51
+ let recoveryVersion = 0;
52
+ const cancelRecovery = () => {
53
+ recoveryVersion++;
54
+ if (recovery)
55
+ clearTimeout(recovery.timer);
56
+ recovery = null;
57
+ };
58
+ const recover = (inputs, bytes, kind) => {
59
+ if (pending.snapshot().sessionName !== null ||
60
+ (!recovery && generation()?.status !== "rebinding"))
61
+ return false;
62
+ const target = scope();
63
+ if (!target)
64
+ return false;
65
+ if (recovery && !sameScope(recovery.scope, target))
66
+ cancelRecovery();
67
+ if (!recovery) {
68
+ const timer = setTimeout(() => {
69
+ cancelRecovery();
70
+ setNote("terminal recovery timed out · queued input was not sent");
71
+ }, 5_000);
72
+ timer.unref?.();
73
+ recovery = { scope: target, inputs: [], weight: 0, admitted: 0, draining: false, timer };
74
+ }
75
+ const weight = bytes.byteLength;
76
+ if (recovery.admitted >= 64 || recovery.weight + weight > 1024 * 1024) {
77
+ setNote("terminal input queue full · wait for the session to connect");
78
+ return true;
79
+ }
80
+ recovery.weight += weight;
81
+ recovery.admitted++;
82
+ const version = recoveryVersion;
83
+ const parserOrigin = captureOrigin()
84
+ ? {
85
+ origin: kind === "paste" ? "bracketed-paste" : "keyboard",
86
+ payload: Buffer.from(bytes),
87
+ }
88
+ : undefined;
89
+ recovery.inputs.push(async () => {
90
+ for (const [index, input] of inputs.entries()) {
91
+ if (version !== recoveryVersion ||
92
+ !sameScope(target, scope()) ||
93
+ generation()?.status !== "live" ||
94
+ !(await interaction.sendInputToPane(target.pane, input, index === 0 ? parserOrigin : undefined)))
95
+ return false;
96
+ }
97
+ return true;
98
+ });
99
+ setNote(`${target.session} · terminal ${kind} queued`);
100
+ return true;
101
+ };
102
+ const flushRecovery = () => {
103
+ const waiting = recovery;
104
+ if (!waiting || waiting.draining)
105
+ return;
106
+ if (!sameScope(waiting.scope, scope())) {
107
+ cancelRecovery();
108
+ setNote("terminal changed · queued input was not sent");
109
+ return;
110
+ }
111
+ if (generation()?.status !== "live" || !generation()?.fastLane)
112
+ return;
113
+ waiting.draining = true;
114
+ const version = recoveryVersion;
115
+ void (async () => {
116
+ while (version === recoveryVersion && waiting.inputs.length > 0) {
117
+ const send = waiting.inputs.shift();
118
+ try {
119
+ if (await send())
120
+ continue;
121
+ }
122
+ catch {
123
+ /* An uncertain send is never retried. */
124
+ }
125
+ if (version !== recoveryVersion)
126
+ return;
127
+ cancelRecovery();
128
+ setNote("terminal unavailable · queued input was not sent");
129
+ return;
130
+ }
131
+ if (version === recoveryVersion) {
132
+ cancelRecovery();
133
+ setNote(null);
134
+ }
135
+ })();
136
+ };
10
137
  const captureOrigin = () => Boolean(currentTuiPerformanceEventSink()?.terminalInputOrigin);
11
138
  const flush = () => {
12
139
  const owner = sessionOwner();
@@ -36,9 +163,14 @@ export function createApplicationTerminalInputIngress(interaction, generation, s
36
163
  else
37
164
  setNote(`terminal unavailable · ${kind} was not sent`);
38
165
  };
166
+ const cancelInteractionInput = () => {
167
+ cancelRecovery();
168
+ interaction.cancelPendingInput();
169
+ };
39
170
  return {
40
171
  wrapStarter(starter) {
41
172
  const start = async (...args) => {
173
+ cancelInteractionInput();
42
174
  const [sessionName] = args;
43
175
  const identity = pending.begin(sessionName);
44
176
  const result = await starter(...args);
@@ -52,24 +184,32 @@ export function createApplicationTerminalInputIngress(interaction, generation, s
52
184
  };
53
185
  return Object.assign(start, {
54
186
  cancel() {
187
+ cancelInteractionInput();
55
188
  pending.dispose();
56
189
  starter.cancel();
57
190
  },
58
191
  });
59
192
  },
60
- adopt() {
193
+ adopt(terminalsActive = true) {
194
+ if (!terminalsActive)
195
+ cancelInteractionInput();
61
196
  flush();
197
+ flushRecovery();
62
198
  },
63
199
  routeKey(event) {
64
200
  const active = generation();
65
201
  if (pending.snapshot().sessionName === null &&
202
+ recovery === null &&
66
203
  active?.status === "live" &&
67
204
  active.fastLane &&
68
205
  focusedPane()) {
69
206
  sendApplicationTerminalKey(interaction, event, captureOrigin());
70
207
  return;
71
208
  }
72
- if (!terminalInputForOpenTuiKey(event))
209
+ const recoveryInput = terminalInputForOpenTuiKey(event);
210
+ if (!recoveryInput)
211
+ return;
212
+ if (recover([recoveryInput], Buffer.from(recoveryInput.data, "utf8"), "input"))
73
213
  return;
74
214
  const copy = { name: event.name, ctrl: event.ctrl, meta: event.meta, shift: event.shift };
75
215
  noteAdmission(pending.enqueue(() => sendApplicationTerminalKey(interaction, copy, captureOrigin()), Buffer.byteLength(event.name, "utf8")), "input");
@@ -87,15 +227,19 @@ export function createApplicationTerminalInputIngress(interaction, generation, s
87
227
  }
88
228
  const active = generation();
89
229
  if (pending.snapshot().sessionName === null &&
230
+ recovery === null &&
90
231
  active?.status === "live" &&
91
232
  active.fastLane &&
92
233
  focusedPane()) {
93
234
  sendApplicationTerminalPaste(interaction, copy, captureOrigin());
94
235
  return;
95
236
  }
237
+ if (recover(terminalInputsForPaste(Buffer.from(copy).toString("utf8")), copy, "paste"))
238
+ return;
96
239
  noteAdmission(pending.enqueue(() => sendApplicationTerminalPaste(interaction, copy, captureOrigin()), copy.byteLength), "paste");
97
240
  },
98
241
  dispose() {
242
+ cancelInteractionInput();
99
243
  pending.dispose();
100
244
  },
101
245
  };
@@ -84,6 +84,64 @@ export function createApplicationTerminalInteractionController(options) {
84
84
  let diagnosticWindowFrame = null;
85
85
  let diagnosticWindowFrameExpiresAtMicros = 0;
86
86
  let inputAuthorityIdentity = null;
87
+ // Pane selection belongs to the control connection, which can outlive a
88
+ // terminal renderer rebuild. Never carry selection or input across reconnects.
89
+ const controlIdentity = (snapshot) => {
90
+ if (!snapshot ||
91
+ !["live", "rebinding"].includes(snapshot.status) ||
92
+ !snapshot.connection ||
93
+ !snapshot.client ||
94
+ !snapshot.daemonGeneration)
95
+ return null;
96
+ try {
97
+ const generation = snapshot.client.getSnapshot().generation;
98
+ if (!Number.isSafeInteger(generation))
99
+ return null;
100
+ return {
101
+ connection: snapshot.connection,
102
+ client: snapshot.client,
103
+ daemon: snapshot.daemonGeneration,
104
+ generation,
105
+ workspace: snapshot.connection.workspaceName,
106
+ };
107
+ }
108
+ catch {
109
+ return null;
110
+ }
111
+ };
112
+ const sameControl = (left, right) => left !== null &&
113
+ right !== null &&
114
+ left.connection === right.connection &&
115
+ left.client === right.client &&
116
+ left.daemon === right.daemon &&
117
+ left.generation === right.generation &&
118
+ left.workspace === right.workspace;
119
+ let selectedControl = null;
120
+ const waitingInput = new Set();
121
+ let waitingInputBytes = 0;
122
+ let releasingInput = null;
123
+ const awaitInputRuntime = (weight) => {
124
+ const control = controlIdentity(options.generation());
125
+ if (!control || waitingInput.size >= 64 || waitingInputBytes + weight > 1024 * 1024)
126
+ return Promise.resolve(false);
127
+ return new Promise((resolve) => {
128
+ const entry = {
129
+ control,
130
+ weight,
131
+ settle: (ready) => {
132
+ if (!waitingInput.delete(entry))
133
+ return;
134
+ waitingInputBytes -= weight;
135
+ clearTimeout(timer);
136
+ resolve(ready);
137
+ },
138
+ };
139
+ const timer = setTimeout(() => entry.settle(false), 5000);
140
+ timer.unref?.();
141
+ waitingInput.add(entry);
142
+ waitingInputBytes += weight;
143
+ });
144
+ };
87
145
  const armDiagnosticWindowFrame = (pending) => {
88
146
  diagnosticWindowFrame = pending;
89
147
  diagnosticWindowFrameExpiresAtMicros = pending.startedAtMicros + 6_000_000;
@@ -356,6 +414,71 @@ export function createApplicationTerminalInteractionController(options) {
356
414
  pendingWindowSwitch = null;
357
415
  }
358
416
  };
417
+ const beginWindowSwitch = (pane, target, windowLink) => {
418
+ pendingWindowSwitch = null;
419
+ if (options.diagnosticsEnabled) {
420
+ try {
421
+ const active = options.generation();
422
+ const identity = active?.adapter?.paneCanonicalIdentity(pane);
423
+ const clientGeneration = active?.client?.getSnapshot().generation;
424
+ const startedAtMicros = diagnosticNowMicros();
425
+ if (active?.status === "live" &&
426
+ active.daemonGeneration &&
427
+ identity &&
428
+ Number.isSafeInteger(clientGeneration) &&
429
+ startedAtMicros !== null) {
430
+ pendingWindowSwitch = {
431
+ selectionKind: windowLink ? "window-link" : "pane",
432
+ ...(windowLink ? { windowLink } : {}),
433
+ traceId: createTraceId(),
434
+ target,
435
+ paneId: pane,
436
+ startedAtMicros,
437
+ daemonGeneration: active.daemonGeneration,
438
+ clientGeneration: clientGeneration,
439
+ rendererEpoch: active.rendererEpoch,
440
+ sourceEpoch: identity.sourceEpoch,
441
+ generation: identity.generation,
442
+ incarnation: identity.incarnation,
443
+ revision: identity.revision,
444
+ stateHash: identity.stateHash,
445
+ cols: identity.cols,
446
+ rows: identity.rows,
447
+ layoutPublished: false,
448
+ selectionApplied: false,
449
+ presentationPublished: false,
450
+ followUpRequested: false,
451
+ ...(options.detailedWindowSwitchTiming
452
+ ? {
453
+ timing: {
454
+ receiptAtMicros: null,
455
+ layoutAtMicros: null,
456
+ presentationAtMicros: null,
457
+ },
458
+ }
459
+ : {}),
460
+ };
461
+ armDiagnosticWindowFrame({ kind: "window-switch", ...pendingWindowSwitch });
462
+ diagnose("window-switch-start", { ...pendingWindowSwitch });
463
+ }
464
+ else {
465
+ diagnose("window-switch-untracked", {
466
+ paneId: pane,
467
+ target,
468
+ status: active?.status ?? null,
469
+ daemonGeneration: active?.daemonGeneration ?? null,
470
+ rendererEpoch: active?.rendererEpoch ?? null,
471
+ clientGeneration: clientGeneration ?? null,
472
+ identity: identity ?? null,
473
+ laneCounters: active?.fastLane?.lane.counters() ?? null,
474
+ });
475
+ }
476
+ }
477
+ catch {
478
+ pendingWindowSwitch = null;
479
+ }
480
+ }
481
+ };
359
482
  let linkSelection = null;
360
483
  const finishLinkSelection = (selected) => {
361
484
  const pending = linkSelection;
@@ -363,6 +486,20 @@ export function createApplicationTerminalInteractionController(options) {
363
486
  return;
364
487
  linkSelection = null;
365
488
  clearTimeout(pending.timer);
489
+ const diagnostic = pending.diagnostic;
490
+ if (diagnostic && pendingWindowSwitch === diagnostic) {
491
+ if (selected) {
492
+ diagnostic.selectionApplied = true;
493
+ maybeRequestWindowSwitchFrame();
494
+ }
495
+ else {
496
+ diagnoseCritical(`window-switch:${diagnostic.traceId}:failed`, "window-switch-failed", {
497
+ ...diagnostic,
498
+ reason: "window-link-selection-not-applied",
499
+ });
500
+ pendingWindowSwitch = null;
501
+ }
502
+ }
366
503
  pending.resolve(selected);
367
504
  };
368
505
  const reconcileLinkSelection = (snapshot) => {
@@ -386,11 +523,18 @@ export function createApplicationTerminalInteractionController(options) {
386
523
  select: async (paneId) => {
387
524
  finishLinkSelection(false);
388
525
  const expected = liveSelectionTarget();
526
+ const selectionControl = controlIdentity(options.generation());
389
527
  const selecting = pendingWindowSwitch;
390
528
  const operationId = selecting?.traceId;
391
529
  const selectionFailure = { current: null };
392
530
  const receipt = expected
393
- ? await selectTerminalPane(expected, liveSelectionTarget, paneId, operationId, (failure) => {
531
+ ? await selectTerminalPane(expected, () => {
532
+ if (!selectionControl)
533
+ return liveSelectionTarget();
534
+ return sameControl(selectionControl, controlIdentity(options.generation()))
535
+ ? expected
536
+ : null;
537
+ }, paneId, operationId, (failure) => {
394
538
  selectionFailure.current = failure;
395
539
  }, windowLinkForPane(options.layout(), paneId) ?? undefined)
396
540
  : null;
@@ -455,6 +599,16 @@ export function createApplicationTerminalInteractionController(options) {
455
599
  },
456
600
  send: async (paneId, routed) => {
457
601
  const { input, parserOrigin } = routed;
602
+ const selectionVersion = paneInput.selectionVersion;
603
+ if (options.generation()?.status === "rebinding") {
604
+ if (!(await awaitInputRuntime(Buffer.byteLength(input.data, "utf8"))))
605
+ return false;
606
+ }
607
+ else if (releasingInput) {
608
+ await releasingInput;
609
+ }
610
+ if (paneInput.selectionVersion !== selectionVersion || paneInput.focusedPane !== paneId)
611
+ return false;
458
612
  const active = options.generation();
459
613
  if (active?.status !== "live" || !active.fastLane)
460
614
  return false;
@@ -534,7 +688,35 @@ export function createApplicationTerminalInteractionController(options) {
534
688
  onFocusedPane: options.setFocusedPane,
535
689
  });
536
690
  return {
691
+ cancelPendingInput() {
692
+ finishLinkSelection(false);
693
+ paneInput.invalidateSelection();
694
+ for (const entry of waitingInput)
695
+ entry.settle(false);
696
+ },
537
697
  adoptGeneration(snapshot) {
698
+ const control = controlIdentity(snapshot);
699
+ if (!sameControl(selectedControl, control)) {
700
+ finishLinkSelection(false);
701
+ paneInput.invalidateSelection();
702
+ }
703
+ selectedControl = control;
704
+ if (waitingInput.size > 0 && snapshot?.status === "live" && snapshot.fastLane) {
705
+ // Old waiters resume first; input admitted synchronously by the next
706
+ // layout publication must not overtake them in this microtask turn.
707
+ const barrier = Promise.resolve();
708
+ releasingInput = barrier;
709
+ queueMicrotask(() => {
710
+ if (releasingInput === barrier)
711
+ releasingInput = null;
712
+ });
713
+ }
714
+ for (const entry of waitingInput) {
715
+ if (!sameControl(entry.control, control))
716
+ entry.settle(false);
717
+ else if (snapshot?.status === "live" && snapshot.fastLane)
718
+ entry.settle(true);
719
+ }
538
720
  let next = null;
539
721
  if (snapshot?.status === "live" &&
540
722
  snapshot.daemonGeneration &&
@@ -570,7 +752,6 @@ export function createApplicationTerminalInteractionController(options) {
570
752
  next.adapter !== inputAuthorityIdentity.adapter);
571
753
  if (next === null || replaced) {
572
754
  finishLinkSelection(false);
573
- paneInput.invalidateSelection();
574
755
  pendingWindowSwitch = null;
575
756
  pendingWindowRename = null;
576
757
  diagnosticWindowFrame = null;
@@ -715,6 +896,7 @@ export function createApplicationTerminalInteractionController(options) {
715
896
  observed.expectedSemanticWindowId !== target.expectedSemanticWindowId)
716
897
  return;
717
898
  finishLinkSelection(false);
899
+ beginWindowSwitch(paneId, target.expectedSemanticWindowId, target);
718
900
  let resolve;
719
901
  const settled = new Promise((done) => {
720
902
  resolve = done;
@@ -724,6 +906,7 @@ export function createApplicationTerminalInteractionController(options) {
724
906
  paneId,
725
907
  layout: snapshot,
726
908
  received: false,
909
+ diagnostic: pendingWindowSwitch,
727
910
  resolve,
728
911
  timer: setTimeout(() => {
729
912
  if (linkSelection === pending)
@@ -751,6 +934,7 @@ export function createApplicationTerminalInteractionController(options) {
751
934
  }
752
935
  const wrapper = (await expected.client.dispatch({
753
936
  kind: "semantic-intent",
937
+ ...(pending.diagnostic ? { operationId: pending.diagnostic.traceId } : {}),
754
938
  intent: {
755
939
  verb: "workspace.window.link.select",
756
940
  workspaceName: expected.workspaceName,
@@ -763,6 +947,7 @@ export function createApplicationTerminalInteractionController(options) {
763
947
  if (wrapper.kind !== "semantic-intent" ||
764
948
  !result.success ||
765
949
  result.data.operationId !== wrapper.operationId ||
950
+ (pending.diagnostic && wrapper.operationId !== pending.diagnostic.traceId) ||
766
951
  result.data.daemonInstanceId !== expected.daemonGeneration ||
767
952
  result.data.workspaceName !== expected.workspaceName ||
768
953
  result.data.target.linkId !== target.linkId ||
@@ -772,6 +957,18 @@ export function createApplicationTerminalInteractionController(options) {
772
957
  finishLinkSelection(false);
773
958
  return;
774
959
  }
960
+ if (pending.diagnostic) {
961
+ const receiptAtMicros = diagnosticNowMicros();
962
+ if (pending.diagnostic.timing)
963
+ pending.diagnostic.timing.receiptAtMicros = receiptAtMicros;
964
+ diagnose("window-switch-receipt", {
965
+ ...pending.diagnostic,
966
+ operationId: wrapper.operationId,
967
+ selected: true,
968
+ applied: true,
969
+ ...(options.detailedWindowSwitchTiming ? { phaseAtMicros: receiptAtMicros } : {}),
970
+ });
971
+ }
775
972
  pending.received = true;
776
973
  reconcileLinkSelection(pending.layout);
777
974
  await settled;
@@ -1022,54 +1219,7 @@ export function createApplicationTerminalInteractionController(options) {
1022
1219
  const target = next?.semanticWindowId ?? next?.windowName;
1023
1220
  if (!pane || !target)
1024
1221
  return;
1025
- if (options.diagnosticsEnabled) {
1026
- try {
1027
- const active = options.generation();
1028
- const identity = active?.adapter?.paneCanonicalIdentity(pane);
1029
- const clientGeneration = active?.client?.getSnapshot().generation;
1030
- const startedAtMicros = diagnosticNowMicros();
1031
- if (active?.status === "live" &&
1032
- active.daemonGeneration &&
1033
- identity &&
1034
- Number.isSafeInteger(clientGeneration) &&
1035
- startedAtMicros !== null) {
1036
- pendingWindowSwitch = {
1037
- traceId: createTraceId(),
1038
- target,
1039
- paneId: pane,
1040
- startedAtMicros,
1041
- daemonGeneration: active.daemonGeneration,
1042
- clientGeneration: clientGeneration,
1043
- rendererEpoch: active.rendererEpoch,
1044
- sourceEpoch: identity.sourceEpoch,
1045
- generation: identity.generation,
1046
- incarnation: identity.incarnation,
1047
- revision: identity.revision,
1048
- stateHash: identity.stateHash,
1049
- cols: identity.cols,
1050
- rows: identity.rows,
1051
- layoutPublished: false,
1052
- selectionApplied: false,
1053
- presentationPublished: false,
1054
- followUpRequested: false,
1055
- ...(options.detailedWindowSwitchTiming
1056
- ? {
1057
- timing: {
1058
- receiptAtMicros: null,
1059
- layoutAtMicros: null,
1060
- presentationAtMicros: null,
1061
- },
1062
- }
1063
- : {}),
1064
- };
1065
- armDiagnosticWindowFrame({ kind: "window-switch", ...pendingWindowSwitch });
1066
- diagnose("window-switch-start", { ...pendingWindowSwitch });
1067
- }
1068
- }
1069
- catch {
1070
- pendingWindowSwitch = null;
1071
- }
1072
- }
1222
+ beginWindowSwitch(pane, target);
1073
1223
  paneInput.selectPane(pane, { presentOptimistically: false });
1074
1224
  },
1075
1225
  async zoomPane(targetPane) {
@@ -1265,6 +1415,7 @@ export function createApplicationTerminalInteractionController(options) {
1265
1415
  ? {}
1266
1416
  : { phaseAtMicros: pending.timing.presentationAtMicros }),
1267
1417
  });
1418
+ maybeRequestWindowSwitchFrame();
1268
1419
  },
1269
1420
  observeDiagnosticWindowFrame() {
1270
1421
  if (!options.diagnosticsEnabled)