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
@@ -1,7 +1,7 @@
1
1
  import { runtimeOwnedPath } from "./runtime-namespace.js";
2
2
  import { randomUUID } from "node:crypto";
3
- import { closeSync, fstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, } from "node:fs";
4
- import { dirname, join } from "node:path";
3
+ import { closeSync, fstatSync, mkdirSync, openSync, readFileSync, renameSync, unlinkSync, writeFileSync, watch, } from "node:fs";
4
+ import { basename, dirname, join } from "node:path";
5
5
  import { SavedMachineRegistrySchema } from "@tmux-ide/contracts";
6
6
  import { changeSavedMachines } from "@tmux-ide/core";
7
7
  import { resolveRuntimeNamespace } from "./runtime-namespace.js";
@@ -29,6 +29,44 @@ export function loadSavedMachines(path = savedMachinesPath()) {
29
29
  closeSync(fd);
30
30
  }
31
31
  }
32
+ /** Watch the directory so atomic registry replacement does not detach the watcher. */
33
+ export function watchSavedMachines(onChange, onError, path = savedMachinesPath()) {
34
+ let stopped = false;
35
+ let pending = null;
36
+ const refresh = () => {
37
+ pending = null;
38
+ if (stopped)
39
+ return;
40
+ try {
41
+ onChange(loadSavedMachines(path));
42
+ }
43
+ catch {
44
+ // Keep the last valid routes while an editor is midway through a write.
45
+ onError();
46
+ }
47
+ };
48
+ // A first-run app can subscribe before the daemon creates its registry.
49
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
50
+ const watcher = watch(dirname(path), (_event, file) => {
51
+ if (stopped || (file !== null && file.toString() !== basename(path)))
52
+ return;
53
+ if (pending)
54
+ clearTimeout(pending);
55
+ pending = setTimeout(refresh, 50);
56
+ pending.unref();
57
+ });
58
+ watcher.unref();
59
+ watcher.on("error", onError);
60
+ // Close the read-before-subscribe race at startup.
61
+ pending = setTimeout(refresh, 0);
62
+ pending.unref();
63
+ return () => {
64
+ stopped = true;
65
+ if (pending)
66
+ clearTimeout(pending);
67
+ watcher.close();
68
+ };
69
+ }
32
70
  /** The daemon owns writes. Synchronous read/reduce/rename cannot interleave within it. */
33
71
  export function updateSavedMachines(change, path = savedMachinesPath()) {
34
72
  const registry = changeSavedMachines(loadSavedMachines(path), change);
@@ -1,6 +1,7 @@
1
1
  import { spawn } from "node:child_process";
2
2
  import { createServer } from "node:net";
3
3
  import { z } from "zod";
4
+ import { remoteTmuxIdeCommand } from "./remote-tmux-command.js";
4
5
  import { createSshDaemonRelay } from "./ssh-daemon-relay.js";
5
6
  import { CanonicalDaemonInfoSchema, DAEMON_WIRE_PROTOCOL_VERSION, DaemonIdentitySchema, SavedMachineSchema, DesktopDaemonCapabilitiesResultSchemaZ, } from "@tmux-ide/contracts";
6
7
  /** The remote CLI emits this over SSH stdout; it must never be logged or persisted. */
@@ -24,12 +25,29 @@ export class SshConnectionError extends Error {
24
25
  this.code = code;
25
26
  }
26
27
  get retryable() {
27
- return this.code === "unavailable";
28
+ // A supervisor may temporarily remove the record while replacing its daemon.
29
+ return this.code === "unavailable" || this.code === "daemon-missing";
28
30
  }
29
31
  }
30
32
  function failure(message, code = "unavailable") {
31
33
  return new SshConnectionError(message, code);
32
34
  }
35
+ /** Classify known OpenSSH errors without publishing remote stderr or credentials. */
36
+ function observeSshFailure(child) {
37
+ let tail = "";
38
+ let code = "unavailable";
39
+ child.stderr?.on("data", (chunk) => {
40
+ tail = (tail + chunk.toString("utf8")).slice(-4096);
41
+ if (/Host key verification failed|REMOTE HOST IDENTIFICATION HAS CHANGED/i.test(tail))
42
+ code = "ssh-host-key";
43
+ else if (/Permission denied \((?:publickey|password|keyboard-interactive)[^)]*\)/i.test(tail))
44
+ code = "ssh-authentication";
45
+ });
46
+ child.once("close", () => {
47
+ tail = "";
48
+ });
49
+ return () => code;
50
+ }
33
51
  const stoppedChildren = new WeakSet();
34
52
  function stop(child) {
35
53
  if (stoppedChildren.has(child) || child.exitCode !== null || child.signalCode !== null)
@@ -119,6 +137,7 @@ const defaults = {
119
137
  probe: probeSshDaemonIdentity,
120
138
  };
121
139
  function discover(child, signal) {
140
+ const sshFailure = observeSshFailure(child);
122
141
  return new Promise((resolve, reject) => {
123
142
  const chunks = [];
124
143
  let bytes = 0;
@@ -147,14 +166,12 @@ function discover(child, signal) {
147
166
  }
148
167
  chunks.push(Buffer.from(chunk));
149
168
  });
150
- // Drain without capturing: SSH banners/errors may contain confidential remote information.
151
- child.stderr?.resume();
152
169
  child.once("error", () => finish(failure("could not start OpenSSH")));
153
170
  child.once("close", (code) => {
154
171
  if (settled)
155
172
  return;
156
173
  if (code !== 0) {
157
- finish(failure("discovery failed; check SSH authentication, host trust, and remote tmux-ide installation"));
174
+ finish(failure("discovery failed; check SSH authentication, host trust, and remote tmux-ide installation", code === 127 ? "remote-cli-missing" : sshFailure()));
158
175
  return;
159
176
  }
160
177
  try {
@@ -234,9 +251,7 @@ export async function openSshDaemonTransport(options, dependencies = defaults) {
234
251
  "ForkAfterAuthentication=no",
235
252
  "--",
236
253
  options.alias,
237
- "tmux-ide",
238
- "remote-daemon-info",
239
- "--json",
254
+ remoteTmuxIdeCommand("discover"),
240
255
  ]);
241
256
  const daemon = await discover(child, signal);
242
257
  const port = await cancellable(dependencies.allocatePort(), signal);
@@ -276,7 +291,7 @@ export async function openSshDaemonTransport(options, dependencies = defaults) {
276
291
  options.alias,
277
292
  ]);
278
293
  child.stdout?.resume();
279
- child.stderr?.resume();
294
+ const sshFailure = observeSshFailure(child);
280
295
  child.once("exit", dispose);
281
296
  const tunnelClosed = new Promise((resolve) => {
282
297
  child.once("close", () => {
@@ -312,7 +327,7 @@ export async function openSshDaemonTransport(options, dependencies = defaults) {
312
327
  }
313
328
  await delay(signal);
314
329
  }
315
- throw failure("tunnel could not authenticate the expected daemon before cancellation or timeout");
330
+ throw failure("tunnel could not authenticate the expected daemon before cancellation or timeout", sshFailure());
316
331
  }
317
332
  catch (error) {
318
333
  dispose();
@@ -198,7 +198,7 @@ export async function createNativeTmuxServerOwner(options) {
198
198
  if (intent.verb === "workspace.window.link.select" ||
199
199
  intent.verb === "workspace.window.link.unlink" ||
200
200
  intent.verb === "workspace.pane.select") {
201
- return multiplexer.mutateWindowLink({ operationId, expectedDaemonInstanceId: generation, intent }, (session, action) => sessionRuntimeRegistry.executeWindowLinkAction(session, action));
201
+ return multiplexer.mutateWindowLink({ operationId, expectedDaemonInstanceId: generation, intent }, (session, action) => sessionRuntimeRegistry.executeWindowLinkAction(session, action), timing);
202
202
  }
203
203
  if (intent.verb === "workspace.pane.resize") {
204
204
  return multiplexer.mutateResize({ operationId, expectedDaemonInstanceId: generation, intent }, (session) => {
@@ -53,12 +53,30 @@ export function createTmuxServerProbe(executable) {
53
53
  const args = selector.kind === "path" ? ["-S", selector.path] : ["-L", selector.name];
54
54
  const read = async (selection) => (await exec(executablePath, [...selection, "-N", "display-message", "-p", "#{socket_path}|#{pid}|#{start_time}"], { env, encoding: "utf8", timeout: 500, maxBuffer: 8192 })).stdout.trimEnd();
55
55
  try {
56
- const first = await read(args);
56
+ // A direct socket can be pinned before contacting tmux. Named servers
57
+ // and aliases still need discovery followed by an explicit-path proof.
58
+ let pinned = null;
59
+ if (selector.kind === "path") {
60
+ try {
61
+ pinned = captureUnixSocketIdentity(selector.path);
62
+ }
63
+ catch {
64
+ // Preserve discovery for aliases which are not direct socket paths.
65
+ }
66
+ }
67
+ const first = await read(pinned ? ["-S", pinned.path] : args);
57
68
  const match = /^(.*)\|([1-9][0-9]*)\|([1-9][0-9]*)$/u.exec(first);
58
69
  if (!match)
59
70
  return null;
60
- const socket = captureUnixSocketIdentity(match[1]);
61
- if ((await read(["-S", socket.path])) !== first)
71
+ const reportedSocket = captureUnixSocketIdentity(match[1]);
72
+ const socket = pinned ?? reportedSocket;
73
+ if (pinned) {
74
+ if (reportedSocket.path !== pinned.path ||
75
+ reportedSocket.dev !== pinned.dev ||
76
+ reportedSocket.ino !== pinned.ino)
77
+ return null;
78
+ }
79
+ else if ((await read(["-S", socket.path])) !== first)
62
80
  return null;
63
81
  revalidateUnixSocketIdentity(socket);
64
82
  return {
@@ -17,7 +17,7 @@ import { createHash, randomUUID } from "node:crypto";
17
17
  import { accessSync, chmodSync, constants as fsConstants, existsSync, mkdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
18
18
  import { dirname, join } from "node:path";
19
19
  import { gunzipSync } from "node:zlib";
20
- import { acquireTuiDownloadLock } from "./tui-download-lock.js";
20
+ import { acquirePrivateOperationLock } from "./private-operation-lock.js";
21
21
  import { getCurrentVersion } from "./update-check.js";
22
22
  /** The GitHub repo the release assets live under. */
23
23
  export const RELEASE_REPO = "wavyrai/tmux-ide";
@@ -274,7 +274,7 @@ export async function downloadTuiBinary(opts = {}) {
274
274
  timeoutMs: opts.timeoutMs ?? TUI_DOWNLOAD_TIMEOUT_MS,
275
275
  };
276
276
  mkdirSync(dirname(dest), { recursive: true });
277
- const releaseLock = await acquireTuiDownloadLock(downloadLockPath(dest), limits.timeoutMs * 2 + 5_000);
277
+ const releaseLock = await acquirePrivateOperationLock(downloadLockPath(dest), limits.timeoutMs * 2 + 5_000);
278
278
  const suffix = `${process.pid}.${randomUUID()}.tmp`;
279
279
  const binaryTmp = `${dest}.${suffix}`;
280
280
  const checksumTmp = `${checksumPath(dest)}.${suffix}`;
@@ -1,3 +1,4 @@
1
+ import { managedInstallerPrefix, runManagedInstallerUpdate, MANAGED_INSTALLER_URL, } from "./managed-installer-update.js";
1
2
  import { resolveRuntimeNamespace } from "./runtime-namespace.js";
2
3
  import { parseStrictSemver } from "./semver.js";
3
4
  /** Origin-aware update planning; unsupported origins never fall back to npm. */
@@ -35,7 +36,20 @@ export function planUpdate(input) {
35
36
  reason: `global ${method} layout (${input.cliPath})`,
36
37
  };
37
38
  }
39
+ if (method === "installer") {
40
+ const prefix = managedInstallerPrefix(input.cliPath);
41
+ if (prefix) {
42
+ return {
43
+ method,
44
+ channel,
45
+ installerPrefix: prefix,
46
+ command: `installer ${MANAGED_INSTALLER_URL} --prefix ${JSON.stringify(prefix)} --version ${channel}`,
47
+ reason: `verified active managed installation (${prefix})`,
48
+ };
49
+ }
50
+ }
38
51
  const guidance = {
52
+ installer: "Rerun https://tmux-ide.com/install.sh with the same --prefix to update atomically and retain rollback. This managed release must not be modified by npm in place.",
39
53
  dev: "Update this checkout with git pull, then follow its build instructions.",
40
54
  homebrew: "Update with brew upgrade tmux-ide (using the tap/formula you installed).",
41
55
  yarn: `Update this Yarn global installation with yarn global add tmux-ide@${channel}.`,
@@ -123,6 +137,10 @@ export function runUpdate({ cliDir, dryRun, json = false }, dependencies = {}) {
123
137
  });
124
138
  executed = true;
125
139
  }
140
+ if (!dryRun && plan.installerPrefix && plan.channel) {
141
+ runManagedInstallerUpdate(plan.installerPrefix, plan.channel, json, dependencies.execute);
142
+ executed = true;
143
+ }
126
144
  if (json)
127
145
  output(JSON.stringify({ ...plan, current, latest, dryRun, executed }));
128
146
  return plan;
@@ -286,7 +286,7 @@ export class WorkspaceMultiplexerAuthority {
286
286
  }
287
287
  }
288
288
  /** Runs only inside the existing semantic mutation lane; never rediscovers a stale link. */
289
- async mutateWindowLink(raw, execute) {
289
+ async mutateWindowLink(raw, execute, timing) {
290
290
  if (this.#disposed)
291
291
  throw new WorkspaceMultiplexerError("workspace_unavailable");
292
292
  const request = WorkspaceMultiplexerMutationRequestSchemaZ.parse(raw);
@@ -297,7 +297,28 @@ export class WorkspaceMultiplexerAuthority {
297
297
  intent.verb !== "workspace.window.link.unlink" &&
298
298
  intent.verb !== "workspace.pane.select")
299
299
  throw new TypeError("Expected a window-link selection or unlink intent");
300
+ const now = () => {
301
+ try {
302
+ return timing?.nowMicros() ?? null;
303
+ }
304
+ catch {
305
+ return null;
306
+ }
307
+ };
308
+ const record = (operation, start) => {
309
+ const end = now();
310
+ if (start !== null && end !== null) {
311
+ try {
312
+ timing?.record(operation, start, end);
313
+ }
314
+ catch {
315
+ /* Diagnostics cannot own selection. */
316
+ }
317
+ }
318
+ };
319
+ const lookupStarted = now();
300
320
  const workspace = this.#registry.get(intent.workspaceName);
321
+ record("semantic-workspace-lookup", lookupStarted);
301
322
  if (!workspace)
302
323
  throw new WorkspaceMultiplexerError("workspace_not_found");
303
324
  const envelope = {
@@ -306,6 +327,7 @@ export class WorkspaceMultiplexerAuthority {
306
327
  workspaceName: intent.workspaceName,
307
328
  };
308
329
  try {
330
+ const effectStarted = now();
309
331
  const result = await execute(workspace.sessionName, intent.verb === "workspace.pane.select"
310
332
  ? {
311
333
  action: "select",
@@ -316,6 +338,7 @@ export class WorkspaceMultiplexerAuthority {
316
338
  action: intent.verb === "workspace.window.link.unlink" ? "unlink" : "select",
317
339
  target: intent.target,
318
340
  });
341
+ record("window-link-effect-proof", effectStarted);
319
342
  if (result.outcome !== "applied") {
320
343
  throw new WorkspaceMultiplexerError(result.outcome === "stale"
321
344
  ? "window_link_stale"
@@ -1,17 +1,53 @@
1
1
  import { readFileSync, statSync } from "node:fs";
2
2
  import { randomUUID } from "node:crypto";
3
- import { SavedMachineRegistrySchema, SavedMachineSchema } from "@tmux-ide/contracts/saved-machines";
3
+ import { SavedMachineRegistrySchema, SavedMachineSchema, SavedMachineMutationSchema, } from "@tmux-ide/contracts/saved-machines";
4
4
  import { loadSavedMachines, planSavedMachineMerge } from "./lib/saved-machines.js";
5
5
  import { loadFleetClientState } from "./lib/fleet-client-state.js";
6
- import { saveMachineProfiles } from "./lib/local-fleet-request.js";
6
+ import { changeSavedMachines } from "@tmux-ide/core";
7
+ import { saveMachineProfiles, mutateMachineProfile } from "./lib/local-fleet-request.js";
8
+ import { remoteTmuxIdeCommand } from "./lib/remote-tmux-command.js";
7
9
  export async function machines(command, argument, options) {
8
10
  const current = loadSavedMachines();
11
+ if ((command === "enable" || command === "disable" || command === "remove" || command === "edit") &&
12
+ argument) {
13
+ const key = argument.normalize("NFKC").toLowerCase();
14
+ const match = current.machines.find((machine) => machine.id === key) ??
15
+ current.machines.find((machine) => machine.label.normalize("NFKC").toLowerCase() === key);
16
+ if (!match)
17
+ throw new Error("Saved machine not found; use machines ls to select its ID or label.");
18
+ const change = SavedMachineMutationSchema.parse(command === "edit"
19
+ ? {
20
+ id: match.id,
21
+ operation: command,
22
+ patch: {
23
+ ...(options.label !== undefined ? { label: options.label } : {}),
24
+ ...(options.sshTarget !== undefined ? { sshTarget: options.sshTarget } : {}),
25
+ },
26
+ }
27
+ : { id: match.id, operation: command });
28
+ const preview = changeSavedMachines(current, command === "remove"
29
+ ? { type: "remove", id: match.id }
30
+ : {
31
+ type: "update",
32
+ id: match.id,
33
+ patch: change.operation === "edit" ? change.patch : { enabled: command === "enable" },
34
+ });
35
+ return {
36
+ written: options.write === true,
37
+ registry: options.write ? await mutateMachineProfile(change) : preview,
38
+ ...(!options.write
39
+ ? {
40
+ note: "Repeat with --write to change this saved profile. Remote sessions and daemons are preserved.",
41
+ }
42
+ : {}),
43
+ };
44
+ }
9
45
  if (command === "start" && argument) {
10
46
  SavedMachineSchema.shape.sshTarget.parse(argument);
11
47
  if (!options.write)
12
48
  return {
13
49
  written: false,
14
- command: ["ssh", "--", argument, "tmux-ide", "update", "--daemon", "--json"],
50
+ command: ["ssh", "--", argument, remoteTmuxIdeCommand("start")],
15
51
  note: "Repeat with --write to start the installed daemon. No package installation occurs.",
16
52
  };
17
53
  await (await import("./lib/remote-daemon-lifecycle.js")).startInstalledRemoteDaemon(argument);
@@ -69,7 +105,7 @@ export async function machines(command, argument, options) {
69
105
  };
70
106
  }
71
107
  else
72
- throw new Error("Usage: tmux-ide machines ls|export|import <file>|add <alias> [--write] [--json]");
108
+ throw new Error("Usage: tmux-ide machines ls|export|import <file>|add <alias>|edit|enable|disable|remove <id-or-label> [--write] [--json]");
73
109
  const preview = planSavedMachineMerge(current, incoming);
74
110
  if (!options.write)
75
111
  return {
@@ -21,9 +21,9 @@
21
21
  <key>CFBundlePackageType</key>
22
22
  <string>APPL</string>
23
23
  <key>CFBundleShortVersionString</key>
24
- <string>2.9.2</string>
24
+ <string>2.9.4</string>
25
25
  <key>CFBundleVersion</key>
26
- <string>2.9.2</string>
26
+ <string>2.9.4</string>
27
27
  <key>LSMinimumSystemVersion</key>
28
28
  <string>11.0</string>
29
29
  <key>LSUIElement</key>
@@ -10,7 +10,7 @@
10
10
  </data>
11
11
  <key>Resources/Assets.car</key>
12
12
  <data>
13
- 1/Kd+BOk3onNhd2X8tbDyMpGtnM=
13
+ TPHWDo+ga7DgPtltNVjNyW6+Kpc=
14
14
  </data>
15
15
  </dict>
16
16
  <key>files2</key>
@@ -26,7 +26,7 @@
26
26
  <dict>
27
27
  <key>hash2</key>
28
28
  <data>
29
- IkaRCLzVQ2gdu2vtvmtL2MtPanLEDl/Dc53vKEcChns=
29
+ I1uLFZ9MLgryptxKSUR8tbfEKr5vQzHGgIpna3gvlxc=
30
30
  </data>
31
31
  </dict>
32
32
  </dict>
@@ -434,7 +434,10 @@ export class TerminalAttachmentAdmissionCoordinator {
434
434
  let sessionRuntimeBinding = null;
435
435
  let live;
436
436
  try {
437
- sessionRuntimeBinding = this.#bindSessionRuntime?.(activeDescriptor) ?? null;
437
+ sessionRuntimeBinding = (await this.#bindSessionRuntime?.(activeDescriptor)) ?? null;
438
+ if (!admission.isOpen()) {
439
+ throw new TerminalAttachmentAdmissionError("redemption-rejected", "Terminal attachment redemption was rejected.");
440
+ }
438
441
  live = new TerminalAttachmentLiveConnection({
439
442
  onRetire: (connection) => this.#trackRetiringRelease(connection),
440
443
  socket,
@@ -821,6 +821,7 @@ export class WorkspaceTerminalInventoryRuntime {
821
821
  #inventoryRead = null;
822
822
  #applicationReads = new Map();
823
823
  #lifecycle = "initializing";
824
+ #registeredWindows;
824
825
  #disposed = false;
825
826
  constructor(options) {
826
827
  const authority = canonicalAuthority(options.tmuxAuthority);
@@ -831,6 +832,10 @@ export class WorkspaceTerminalInventoryRuntime {
831
832
  });
832
833
  this.readRunner = pinnedReadRunner(authority, executeRead);
833
834
  this.#registry = options.registry;
835
+ this.#registeredWindows = options.sessionRuntimeRegistry?.setRegisteredWindowReader?.(async (signal) => {
836
+ const snapshot = await discoverWorkspaceRegistryTerminalInventory(this.#registry, this.readRunner, signal);
837
+ return snapshot.panes.map((pane) => ({ session: pane.sessionName, window: pane.windowId }));
838
+ });
834
839
  this.#observability = options.observability ?? DISABLED_SESSION_RUNTIME_OBSERVABILITY;
835
840
  this.#resolveInteractionEndpoint = options.resolveInteractionEndpoint;
836
841
  this.#nativeServerEpoch = options.nativeServerEpoch;
@@ -929,10 +934,12 @@ export class WorkspaceTerminalInventoryRuntime {
929
934
  this.#observeWorkspaceSession = null;
930
935
  }
931
936
  this.#stopWorkspaceAddedObserver = options.registry.on("workspace.added", (workspace) => {
937
+ this.#registeredWindows?.invalidate();
932
938
  this.invalidate();
933
939
  this.#observeWorkspaceSession?.(workspace.name, workspace.sessionName);
934
940
  });
935
941
  this.#stopWorkspaceRemovedObserver = options.registry.on("workspace.removed", (name) => {
942
+ this.#registeredWindows?.invalidate();
936
943
  this.invalidate();
937
944
  if (!options.sessionRuntimeRegistry)
938
945
  return;
@@ -1200,12 +1207,12 @@ export class WorkspaceTerminalInventoryRuntime {
1200
1207
  let catalogIssue = inventory.catalog
1201
1208
  .invalidRuntimeProof
1202
1209
  ? "invalid-runtime-proof"
1203
- : inventory.catalog.missingSemanticStamp
1204
- ? "missing-semantic-stamp"
1205
- : inventory.catalog.duplicateSemanticStamp
1206
- ? "duplicate-semantic-stamp"
1207
- : inventory.catalog.duplicateRuntimePaneBinding
1208
- ? "duplicate-runtime-pane-binding"
1210
+ : inventory.catalog.duplicateRuntimePaneBinding
1211
+ ? "duplicate-runtime-pane-binding"
1212
+ : inventory.catalog.missingSemanticStamp
1213
+ ? "missing-semantic-stamp"
1214
+ : inventory.catalog.duplicateSemanticStamp
1215
+ ? "duplicate-semantic-stamp"
1209
1216
  : null;
1210
1217
  if (shouldPrewarm &&
1211
1218
  (trustedInventory || allowColdQualification) &&
@@ -1348,6 +1355,7 @@ export class WorkspaceTerminalInventoryRuntime {
1348
1355
  return;
1349
1356
  this.#disposed = true;
1350
1357
  this.#lifecycle = "disposed";
1358
+ this.#registeredWindows?.dispose();
1351
1359
  this.invalidate();
1352
1360
  this.#stopWorkspaceAddedObserver?.();
1353
1361
  this.#stopWorkspaceRemovedObserver?.();
@@ -1460,13 +1468,14 @@ export class NativeTerminalAttachmentRuntime {
1460
1468
  resolveGeometry: (descriptor, client) => geometry.resolve(descriptor, client),
1461
1469
  ...(options.sessionRuntimeRegistry
1462
1470
  ? {
1463
- bindSessionRuntime: (descriptor) => {
1471
+ bindSessionRuntime: async (descriptor) => {
1464
1472
  const workspace = options.registry.get(descriptor.target.workspaceName);
1465
1473
  if (!workspace)
1466
1474
  throw new Error("Terminal attachment workspace is unavailable");
1467
1475
  if (!descriptor.hostClientId) {
1468
1476
  throw new Error("Interactive terminal attachment lacks trusted host identity");
1469
1477
  }
1478
+ await options.sessionRuntimeRegistry.verifyWindowOwnership();
1470
1479
  return new SessionRuntimeTransportBinder(options.sessionRuntimeRegistry).bind({
1471
1480
  transport: "terminal-attachment",
1472
1481
  transportLeaseId: descriptor.leaseId,
@@ -1656,12 +1665,12 @@ export class NativeTerminalAttachmentRuntime {
1656
1665
  const catalogIssue = inventory.catalog
1657
1666
  .invalidRuntimeProof
1658
1667
  ? "invalid-runtime-proof"
1659
- : inventory.catalog.missingSemanticStamp
1660
- ? "missing-semantic-stamp"
1661
- : inventory.catalog.duplicateSemanticStamp
1662
- ? "duplicate-semantic-stamp"
1663
- : inventory.catalog.duplicateRuntimePaneBinding
1664
- ? "duplicate-runtime-pane-binding"
1668
+ : inventory.catalog.duplicateRuntimePaneBinding
1669
+ ? "duplicate-runtime-pane-binding"
1670
+ : inventory.catalog.missingSemanticStamp
1671
+ ? "missing-semantic-stamp"
1672
+ : inventory.catalog.duplicateSemanticStamp
1673
+ ? "duplicate-semantic-stamp"
1665
1674
  : null;
1666
1675
  // Ground-truth agent facts (authority + scrape fallback). All IO stays here;
1667
1676
  // the resource projector composes them purely. Absent probe → no facts →