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
@@ -41,9 +41,9 @@ export interface ApplicationMachineAuthorityManagerDependencies {
41
41
  }
42
42
  interface Machine {
43
43
  readonly id: string;
44
- readonly label: string;
44
+ label: string;
45
45
  readonly kind: "local" | "ssh";
46
- readonly profile?: SavedMachine;
46
+ profile?: SavedMachine;
47
47
  owner: Owner;
48
48
  readonly handle: ApplicationMachineAuthorityHandle;
49
49
  readonly listeners: Set<GenerationListener>;
@@ -51,6 +51,7 @@ interface Machine {
51
51
  stopObserver: (() => void) | null;
52
52
  observingOwner: Owner | null;
53
53
  stopStatus: (() => void) | null;
54
+ retired: boolean;
54
55
  }
55
56
 
56
57
  /** Owns independent transport lifetimes; selecting a machine changes only the active route. */
@@ -67,6 +68,7 @@ export function createApplicationMachineAuthorityManager(
67
68
  let selectedId: string = LOCAL_MACHINE_ID;
68
69
  let selectedEpoch = 0;
69
70
  let disposed = false;
71
+ let stopProfiles: (() => void) | null = null;
70
72
  const safe = (callback: () => void) => {
71
73
  try {
72
74
  callback();
@@ -81,7 +83,7 @@ export function createApplicationMachineAuthorityManager(
81
83
  for (const listener of selectedObservers) safe(() => listener(generation));
82
84
  };
83
85
  const changed = (machine: Machine, generation: string | null) => {
84
- if (disposed) return;
86
+ if (disposed || machine.retired) return;
85
87
  machine.epoch++;
86
88
  for (const listener of machine.listeners) safe(() => listener(generation));
87
89
  if (machine.id === selectedId) {
@@ -99,7 +101,7 @@ export function createApplicationMachineAuthorityManager(
99
101
  })
100
102
  .then(
101
103
  (stop) => {
102
- if (disposed || machine.owner !== owner) stop();
104
+ if (disposed || machine.retired || machine.owner !== owner) stop();
103
105
  else machine.stopObserver = stop;
104
106
  },
105
107
  () => {
@@ -127,25 +129,30 @@ export function createApplicationMachineAuthorityManager(
127
129
  stopObserver: null,
128
130
  observingOwner: null,
129
131
  stopStatus: null,
132
+ retired: false,
130
133
  } as Machine;
131
134
  const handle: ApplicationMachineAuthorityHandle = Object.freeze({
132
135
  id,
133
- label,
136
+ get label() {
137
+ return machine.label;
138
+ },
134
139
  kind: machine.kind,
135
140
  ready,
136
- read: () => (disposed ? null : machine.owner.read()),
141
+ read: () => (disposed || machine.retired ? null : machine.owner.read()),
137
142
  isAlive: (info: CanonicalDaemonInfo) =>
138
- disposed ? Promise.resolve(false) : machine.owner.isAlive(info),
143
+ disposed || machine.retired ? Promise.resolve(false) : machine.owner.isAlive(info),
139
144
  endpoint: () =>
140
145
  Object.freeze({
141
146
  ...machine.owner.endpoint(),
142
147
  kind: machine.kind,
143
- label: profile ? label : null,
148
+ label: machine.profile ? machine.label : null,
144
149
  epoch: machine.epoch,
145
- ...(disposed ? { state: "disconnected" as const, remote: null, localBaseUrl: null } : {}),
150
+ ...(disposed || machine.retired
151
+ ? { state: "disconnected" as const, remote: null, localBaseUrl: null }
152
+ : {}),
146
153
  }),
147
154
  observe: async (listener: GenerationListener) => {
148
- if (disposed) return () => {};
155
+ if (disposed || machine.retired) return () => {};
149
156
  if (machine.kind === "local") observe(machine, machine.owner);
150
157
  listeners.add(listener);
151
158
  return () => {
@@ -166,7 +173,7 @@ export function createApplicationMachineAuthorityManager(
166
173
  observe(machine, owner);
167
174
  publish();
168
175
  void initializing.then(
169
- () => settle(!disposed),
176
+ () => settle(!disposed && !machine.retired),
170
177
  () => settle(false),
171
178
  );
172
179
  return machine;
@@ -255,6 +262,46 @@ export function createApplicationMachineAuthorityManager(
255
262
  SavedMachineRegistrySchema.parse({ version: 1, machines: profiles });
256
263
  for (const profile of profiles) if (profile.enabled) manager.add(profile);
257
264
  },
265
+ reconcile(profiles: readonly SavedMachine[]): void {
266
+ if (disposed) return;
267
+ const next = SavedMachineRegistrySchema.parse({ version: 1, machines: profiles });
268
+ const previousSelection = selectedId;
269
+ for (const machine of [...machines.values()]) {
270
+ if (!machine.profile) continue;
271
+ const profile = next.machines.find(
272
+ (candidate) => candidate.id === machine.id && candidate.enabled,
273
+ );
274
+ if (
275
+ profile &&
276
+ profile.sshTarget === machine.profile.sshTarget &&
277
+ profile.expectedEnvironmentId === machine.profile.expectedEnvironmentId
278
+ ) {
279
+ // Labels are presentation metadata, not transport identity. Preserve
280
+ // the connected owner and every consumer holding its stable handle.
281
+ machine.label = profile.label;
282
+ machine.profile = profile;
283
+ continue;
284
+ }
285
+ if (selectedId === machine.id) manager.select(LOCAL_MACHINE_ID);
286
+ machine.retired = true;
287
+ machine.stopStatus?.();
288
+ machine.stopObserver?.();
289
+ for (const listener of machine.listeners) safe(() => listener(null));
290
+ machine.owner.dispose();
291
+ machine.listeners.clear();
292
+ machines.delete(machine.id);
293
+ }
294
+ for (const profile of next.machines) if (profile.enabled) manager.add(profile);
295
+ if (machines.has(previousSelection)) manager.select(previousSelection);
296
+ publish();
297
+ },
298
+ followProfiles(
299
+ subscribe: (onChange: (profiles: readonly SavedMachine[]) => void) => () => void,
300
+ ): void {
301
+ if (disposed) return;
302
+ stopProfiles?.();
303
+ stopProfiles = subscribe(manager.reconcile);
304
+ },
258
305
  read: (): CanonicalDaemonInfo | null => machines.get(selectedId)?.handle.read() ?? null,
259
306
  isAlive: (info: CanonicalDaemonInfo): Promise<boolean> =>
260
307
  machines.get(selectedId)?.handle.isAlive(info) ?? Promise.resolve(false),
@@ -272,6 +319,8 @@ export function createApplicationMachineAuthorityManager(
272
319
  dispose(): void {
273
320
  if (disposed) return;
274
321
  disposed = true;
322
+ stopProfiles?.();
323
+ stopProfiles = null;
275
324
  lifetime.abort();
276
325
  selectedEpoch++;
277
326
  selectedGeneration(null);
@@ -157,7 +157,10 @@ export function createApplicationMachineNavigation(options: {
157
157
  return false;
158
158
  }
159
159
  const selected = candidates[0]!;
160
- const sameSelection = activeSessionKey() === selected.id && options.sessionName() === name;
160
+ const sameSelection =
161
+ manager.snapshot().selectedMachineId === id &&
162
+ activeSessionKey() === selected.id &&
163
+ options.sessionName() === name;
161
164
  setActiveSessionKey(selected.id);
162
165
  expectedLiveSessionId = selected.liveSessionId;
163
166
  server = selected.server;
@@ -269,25 +272,27 @@ export function createApplicationMachineNavigation(options: {
269
272
  for (const tab of tabs.snapshot().tabs) tabs.observe(tab.key, tabAgents(tab));
270
273
  });
271
274
  const sidebar: ApplicationMachineSidebarModel = {
272
- groups: () =>
273
- snapshot().groups.map((group) => ({
274
- ...group,
275
- agentsAvailable:
276
- group.sessions.some((session) => isDefaultSession(group.id, session)) &&
277
- (agentGroups().find((value) => value.machineId === group.id)?.available ?? false),
278
- agents: !group.sessions.some((session) => isDefaultSession(group.id, session))
279
- ? []
280
- : (agentGroups().find((value) => value.machineId === group.id)?.agents ?? []).map(
281
- (agent) => ({
282
- ...agent,
283
- server: group.sessions.find(
284
- (session) =>
285
- session.liveSessionId === agent.liveSessionId &&
286
- isDefaultSession(group.id, session),
287
- )?.server,
288
- }),
289
- ),
290
- })),
275
+ groups: () => {
276
+ const groupsById = new Map(agentGroups().map((group) => [group.machineId, group]));
277
+ return snapshot().groups.map((group) => {
278
+ // Read once per projection: local authority reads validate the record on disk.
279
+ // The next projection (and action admission) still observes replacement immediately.
280
+ const instanceId = manager.getMachine(group.id)?.read()?.instanceId;
281
+ const defaultSessions = group.sessions.filter(
282
+ (session) => !session.server || session.server.generation === instanceId,
283
+ );
284
+ const agents = defaultSessions.length ? groupsById.get(group.id) : undefined;
285
+ return {
286
+ ...group,
287
+ agentsAvailable: agents?.available ?? false,
288
+ agents: (agents?.agents ?? []).map((agent) => ({
289
+ ...agent,
290
+ server: defaultSessions.find((session) => session.liveSessionId === agent.liveSessionId)
291
+ ?.server,
292
+ })),
293
+ };
294
+ });
295
+ },
291
296
  tabs: () => {
292
297
  tabRevision();
293
298
  const value = tabs.snapshot();
@@ -479,7 +484,10 @@ export function createApplicationMachineNavigation(options: {
479
484
  paletteCommands(): readonly ApplicationPaletteCommand[] {
480
485
  const groupsById = new Map(agentGroups().map((group) => [group.machineId, group]));
481
486
  return snapshot().groups.flatMap((group) => {
482
- const agentGroup = !group.sessions.some((session) => isDefaultSession(group.id, session))
487
+ const daemon = manager.getMachine(group.id)?.read();
488
+ const isDefault = (session: { server?: TmuxServerScope }) =>
489
+ !session.server || session.server.generation === daemon?.instanceId;
490
+ const agentGroup = !group.sessions.some((session) => isDefault(session))
483
491
  ? undefined
484
492
  : groupsById.get(group.id);
485
493
  const agentsBySession = new Map<string, ApplicationMachineAgent[]>();
@@ -489,7 +497,6 @@ export function createApplicationMachineNavigation(options: {
489
497
  list.push(agent);
490
498
  agentsBySession.set(agent.liveSessionId, list);
491
499
  }
492
- const daemon = manager.getMachine(group.id)?.read();
493
500
  const sessions = group.sessions.flatMap((session): ApplicationPaletteCommand[] => {
494
501
  if (!session.liveSessionId) return [];
495
502
  const fleet = {
@@ -502,7 +509,7 @@ export function createApplicationMachineNavigation(options: {
502
509
  server: session.server,
503
510
  hostLabel: `${group.label}${session.serverLabel ? ` / ${session.serverLabel}` : ""}`,
504
511
  agentActivities:
505
- isDefaultSession(group.id, session) && agentGroup?.available
512
+ isDefault(session) && agentGroup?.available
506
513
  ? (agentsBySession.get(session.liveSessionId) ?? []).map((a) => ({
507
514
  paneId: a.paneId!,
508
515
  attention: a.attention,
@@ -514,16 +521,15 @@ export function createApplicationMachineNavigation(options: {
514
521
  };
515
522
  return [
516
523
  { kind: "open-session", sessionName: session.name, label: session.name, fleet },
517
- ...(isDefaultSession(group.id, session)
518
- ? (agentsBySession.get(session.liveSessionId) ?? [])
519
- : []
520
- ).map((a) => ({
521
- kind: "jump-agent" as const,
522
- sessionName: session.name,
523
- paneId: a.paneId!,
524
- label: a.name,
525
- fleet: { ...fleet, disabled: fleet.disabled || a.disabled },
526
- })),
524
+ ...(isDefault(session) ? (agentsBySession.get(session.liveSessionId) ?? []) : []).map(
525
+ (a) => ({
526
+ kind: "jump-agent" as const,
527
+ sessionName: session.name,
528
+ paneId: a.paneId!,
529
+ label: a.name,
530
+ fleet: { ...fleet, disabled: fleet.disabled || a.disabled },
531
+ }),
532
+ ),
527
533
  ];
528
534
  });
529
535
  return [
@@ -25,7 +25,7 @@ import {
25
25
  type Accessor,
26
26
  type JSX,
27
27
  } from "solid-js";
28
- import { friendlySessionLabel } from "../terminal-text.ts";
28
+ import { friendlySessionLabel, wrapText } from "../terminal-text.ts";
29
29
  import type { SemanticThemeSnapshot } from "../theme.ts";
30
30
  import { KeyHint } from "../ui/key-hint.tsx";
31
31
  import { NavigationRow } from "../ui/navigation-row.tsx";
@@ -124,6 +124,9 @@ export function ApplicationMachineSidebar(props: {
124
124
  readonly agentRows?: number;
125
125
  readonly onHelp?: (source: "keyboard" | "mouse") => void;
126
126
  }) {
127
+ // All visible sections share one projection for the current reactive inputs.
128
+ // Event handlers retain the model's fresh authority checks at action time.
129
+ const groups = createMemo(() => props.model.groups());
127
130
  const [localFocused, setLocalFocused] = createSignal(false);
128
131
  const focused = () => props.model.focused?.() ?? localFocused();
129
132
  const [localCollapsed, setCollapsed] = createSignal<ReadonlySet<string>>(new Set());
@@ -133,22 +136,22 @@ export function ApplicationMachineSidebar(props: {
133
136
  let scroll: ScrollBoxRenderable | undefined;
134
137
  const agentHeight = () =>
135
138
  (props.agentRows ?? 0) > 0
136
- ? Math.min((props.agentRows ?? 0) + 2, Math.max(0, Math.floor(props.height / 2)))
139
+ ? Math.min(
140
+ (props.agentRows ?? 0) + 2,
141
+ Math.max(0, Math.floor(props.height / 2)),
142
+ Math.max(0, props.height - fixedHeight() - controlsHeight() - 2),
143
+ )
137
144
  : 0;
138
- const controlsHeight = () => (props.height >= 8 && controlGroup() ? 4 : 0);
145
+ const controlsHeight = () => (props.height >= 8 && controlGroup() ? controlTextHeight() + 2 : 0);
139
146
  const searchHeight = () =>
140
147
  (props.model.onOpenSwitcher ? 1 : 0) + (props.model.onOpenAttention ? 1 : 0);
148
+ const fixedHeight = () => 1 + (props.model.onAddMachine ? 1 : 0) + searchHeight();
149
+ // Keep both action rows and at least two machine rows available on short terminals.
150
+ const controlTextHeight = () =>
151
+ Math.min(controlTextLines().length, Math.max(1, props.height - fixedHeight() - 4));
141
152
 
142
153
  const machineHeight = () =>
143
- Math.max(
144
- 0,
145
- props.height -
146
- 1 -
147
- (props.model.onAddMachine ? 1 : 0) -
148
- agentHeight() -
149
- controlsHeight() -
150
- searchHeight(),
151
- );
154
+ Math.max(0, props.height - fixedHeight() - agentHeight() - controlsHeight());
152
155
  const active = (row: Row) =>
153
156
  row.group.id === props.model.activeMachineId() &&
154
157
  (row.agent
@@ -170,7 +173,7 @@ export function ApplicationMachineSidebar(props: {
170
173
  : row.session?.name === props.model.activeSessionName());
171
174
  const preferenceKey = (group: ApplicationMachineGroup) => group.environmentId ?? group.id;
172
175
  const rows = createMemo<readonly Row[]>(() => [
173
- ...props.model.groups().flatMap((group) =>
176
+ ...groups().flatMap((group) =>
174
177
  groupApplicationTeamRows(group.agents ?? []).map((agent) => ({
175
178
  key: JSON.stringify([
176
179
  group.id,
@@ -183,7 +186,7 @@ export function ApplicationMachineSidebar(props: {
183
186
  agent,
184
187
  })),
185
188
  ),
186
- ...props.model.groups().flatMap((group) => [
189
+ ...groups().flatMap((group) => [
187
190
  { key: JSON.stringify([group.id]), group },
188
191
  ...group.sessions
189
192
  .filter(
@@ -255,6 +258,15 @@ export function ApplicationMachineSidebar(props: {
255
258
  : "disconnected"
256
259
  : diagnostic.phase;
257
260
  };
261
+ const controlTextLines = createMemo(() => {
262
+ const group = controlGroup();
263
+ return group
264
+ ? wrapText(
265
+ group.diagnostic ? fleetConnectionMessage(group.diagnostic) : connectionDetail(group),
266
+ Math.max(1, props.width),
267
+ )
268
+ : [];
269
+ });
258
270
  const toggle = (
259
271
  group: ApplicationMachineGroup,
260
272
  value = !collapsed().has(preferenceKey(group)),
@@ -303,7 +315,7 @@ export function ApplicationMachineSidebar(props: {
303
315
  const rowHeight = (row: Row) =>
304
316
  (row.agent ? 2 : row.serverHeading ? 2 : 1) +
305
317
  sectionGap(row) +
306
- (hasAgents() && row.key === JSON.stringify([props.model.groups()[0]?.id]) ? 1 : 0);
318
+ (hasAgents() && row.key === JSON.stringify([groups()[0]?.id]) ? 1 : 0);
307
319
  const revealFocusedRow = () => {
308
320
  if (!focused() || !scroll) return;
309
321
  const y = rows()
@@ -528,9 +540,7 @@ export function ApplicationMachineSidebar(props: {
528
540
  }}
529
541
  />
530
542
  </Show>
531
- <Show
532
- when={hasAgents() && row.key === JSON.stringify([props.model.groups()[0]?.id])}
533
- >
543
+ <Show when={hasAgents() && row.key === JSON.stringify([groups()[0]?.id])}>
534
544
  <text height={1} fg={props.theme.roles.text.secondary}>
535
545
  {" "}
536
546
  Machines
@@ -646,7 +656,7 @@ export function ApplicationMachineSidebar(props: {
646
656
  <KeyHint
647
657
  theme={props.theme}
648
658
  keys={CHROME_ACTIONS.attention.keys}
649
- label={`${CHROME_ACTIONS.attention.label} (${props.model.groups().reduce((sum, group) => sum + (group.state === "ready" ? (group.agents ?? []).filter((agent) => agent.attention && !agent.disabled).length : 0), 0)})`}
659
+ label={`${CHROME_ACTIONS.attention.label} (${groups().reduce((sum, group) => sum + (group.state === "ready" ? (group.agents ?? []).filter((agent) => agent.attention && !agent.disabled).length : 0), 0)})`}
650
660
  width={props.width}
651
661
  quiet
652
662
  button
@@ -656,10 +666,8 @@ export function ApplicationMachineSidebar(props: {
656
666
  <Show when={controlsHeight() > 0 && controlGroup()}>
657
667
  {(group) => (
658
668
  <>
659
- <text height={2} fg={props.theme.roles.text.secondary}>
660
- {group().diagnostic
661
- ? fleetConnectionMessage(group().diagnostic!)
662
- : connectionDetail(group())}
669
+ <text height={controlTextHeight()} flexShrink={0} fg={props.theme.roles.text.secondary}>
670
+ {controlTextLines().join("\n")}
663
671
  </text>
664
672
  <KeyHint
665
673
  theme={props.theme}
@@ -1,8 +1,9 @@
1
1
  import { resolveRuntimeNamespace } from "../../../lib/runtime-namespace.ts";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { SavedMachineSchema, type SavedMachine } from "@tmux-ide/contracts";
4
- import { loadSavedMachines } from "../../../lib/saved-machines.ts";
4
+ import { loadSavedMachines, watchSavedMachines } from "../../../lib/saved-machines.ts";
5
5
  import { applicationMachineAuthorityManager } from "./application-machine-authority.ts";
6
+ import { tuiPerfMark } from "./application-performance-log.ts";
6
7
 
7
8
  /** Labels describe access routes; they never become routing identities. */
8
9
  export function ephemeralMachineProfile(
@@ -25,16 +26,51 @@ export function ephemeralMachineProfile(
25
26
 
26
27
  /** Local discovery and rendering never wait for any remote handshake. */
27
28
  export function initializeApplicationMachines(aliases: readonly string[]): void {
28
- const profiles = resolveRuntimeNamespace().development ? [] : [...loadSavedMachines().machines];
29
+ const development = resolveRuntimeNamespace().development;
30
+ const profiles = development ? [] : [...loadSavedMachines().machines];
31
+ const ephemeral: SavedMachine[] = [];
29
32
  let preferred: string | null = null;
30
33
  for (const alias of aliases) {
31
34
  let profile = profiles.find((p) => p.sshTarget === alias && p.enabled);
32
35
  if (!profile) {
33
36
  profile = ephemeralMachineProfile(alias, profiles);
34
37
  profiles.push(profile);
38
+ ephemeral.push(profile);
35
39
  }
36
40
  preferred ??= profile.id;
37
41
  }
38
42
  applicationMachineAuthorityManager.initialize(profiles);
39
43
  if (preferred) applicationMachineAuthorityManager.select(preferred);
44
+ if (!development) {
45
+ const failed = () =>
46
+ tuiPerfMark("machine-registry-error", { reason: "registry-unavailable-or-invalid" });
47
+ try {
48
+ applicationMachineAuthorityManager.followProfiles((onChange) =>
49
+ watchSavedMachines((registry) => {
50
+ const next = [...registry.machines];
51
+ for (let index = 0; index < ephemeral.length; index++) {
52
+ let profile = ephemeral[index]!;
53
+ // A newly saved route may claim an ephemeral display label. Keep the
54
+ // explicit connection's identity while making the label unambiguous.
55
+ if (
56
+ next.some(
57
+ (entry) =>
58
+ entry.label.normalize("NFKC").toLowerCase() ===
59
+ profile.label.normalize("NFKC").toLowerCase(),
60
+ )
61
+ )
62
+ profile = {
63
+ ...profile,
64
+ label: ephemeralMachineProfile(profile.sshTarget, next).label,
65
+ };
66
+ ephemeral[index] = profile;
67
+ next.push(profile);
68
+ }
69
+ onChange(next);
70
+ }, failed),
71
+ );
72
+ } catch {
73
+ failed();
74
+ }
75
+ }
40
76
  }
@@ -1,4 +1,4 @@
1
- import { createMemo, createSignal } from "solid-js";
1
+ import { createMemo, createSignal, untrack } from "solid-js";
2
2
  import {
3
3
  applicationCommandDescription,
4
4
  filterApplicationCommands,
@@ -28,7 +28,7 @@ export function createApplicationPaletteSearchOwner(options: {
28
28
  const [help, setHelp] = createSignal(false);
29
29
  const [query, setQuery] = createSignal("");
30
30
  const [selectedId, setSelectedId] = createSignal<string | null>(null);
31
- const commands = createMemo(() =>
31
+ const readCommands = () =>
32
32
  filterApplicationCommands(
33
33
  options
34
34
  .commands()
@@ -36,8 +36,11 @@ export function createApplicationPaletteSearchOwner(options: {
36
36
  (c) => !localOnly() || typeof c === "string" || !c.fleet || c.fleet.machineId === "local",
37
37
  ),
38
38
  query(),
39
- ),
40
- );
39
+ );
40
+ // Closed overlays must not subscribe to catalog churn. Explicit reads still
41
+ // resolve current commands, including reset() before the open action commits.
42
+ const activeCommands = createMemo(() => (options.open() ? readCommands() : null));
43
+ const commands = () => activeCommands() ?? untrack(readCommands);
41
44
  const selection = () =>
42
45
  Math.max(
43
46
  0,
@@ -34,6 +34,8 @@ export const tuiLifecycleStream = stream ? Object.freeze({ enabled: true as cons
34
34
  export const tuiPerfStream = TUI_PERF_LOG ? tuiLifecycleStream : null;
35
35
 
36
36
  const LIFECYCLE_PHASES = new Set([
37
+ "machine-connection-status",
38
+ "machine-registry-error",
37
39
  "generation-status",
38
40
  "generation-connection-start",
39
41
  "generation-connection-resolved",
@@ -170,6 +172,7 @@ export function closeTuiPerfMarks(): Promise<void> {
170
172
  closing = true;
171
173
  closePromise = (async () => {
172
174
  if (!stream) return;
175
+ await writer?.flush();
173
176
  await flushTuiPerfMarks();
174
177
  // No drain callback may enqueue another critical record after end().
175
178
  stream.off("drain", drain);
@@ -427,7 +427,7 @@ export async function startApplicationRoot(options: StartApplicationRootOptions
427
427
  const currentShell = shell();
428
428
  semanticViewportResize.adopt(dimensions(), currentShell.semantic, generation());
429
429
  focusedPane();
430
- terminalInputIngress.adopt();
430
+ terminalInputIngress.adopt(activeSurface() === "terminals");
431
431
  });
432
432
  const sidebarShortcut = createApplicationSidebarShortcuts(
433
433
  activeSurface,
@@ -497,52 +497,53 @@ export function ApplicationShellView(props: ApplicationShellViewProps): JSX.Elem
497
497
  />
498
498
  }
499
499
  >
500
- {(model) => (
501
- <ApplicationMachineSidebar
502
- model={model()}
503
- paneName={(endpoint) =>
504
- nameForCurrentEndpoint(
505
- model()
506
- .groups()
507
- .flatMap((group) => group.agents ?? []),
508
- endpoint,
509
- )
510
- }
511
- interactionForAgent={(agent) =>
512
- interactionForCurrentPane(
513
- props.paneInteractions?.(),
514
- agent.interactionEndpoint,
515
- agent.nativeIdentity,
516
- )
517
- }
518
- onHelp={(source) => {
519
- props.onSetPaletteOpen(true, source);
520
- props.onPaletteReferenceChange?.("help");
521
- }}
522
- agentRows={
523
- model()
524
- .groups()
525
- .some((group) => group.agents !== undefined)
526
- ? 0
527
- : shell.semantic.sidebar.agents.length
528
- }
529
- agents={
530
- <ApplicationSidebarAgents
531
- shell={shell}
532
- theme={appearance.theme}
533
- width={Math.max(1, shell.layout.sidebar.width - 1)}
534
- onIntent={(intent) => {
535
- if (intent.type !== "agent.open") return;
536
- model().onBlur?.();
537
- props.onOpenAgent?.(intent.sessionName, intent.paneId, intent.source);
538
- }}
539
- />
540
- }
541
- width={shell.layout.sidebar.width}
542
- height={shell.layout.sidebar.height}
543
- theme={appearance.theme}
544
- />
545
- )}
500
+ {(model) => {
501
+ // Sizing, rows and pane labels share this render projection.
502
+ // Keep the navigation model and its action-time reads intact.
503
+ const groups = createMemo(() => model().groups());
504
+ return (
505
+ <ApplicationMachineSidebar
506
+ model={{ ...model(), groups }}
507
+ paneName={(endpoint) =>
508
+ nameForCurrentEndpoint(
509
+ groups().flatMap((group) => group.agents ?? []),
510
+ endpoint,
511
+ )
512
+ }
513
+ interactionForAgent={(agent) =>
514
+ interactionForCurrentPane(
515
+ props.paneInteractions?.(),
516
+ agent.interactionEndpoint,
517
+ agent.nativeIdentity,
518
+ )
519
+ }
520
+ onHelp={(source) => {
521
+ props.onSetPaletteOpen(true, source);
522
+ props.onPaletteReferenceChange?.("help");
523
+ }}
524
+ agentRows={
525
+ groups().some((group) => group.agents !== undefined)
526
+ ? 0
527
+ : shell.semantic.sidebar.agents.length
528
+ }
529
+ agents={
530
+ <ApplicationSidebarAgents
531
+ shell={shell}
532
+ theme={appearance.theme}
533
+ width={Math.max(1, shell.layout.sidebar.width - 1)}
534
+ onIntent={(intent) => {
535
+ if (intent.type !== "agent.open") return;
536
+ model().onBlur?.();
537
+ props.onOpenAgent?.(intent.sessionName, intent.paneId, intent.source);
538
+ }}
539
+ />
540
+ }
541
+ width={shell.layout.sidebar.width}
542
+ height={shell.layout.sidebar.height}
543
+ theme={appearance.theme}
544
+ />
545
+ );
546
+ }}
546
547
  </Show>
547
548
  }
548
549
  >