tmux-ide 2.9.0-beta.21 → 2.9.0-beta.26

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 (209) hide show
  1. package/bin/cli.js +46791 -43589
  2. package/package.json +2 -2
  3. package/packages/contracts/src/__tests__/daemon-wire.test.ts +2 -2
  4. package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +35 -0
  5. package/packages/contracts/src/__tests__/issue-error.test.ts +1 -1
  6. package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +3 -1
  7. package/packages/contracts/src/__tests__/pane-stream.test.ts +17 -5
  8. package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +166 -0
  9. package/packages/contracts/src/__tests__/window-links.test.ts +243 -0
  10. package/packages/contracts/src/actions-contract.ts +12 -0
  11. package/packages/contracts/src/daemon-resource-request.test.ts +1 -1
  12. package/packages/contracts/src/daemon-wire.ts +10 -5
  13. package/packages/contracts/src/fleet-client-state.ts +3 -0
  14. package/packages/contracts/src/fleet-lifecycle.ts +5 -0
  15. package/packages/contracts/src/index.ts +2 -0
  16. package/packages/contracts/src/interaction-receipts.ts +39 -0
  17. package/packages/contracts/src/issue-error.ts +1 -1
  18. package/packages/contracts/src/multiplexer-verbs.ts +25 -1
  19. package/packages/contracts/src/pane-stream.ts +51 -8
  20. package/packages/contracts/src/tmux-server-scope.ts +172 -0
  21. package/packages/contracts/src/window-links.ts +76 -0
  22. package/packages/contracts/src/workspace-multiplexer.ts +54 -0
  23. package/packages/core/src/interaction-receipts.ts +4 -0
  24. package/packages/daemon/dist/command-center/actions/command-definitions.js +6 -0
  25. package/packages/daemon/dist/command-center/actions/handlers/workspace-multiplexer.js +2 -0
  26. package/packages/daemon/dist/command-center/actions/registry.js +13 -1
  27. package/packages/daemon/dist/command-center/discovery.js +2 -9
  28. package/packages/daemon/dist/command-center/server.js +21 -1
  29. package/packages/daemon/dist/command-center/tmux-server-native-backing.js +37 -0
  30. package/packages/daemon/dist/command-center/tmux-servers.js +351 -0
  31. package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +64 -8
  32. package/packages/daemon/dist/lib/canonical-daemon.js +5 -2
  33. package/packages/daemon/dist/lib/daemon-embed.js +198 -30
  34. package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +147 -0
  35. package/packages/daemon/dist/lib/fleet-lifecycle-authority.js +31 -11
  36. package/packages/daemon/dist/lib/headless-daemon.js +10 -1
  37. package/packages/daemon/dist/lib/runtime-namespace.js +17 -3
  38. package/packages/daemon/dist/lib/tmux-client-execution.js +59 -0
  39. package/packages/daemon/dist/lib/tmux-named-socket-fence.js +4 -0
  40. package/packages/daemon/dist/lib/tmux-server-generation-runner.js +62 -0
  41. package/packages/daemon/dist/lib/tmux-server-owner.js +285 -0
  42. package/packages/daemon/dist/lib/tmux-server-owners.js +192 -0
  43. package/packages/daemon/dist/lib/tmux-server-proof.js +82 -0
  44. package/packages/daemon/dist/lib/tmux-server-registration.js +85 -0
  45. package/packages/daemon/dist/lib/tmux-server-session-create.js +43 -0
  46. package/packages/daemon/dist/lib/tmux-server-session-open.js +101 -0
  47. package/packages/daemon/dist/lib/tmux-servers-cli.js +134 -0
  48. package/packages/daemon/dist/lib/tmux-session-mutation-fence.js +55 -0
  49. package/packages/daemon/dist/lib/tmux-window-link-guard.js +88 -0
  50. package/packages/daemon/dist/lib/workspace-config-loader.js +3 -0
  51. package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +82 -1
  52. package/packages/daemon/dist/lib/workspace-pane-creation.js +13 -57
  53. package/packages/daemon/dist/lib/workspace-promotion.js +91 -12
  54. package/packages/daemon/dist/lib/workspace-registry.js +4 -0
  55. package/packages/daemon/dist/server/pane-stream-upgrade.js +20 -6
  56. package/packages/daemon/dist/terminal/attachments/native-runtime.js +61 -17
  57. package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +9 -1
  58. package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +11 -2
  59. package/packages/daemon/dist/terminal/mirror/control-channel.js +65 -6
  60. package/packages/daemon/dist/terminal/mirror/mirror-service.js +44 -0
  61. package/packages/daemon/dist/terminal/mirror/session-channel.js +210 -50
  62. package/packages/daemon/dist/terminal/mirror/window-link-authority.js +160 -0
  63. package/packages/daemon/dist/terminal/pane-stream/lease-manager.js +3 -3
  64. package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +40 -6
  65. package/packages/daemon/dist/terminal/protocol/live-session-identity.js +9 -0
  66. package/packages/daemon/dist/terminal/protocol/native-backing-client.js +2 -1
  67. package/packages/daemon/dist/terminal/session-runtime/interaction-receipt-facts.js +9 -0
  68. package/packages/daemon/dist/terminal/session-runtime/registry.js +6 -0
  69. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +1 -1
  70. package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-resource-changes.js +1 -0
  71. package/packages/daemon/dist/tui/mirror/application-shell-daemon-connection.js +7 -2
  72. package/packages/daemon/dist/tui/mirror/application-shell-server-connection.js +114 -0
  73. package/packages/daemon/dist/tui/mirror/menu-model.js +1 -1
  74. package/packages/daemon/dist/tui/mirror/open-tui-verified-routing.js +34 -4
  75. package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +29 -8
  76. package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-integration.jsx +119 -0
  77. package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-owner.js +132 -0
  78. package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +21 -9
  79. package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-transport.js +63 -1
  80. package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +8 -2
  81. package/packages/daemon/dist/tui/mirror/runtime/application-home-agents.js +3 -1
  82. package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog-owner.js +11 -5
  83. package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog.js +140 -75
  84. package/packages/daemon/dist/tui/mirror/runtime/application-home-experience.js +40 -0
  85. package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +132 -0
  86. package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +9 -0
  87. package/packages/daemon/dist/tui/mirror/runtime/application-machine-catalog.js +12 -2
  88. package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +211 -31
  89. package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +91 -37
  90. package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +20 -7
  91. package/packages/daemon/dist/tui/mirror/runtime/application-root-configuration.js +9 -3
  92. package/packages/daemon/dist/tui/mirror/runtime/application-route-connection.js +95 -2
  93. package/packages/daemon/dist/tui/mirror/runtime/application-shell-home.jsx +6 -2
  94. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +158 -2
  95. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace-policy.js +43 -0
  96. package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +78 -25
  97. package/packages/daemon/dist/tui/mirror/runtime/application-theme-repaint.js +9 -0
  98. package/packages/daemon/dist/tui/mirror/runtime/fleet-lifecycle-client.js +44 -1
  99. package/packages/daemon/dist/tui/mirror/runtime/guided-tour-coach.jsx +54 -0
  100. package/packages/daemon/dist/tui/mirror/runtime/guided-tour-storage.js +67 -0
  101. package/packages/daemon/dist/tui/mirror/runtime/guided-tour.js +113 -0
  102. package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +1 -1
  103. package/packages/daemon/dist/tui/mirror/runtime/resize-terminal-pane.js +4 -0
  104. package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +2 -1
  105. package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +12 -4
  106. package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +6 -3
  107. package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +11 -2
  108. package/packages/daemon/dist/tui/mirror/workspace/terminal-window-strip.jsx +2 -1
  109. package/packages/daemon/src/command-center/actions/command-definitions.ts +6 -0
  110. package/packages/daemon/src/command-center/actions/errors.ts +4 -0
  111. package/packages/daemon/src/command-center/actions/handlers/workspace-multiplexer.ts +2 -0
  112. package/packages/daemon/src/command-center/actions/registry.ts +14 -0
  113. package/packages/daemon/src/command-center/discovery.ts +2 -10
  114. package/packages/daemon/src/command-center/server.ts +29 -1
  115. package/packages/daemon/src/command-center/tmux-server-native-backing.ts +49 -0
  116. package/packages/daemon/src/command-center/tmux-servers.ts +479 -0
  117. package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +111 -4
  118. package/packages/daemon/src/lib/canonical-daemon.ts +15 -3
  119. package/packages/daemon/src/lib/daemon-embed.ts +241 -28
  120. package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +190 -0
  121. package/packages/daemon/src/lib/fleet-lifecycle-authority.ts +33 -9
  122. package/packages/daemon/src/lib/headless-daemon.ts +18 -1
  123. package/packages/daemon/src/lib/runtime-namespace.ts +24 -3
  124. package/packages/daemon/src/lib/tmux-client-execution.ts +56 -0
  125. package/packages/daemon/src/lib/tmux-named-socket-fence.ts +5 -0
  126. package/packages/daemon/src/lib/tmux-server-generation-runner.ts +81 -0
  127. package/packages/daemon/src/lib/tmux-server-owner.ts +336 -0
  128. package/packages/daemon/src/lib/tmux-server-owners.ts +228 -0
  129. package/packages/daemon/src/lib/tmux-server-proof.ts +95 -0
  130. package/packages/daemon/src/lib/tmux-server-registration.ts +105 -0
  131. package/packages/daemon/src/lib/tmux-server-session-create.ts +52 -0
  132. package/packages/daemon/src/lib/tmux-server-session-open.ts +106 -0
  133. package/packages/daemon/src/lib/tmux-servers-cli.ts +186 -0
  134. package/packages/daemon/src/lib/tmux-session-mutation-fence.ts +62 -0
  135. package/packages/daemon/src/lib/tmux-window-link-guard.ts +112 -0
  136. package/packages/daemon/src/lib/workspace-config-loader.ts +3 -0
  137. package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +121 -1
  138. package/packages/daemon/src/lib/workspace-pane-creation.ts +16 -55
  139. package/packages/daemon/src/lib/workspace-promotion.ts +98 -12
  140. package/packages/daemon/src/lib/workspace-registry.ts +5 -0
  141. package/packages/daemon/src/server/pane-stream-upgrade.ts +25 -5
  142. package/packages/daemon/src/terminal/attachments/native-runtime.ts +70 -16
  143. package/packages/daemon/src/terminal/mirror/control-channel.ts +64 -7
  144. package/packages/daemon/src/terminal/mirror/events.ts +2 -0
  145. package/packages/daemon/src/terminal/mirror/mirror-service.ts +56 -0
  146. package/packages/daemon/src/terminal/mirror/session-channel.ts +245 -50
  147. package/packages/daemon/src/terminal/mirror/window-link-authority.ts +203 -0
  148. package/packages/daemon/src/terminal/pane-stream/lease-manager.ts +3 -3
  149. package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +50 -6
  150. package/packages/daemon/src/terminal/protocol/live-session-identity.ts +14 -0
  151. package/packages/daemon/src/terminal/protocol/native-backing-client.ts +4 -1
  152. package/packages/daemon/src/terminal/session-runtime/interaction-receipt-facts.ts +9 -0
  153. package/packages/daemon/src/terminal/session-runtime/registry.ts +9 -0
  154. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +2 -2
  155. package/packages/daemon/src/terminal/session-runtime/semantic-mutation-resource-changes.ts +1 -0
  156. package/packages/daemon/src/tui/mirror/application-shell-daemon-connection.ts +28 -2
  157. package/packages/daemon/src/tui/mirror/application-shell-server-connection.ts +154 -0
  158. package/packages/daemon/src/tui/mirror/menu-model.ts +1 -1
  159. package/packages/daemon/src/tui/mirror/open-tui-verified-routing.ts +41 -3
  160. package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +47 -6
  161. package/packages/daemon/src/tui/mirror/runtime/application-entry.ts +2 -1
  162. package/packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions.tsx +10 -4
  163. package/packages/daemon/src/tui/mirror/runtime/application-fleet-tabs.ts +2 -0
  164. package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-integration.tsx +199 -0
  165. package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-owner.ts +162 -0
  166. package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +36 -10
  167. package/packages/daemon/src/tui/mirror/runtime/application-home-agent-transport.ts +69 -0
  168. package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +12 -2
  169. package/packages/daemon/src/tui/mirror/runtime/application-home-agents.ts +13 -2
  170. package/packages/daemon/src/tui/mirror/runtime/application-home-catalog-owner.ts +13 -3
  171. package/packages/daemon/src/tui/mirror/runtime/application-home-catalog.ts +172 -100
  172. package/packages/daemon/src/tui/mirror/runtime/application-home-experience.ts +59 -0
  173. package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +175 -0
  174. package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +9 -1
  175. package/packages/daemon/src/tui/mirror/runtime/application-machine-catalog.ts +19 -7
  176. package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +285 -36
  177. package/packages/daemon/src/tui/mirror/runtime/application-machine-overlays.tsx +1 -1
  178. package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +124 -40
  179. package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +3 -0
  180. package/packages/daemon/src/tui/mirror/runtime/application-performance-log.ts +18 -6
  181. package/packages/daemon/src/tui/mirror/runtime/application-root-configuration.ts +12 -3
  182. package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +40 -40
  183. package/packages/daemon/src/tui/mirror/runtime/application-route-connection.ts +93 -1
  184. package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +4 -0
  185. package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +20 -1
  186. package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +10 -0
  187. package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +177 -2
  188. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-policy.ts +62 -0
  189. package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +87 -30
  190. package/packages/daemon/src/tui/mirror/runtime/application-theme-repaint.ts +19 -0
  191. package/packages/daemon/src/tui/mirror/runtime/fleet-lifecycle-client.ts +55 -0
  192. package/packages/daemon/src/tui/mirror/runtime/guided-tour-coach.tsx +118 -0
  193. package/packages/daemon/src/tui/mirror/runtime/guided-tour-storage.ts +72 -0
  194. package/packages/daemon/src/tui/mirror/runtime/guided-tour.ts +147 -0
  195. package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +1 -1
  196. package/packages/daemon/src/tui/mirror/runtime/resize-terminal-pane.ts +3 -0
  197. package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +3 -0
  198. package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +11 -4
  199. package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +15 -4
  200. package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +13 -2
  201. package/packages/daemon/src/tui/mirror/workspace/terminal-window-strip.tsx +5 -1
  202. package/packages/daemon-client/package.json +3 -1
  203. package/packages/daemon-client/src/index.ts +2 -0
  204. package/packages/daemon-client/src/pane-stream-client.test.ts +108 -38
  205. package/packages/daemon-client/src/pane-stream-client.ts +26 -0
  206. package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +114 -0
  207. package/packages/daemon-client/src/scoped-tmux-server-transport.ts +385 -0
  208. package/packages/daemon-client/src/tmux-server-client.test.ts +279 -0
  209. package/packages/daemon-client/src/tmux-server-client.ts +287 -0
@@ -7,6 +7,16 @@ import { createSignal, onCleanup } from "solid-js";
7
7
  import { applicationMachineAuthorityManager as manager } from "./application-machine-authority.js";
8
8
  import { createApplicationMachineCatalog } from "./application-machine-catalog.js";
9
9
  import { ephemeralMachineProfile } from "./application-machine-startup.js";
10
+ /** Exact attached authority; an unscoped default connection still proves its incarnation. */
11
+ function attachedMachineNavigationTarget(active) {
12
+ if (active?.status !== "live" || !active.connection?.liveSessionId || !active.daemonGeneration)
13
+ return null;
14
+ return {
15
+ liveSessionId: active.connection.liveSessionId,
16
+ daemonGeneration: active.daemonGeneration,
17
+ ...(active.connection.server ? { server: active.connection.server } : {}),
18
+ };
19
+ }
10
20
  export function createApplicationMachineNavigation(options) {
11
21
  const preferences = createApplicationFleetPreferences({
12
22
  onError: options.setNote,
@@ -31,10 +41,12 @@ export function createApplicationMachineNavigation(options) {
31
41
  const [snapshot, setSnapshot] = createSignal(catalog.getSnapshot());
32
42
  const [focused, setFocused] = createSignal(false);
33
43
  const [switching, setSwitching] = createSignal(false);
44
+ const [serverFocus, setServerFocus] = createSignal(null);
34
45
  const [attentionOnly, setAttentionOnly] = createSignal(false);
35
46
  const showSwitcher = (attention) => {
36
47
  navigation++;
37
48
  options.cancelOpen();
49
+ setServerFocus(null);
38
50
  setAttentionOnly(attention);
39
51
  setFocused(false);
40
52
  setSwitching(true);
@@ -42,39 +54,80 @@ export function createApplicationMachineNavigation(options) {
42
54
  const [adding, setAdding] = createSignal(false);
43
55
  const [alias, setAlias] = createSignal("");
44
56
  const [error, setError] = createSignal(null);
45
- const stop = catalog.subscribe(setSnapshot);
57
+ let attemptStartup = null;
58
+ const stop = catalog.subscribe((value) => {
59
+ setSnapshot(value);
60
+ attemptStartup?.();
61
+ });
46
62
  let navigation = 0;
63
+ const [activeSessionKey, setActiveSessionKey] = createSignal(null);
47
64
  const [tabRevision, setTabRevision] = createSignal(0);
48
65
  const history = [...saved().recent].reverse();
49
66
  let historyIndex = history.length - 1;
50
- const select = (id, expectedLiveSessionId) => {
67
+ const select = (id, expectedLiveSessionId, server) => {
51
68
  if (!manager.getMachine(id))
52
69
  return false;
53
70
  if (manager.snapshot().selectedMachineId !== id) {
54
71
  navigation++;
55
72
  options.cancelOpen();
56
73
  // Synchronously remove the old input/shell owner before changing the route.
57
- options.resetWorkspace(id, expectedLiveSessionId);
74
+ if (server)
75
+ options.resetWorkspace(id, expectedLiveSessionId, server);
76
+ else
77
+ options.resetWorkspace(id, expectedLiveSessionId);
58
78
  manager.select(id);
59
79
  }
60
80
  else if (expectedLiveSessionId) {
61
- options.resetWorkspace(id, expectedLiveSessionId);
81
+ const attached = options.attachedTarget?.() ??
82
+ attachedMachineNavigationTarget(options.attachedGeneration?.() ?? null);
83
+ const sameAuthority = attached?.liveSessionId === expectedLiveSessionId &&
84
+ (server
85
+ ? attached.server
86
+ ? attached.server.serverId === server.serverId &&
87
+ attached.server.generation === server.generation
88
+ : attached.daemonGeneration === server.generation &&
89
+ manager.getMachine(id)?.read()?.instanceId === server.generation
90
+ : !attached.server);
91
+ if (sameAuthority)
92
+ return true;
93
+ if (server)
94
+ options.resetWorkspace(id, expectedLiveSessionId, server);
95
+ else
96
+ options.resetWorkspace(id, expectedLiveSessionId);
62
97
  }
63
98
  return true;
64
99
  };
65
- const open = async (id, name, source, remember = true, expectedLiveSessionId) => {
100
+ const open = async (id, name, source, remember = true, expectedLiveSessionId, server, sessionKey) => {
66
101
  // A same-machine session click supersedes any pending exact-agent focus too.
67
102
  if (manager.snapshot().selectedMachineId === id)
68
103
  options.cancelOpen();
69
104
  // A sidebar selection can leave a tab whose route is pinned to a different
70
105
  // live incarnation on this same machine. Select a new exact owner before
71
106
  // preparation; never apply that old tab's identity fence to the new name.
72
- if (!expectedLiveSessionId && options.sessionName() !== name) {
73
- expectedLiveSessionId = snapshot()
74
- .groups.find((group) => group.id === id || group.routeIds?.includes(id))
75
- ?.sessions.find((row) => row.name === name && !row.disabled)?.liveSessionId;
107
+ if (manager.getMachine(id)?.endpoint().state !== "ready") {
108
+ select(id);
109
+ options.setNote("This machine is disconnected. Select the session after reconnecting.");
110
+ return false;
76
111
  }
77
- if (!select(id, expectedLiveSessionId))
112
+ const candidates = snapshot()
113
+ .groups.find((group) => group.id === id || group.routeIds?.includes(id))
114
+ ?.sessions.filter((row) => row.name === name &&
115
+ !row.disabled &&
116
+ (!sessionKey || row.id === sessionKey) &&
117
+ (!expectedLiveSessionId || row.liveSessionId === expectedLiveSessionId) &&
118
+ (!server ||
119
+ (row.server?.serverId === server.serverId &&
120
+ row.server.generation === server.generation))) ?? [];
121
+ if (candidates.length !== 1) {
122
+ options.setNote("Select the session under its tmux server; this target is ambiguous or has changed.");
123
+ return false;
124
+ }
125
+ const selected = candidates[0];
126
+ const sameSelection = activeSessionKey() === selected.id && options.sessionName() === name;
127
+ setActiveSessionKey(selected.id);
128
+ expectedLiveSessionId = selected.liveSessionId;
129
+ server = selected.server;
130
+ if (!select(id, sameSelection ? undefined : expectedLiveSessionId, server))
78
131
  return;
79
132
  const token = ++navigation;
80
133
  setFocused(false);
@@ -92,15 +145,21 @@ export function createApplicationMachineNavigation(options) {
92
145
  if (token === navigation) {
93
146
  const session = snapshot()
94
147
  .groups.find((group) => group.id === id || group.routeIds?.includes(id))
95
- ?.sessions.find((row) => row.name === name && !row.disabled);
148
+ ?.sessions.find((row) => row.id === selected.id && !row.disabled);
96
149
  if (session) {
97
150
  if (session.liveSessionId)
98
151
  tabs.remember({
99
- key: JSON.stringify([id, session.liveSessionId]),
152
+ key: JSON.stringify([
153
+ id,
154
+ session.server?.serverId,
155
+ session.server?.generation,
156
+ session.liveSessionId,
157
+ ]),
158
+ server: session.server,
100
159
  machineId: id,
101
160
  liveSessionId: session.liveSessionId,
102
161
  label: session.name,
103
- hostLabel: manager.getMachine(id)?.label ?? id,
162
+ hostLabel: `${manager.getMachine(id)?.label ?? id}${session.serverLabel ? ` / ${session.serverLabel}` : ""}`,
104
163
  });
105
164
  preferences.change({ type: "visit", key: session.id });
106
165
  if (remember && history[historyIndex] !== session.id) {
@@ -118,26 +177,39 @@ export function createApplicationMachineNavigation(options) {
118
177
  if (manager.getMachine(target.machineId)?.endpoint().state !== "ready")
119
178
  return null;
120
179
  const group = snapshot().groups.find((g) => g.id === target.machineId || g.routeIds?.includes(target.machineId));
121
- const row = group?.sessions.find((s) => s.liveSessionId === target.liveSessionId && !s.disabled);
180
+ const row = group?.sessions.find((s) => s.liveSessionId === target.liveSessionId &&
181
+ !s.disabled &&
182
+ s.server?.serverId === target.server?.serverId &&
183
+ s.server?.generation === target.server?.generation);
122
184
  return row ? { ...target, label: row.name } : null;
123
185
  };
124
186
  const tabs = createFleetTabs({
125
187
  resolve: resolveTab,
126
188
  retireActive: () => {
189
+ setActiveSessionKey(null);
127
190
  navigation++;
128
191
  options.cancelOpen();
129
192
  options.resetWorkspace(manager.snapshot().selectedMachineId);
130
193
  options.setSurface("home");
131
194
  },
132
- open: async (target) => !!(await open(target.machineId, target.label, "keyboard", true, target.liveSessionId)),
195
+ open: async (target) => !!(await open(target.machineId, target.label, "keyboard", true, target.liveSessionId, target.server)),
133
196
  publish: () => setTabRevision((value) => value + 1),
134
197
  unavailable: () => options.setNote("That tab's session is unavailable or has been replaced."),
135
198
  });
199
+ const isDefaultSession = (machineId, session) => !session.server ||
200
+ session.server.generation === manager.getMachine(machineId)?.read()?.instanceId;
136
201
  const sidebar = {
137
202
  groups: () => snapshot().groups.map((group) => ({
138
203
  ...group,
139
- agentsAvailable: agentGroups().find((value) => value.machineId === group.id)?.available ?? false,
140
- agents: agentGroups().find((value) => value.machineId === group.id)?.agents ?? [],
204
+ agentsAvailable: group.sessions.some((session) => isDefaultSession(group.id, session)) &&
205
+ (agentGroups().find((value) => value.machineId === group.id)?.available ?? false),
206
+ agents: !group.sessions.some((session) => isDefaultSession(group.id, session))
207
+ ? []
208
+ : (agentGroups().find((value) => value.machineId === group.id)?.agents ?? []).map((agent) => ({
209
+ ...agent,
210
+ server: group.sessions.find((session) => session.liveSessionId === agent.liveSessionId &&
211
+ isDefaultSession(group.id, session))?.server,
212
+ })),
141
213
  })),
142
214
  tabs: () => {
143
215
  tabRevision();
@@ -158,6 +230,7 @@ export function createApplicationMachineNavigation(options) {
158
230
  onCollapse: (key, enabled) => preferences.change({ type: "collapse", key, enabled }),
159
231
  activeMachineId: () => snapshot().selectedMachineId,
160
232
  activeSessionName: options.sessionName,
233
+ activeSessionKey,
161
234
  activePaneId: options.activePaneId,
162
235
  focused,
163
236
  onFocus: () => {
@@ -166,8 +239,14 @@ export function createApplicationMachineNavigation(options) {
166
239
  setFocused(true);
167
240
  },
168
241
  onBlur: () => setFocused(false),
169
- onOpen: (id, name, source) => void open(id, name, source),
242
+ onOpen: (id, name, source, sessionKey) => void open(id, name, source, true, undefined, undefined, sessionKey),
170
243
  onOpenAgent: (id, sessionName, paneId, source) => {
244
+ const sessions = snapshot().groups.find((group) => group.id === id)?.sessions ?? [];
245
+ const selectedSession = sessions.find((session) => session.name === sessionName && isDefaultSession(id, session));
246
+ if (sessions.length && !selectedSession) {
247
+ options.setNote("Open this agent through its server session first.");
248
+ return;
249
+ }
171
250
  const row = agentGroups()
172
251
  .find((group) => group.machineId === id)
173
252
  ?.agents.find((agent) => agent.sessionName === sessionName && agent.paneId === paneId);
@@ -176,12 +255,18 @@ export function createApplicationMachineNavigation(options) {
176
255
  return;
177
256
  }
178
257
  options.cancelOpen();
179
- if (!select(id))
258
+ if (!select(id, selectedSession?.liveSessionId, selectedSession?.server))
180
259
  return;
260
+ if (selectedSession)
261
+ setActiveSessionKey(selectedSession.id);
181
262
  navigation++;
182
263
  setFocused(false);
183
264
  void options.openAgent?.(row, source);
184
265
  },
266
+ onSelectServer: (machineId, server) => {
267
+ showSwitcher(false);
268
+ setServerFocus({ machineId, server });
269
+ },
185
270
  onSelectMachine: (id) => {
186
271
  tabs.suspend();
187
272
  navigation++;
@@ -217,6 +302,28 @@ export function createApplicationMachineNavigation(options) {
217
302
  catalog,
218
303
  agents,
219
304
  selectedMachineId: () => manager.snapshot().selectedMachineId,
305
+ openHomeAgent: (row, source) => {
306
+ if (!agents.isCurrentTarget(row.machineId, row) || row.disabled)
307
+ return;
308
+ const session = snapshot()
309
+ .groups.find((group) => group.id === row.machineId)
310
+ ?.sessions.find((session) => session.liveSessionId === row.liveSessionId &&
311
+ !session.disabled &&
312
+ session.server?.serverId === row.server?.serverId &&
313
+ session.server?.generation === row.server?.generation);
314
+ if (!session) {
315
+ options.setNote("That agent session has changed. Select it again.");
316
+ return;
317
+ }
318
+ options.cancelOpen();
319
+ if (!select(row.machineId, row.liveSessionId, row.server))
320
+ return;
321
+ setActiveSessionKey(session.id);
322
+ navigation++;
323
+ setFocused(false);
324
+ return options.openAgent?.(row, source);
325
+ },
326
+ openSelectedSession: (name, source, key) => open(manager.snapshot().selectedMachineId, name, source, true, undefined, undefined, key),
220
327
  automaticOpen: manager.snapshot().machines.length === 1,
221
328
  automaticOpenAllowed: () => navigation === 0 && manager.snapshot().selectedMachineId === "local",
222
329
  sidebar,
@@ -235,7 +342,7 @@ export function createApplicationMachineNavigation(options) {
235
342
  options.setNote("That session is unavailable or has been replaced.");
236
343
  return;
237
344
  }
238
- if (await open(group.id, session.name, "keyboard", false))
345
+ if (await open(group.id, session.name, "keyboard", false, session.liveSessionId, session.server, session.id))
239
346
  historyIndex = next;
240
347
  },
241
348
  cycleTab(direction, close = false) {
@@ -253,10 +360,20 @@ export function createApplicationMachineNavigation(options) {
253
360
  void tabs.activate(target.key);
254
361
  },
255
362
  closeSwitcher: () => setSwitching(false),
363
+ switcherFilter: (command) => {
364
+ const focus = serverFocus();
365
+ return (!focus ||
366
+ (typeof command === "object" &&
367
+ command.fleet?.machineId === focus.machineId &&
368
+ command.fleet?.server?.serverId === focus.server.serverId &&
369
+ command.fleet?.server?.generation === focus.server.generation));
370
+ },
256
371
  paletteCommands() {
257
372
  const groupsById = new Map(agentGroups().map((group) => [group.machineId, group]));
258
373
  return snapshot().groups.flatMap((group) => {
259
- const agentGroup = groupsById.get(group.id);
374
+ const agentGroup = !group.sessions.some((session) => isDefaultSession(group.id, session))
375
+ ? undefined
376
+ : groupsById.get(group.id);
260
377
  const agentsBySession = new Map();
261
378
  for (const agent of agentGroup?.agents ?? []) {
262
379
  if (!agent.liveSessionId || !agent.paneId)
@@ -276,8 +393,9 @@ export function createApplicationMachineNavigation(options) {
276
393
  ? saved().recent.indexOf(session.id)
277
394
  : 1000,
278
395
  liveSessionId: session.liveSessionId,
279
- hostLabel: group.label,
280
- agentActivities: agentGroup?.available
396
+ server: session.server,
397
+ hostLabel: `${group.label}${session.serverLabel ? ` / ${session.serverLabel}` : ""}`,
398
+ agentActivities: isDefaultSession(group.id, session) && agentGroup?.available
281
399
  ? (agentsBySession.get(session.liveSessionId) ?? []).map((a) => ({
282
400
  paneId: a.paneId,
283
401
  attention: a.attention,
@@ -289,7 +407,9 @@ export function createApplicationMachineNavigation(options) {
289
407
  };
290
408
  return [
291
409
  { kind: "open-session", sessionName: session.name, label: session.name, fleet },
292
- ...(agentsBySession.get(session.liveSessionId) ?? []).map((a) => ({
410
+ ...(isDefaultSession(group.id, session)
411
+ ? (agentsBySession.get(session.liveSessionId) ?? [])
412
+ : []).map((a) => ({
293
413
  kind: "jump-agent",
294
414
  sessionName: session.name,
295
415
  paneId: a.paneId,
@@ -311,6 +431,21 @@ export function createApplicationMachineNavigation(options) {
311
431
  disabled: group.state !== "ready",
312
432
  },
313
433
  },
434
+ ...(group.servers ?? []).map((server) => ({
435
+ kind: "open-machine",
436
+ sessionName: "",
437
+ label: `${server.label} · ${server.serverId.slice(-6)} · New session ^N`,
438
+ fleet: {
439
+ machineId: group.id,
440
+ hostLabel: `${group.label} / ${server.label}`,
441
+ liveSessionId: "",
442
+ daemonInstanceId: daemon?.instanceId ?? "",
443
+ disabled: group.state !== "ready" || server.state !== "online",
444
+ ...(server.state === "online"
445
+ ? { server: { serverId: server.serverId, generation: server.generation } }
446
+ : {}),
447
+ },
448
+ })),
314
449
  ...sessions,
315
450
  ];
316
451
  });
@@ -321,7 +456,9 @@ export function createApplicationMachineNavigation(options) {
321
456
  const target = command.fleet;
322
457
  const session = snapshot()
323
458
  .groups.find((g) => g.id === target.machineId)
324
- ?.sessions.find((s) => s.liveSessionId === target.liveSessionId);
459
+ ?.sessions.find((s) => s.liveSessionId === target.liveSessionId &&
460
+ s.server?.serverId === target.server?.serverId &&
461
+ s.server?.generation === target.server?.generation);
325
462
  if (session)
326
463
  preferences.change({
327
464
  type: "favorite",
@@ -335,6 +472,19 @@ export function createApplicationMachineNavigation(options) {
335
472
  return;
336
473
  const handle = manager.getMachine(target.machineId);
337
474
  if (command.kind === "open-machine") {
475
+ if (target.server) {
476
+ const descriptor = snapshot()
477
+ .groups.find((group) => group.id === target.machineId)
478
+ ?.servers?.find((server) => server.serverId === target.server?.serverId &&
479
+ server.generation === target.server.generation &&
480
+ server.state === "online");
481
+ if (!descriptor || handle?.read()?.instanceId !== target.daemonInstanceId) {
482
+ options.setNote("That server has changed. Select it again.");
483
+ return;
484
+ }
485
+ sidebar.onSelectServer?.(target.machineId, target.server);
486
+ return;
487
+ }
338
488
  if (handle?.endpoint().state === "ready" &&
339
489
  handle.read()?.instanceId === target.daemonInstanceId)
340
490
  sidebar.onSelectMachine(target.machineId, source);
@@ -343,7 +493,10 @@ export function createApplicationMachineNavigation(options) {
343
493
  return;
344
494
  }
345
495
  const group = snapshot().groups.find((g) => g.id === target.machineId || g.routeIds?.includes(target.machineId));
346
- const session = group?.sessions.find((s) => s.liveSessionId === target.liveSessionId && !s.disabled);
496
+ const session = group?.sessions.find((s) => s.liveSessionId === target.liveSessionId &&
497
+ !s.disabled &&
498
+ s.server?.serverId === target.server?.serverId &&
499
+ s.server?.generation === target.server?.generation);
347
500
  if (!session ||
348
501
  handle?.endpoint().state !== "ready" ||
349
502
  handle.read()?.instanceId !== target.daemonInstanceId) {
@@ -353,7 +506,7 @@ export function createApplicationMachineNavigation(options) {
353
506
  if (command.kind === "jump-agent")
354
507
  sidebar.onOpenAgent?.(target.machineId, session.name, command.paneId, source);
355
508
  else
356
- await open(target.machineId, session.name, source, true, target.liveSessionId);
509
+ await open(target.machineId, session.name, source, true, target.liveSessionId, target.server);
357
510
  },
358
511
  adding,
359
512
  alias,
@@ -402,17 +555,44 @@ export function createApplicationMachineNavigation(options) {
402
555
  setError(cause instanceof Error ? cause.message : "Could not add this machine.");
403
556
  }
404
557
  },
405
- start(target) {
558
+ start(target, serverId) {
406
559
  agents.start();
407
560
  catalog.start();
408
- if (!target)
561
+ if (!target && !serverId)
409
562
  return;
410
563
  const id = manager.snapshot().selectedMachineId;
411
564
  const token = navigation;
565
+ let machineReady = false;
566
+ const attempt = () => {
567
+ if (!machineReady)
568
+ return;
569
+ if (token !== navigation) {
570
+ attemptStartup = null;
571
+ return;
572
+ }
573
+ const group = snapshot().groups.find((group) => group.id === id);
574
+ if (group?.state !== "ready")
575
+ return;
576
+ const matches = group.sessions.filter((row) => !row.disabled &&
577
+ (!serverId || row.server?.serverId === serverId) &&
578
+ (!target || row.name === target));
579
+ if (matches.length !== 1) {
580
+ options.setNote(matches.length
581
+ ? "Select a session under the requested tmux server."
582
+ : "Requested server or session is unavailable. Select it again.");
583
+ attemptStartup = null;
584
+ return;
585
+ }
586
+ const row = matches[0];
587
+ attemptStartup = null;
588
+ void open(id, row.name, "keyboard", true, row.liveSessionId, row.server, row.id);
589
+ };
590
+ attemptStartup = attempt;
412
591
  void manager.getMachine(id)?.ready.then((ready) => {
413
- if (ready && token === navigation && manager.snapshot().selectedMachineId === id)
414
- void open(id, target, "keyboard");
592
+ machineReady = ready;
593
+ attemptStartup?.();
415
594
  });
595
+ attempt();
416
596
  },
417
597
  };
418
598
  }
@@ -43,9 +43,15 @@ export function ApplicationMachineSidebar(props) {
43
43
  ? row.group.state === "ready" &&
44
44
  !row.agent.disabled &&
45
45
  row.agent.sessionName === props.model.activeSessionName() &&
46
+ (!props.model.activeSessionKey ||
47
+ row.group.sessions.some((session) => session.id === props.model.activeSessionKey?.() &&
48
+ session.server?.serverId === row.agent?.server?.serverId &&
49
+ session.server?.generation === row.agent?.server?.generation)) &&
46
50
  row.agent.paneId !== null &&
47
51
  row.agent.paneId === props.model.activePaneId?.()
48
- : row.session?.name === props.model.activeSessionName());
52
+ : props.model.activeSessionKey
53
+ ? row.session?.id === props.model.activeSessionKey()
54
+ : row.session?.name === props.model.activeSessionName());
49
55
  const preferenceKey = (group) => group.environmentId ?? group.id;
50
56
  const rows = createMemo(() => props.model.groups().flatMap((group) => [
51
57
  { key: JSON.stringify([group.id]), group },
@@ -53,12 +59,29 @@ export function ApplicationMachineSidebar(props) {
53
59
  .filter((session) => !collapsed().has(preferenceKey(group)) ||
54
60
  props.model.favorites?.().includes(session.id) ||
55
61
  (group.id === props.model.activeMachineId() &&
56
- session.name === props.model.activeSessionName()))
57
- .map((session) => ({
62
+ (props.model.activeSessionKey
63
+ ? session.id === props.model.activeSessionKey()
64
+ : session.name === props.model.activeSessionName())))
65
+ .map((session, index, sessions) => ({
66
+ serverHeading: (group.servers?.length ??
67
+ new Set(group.sessions.map((row) => row.server?.serverId)).size) > 1 &&
68
+ session.server &&
69
+ (index === 0 || sessions[index - 1]?.server?.serverId !== session.server.serverId)
70
+ ? `${session.serverLabel ?? "Server"} · ${session.server.serverId.slice(-6)}`
71
+ : undefined,
58
72
  key: JSON.stringify([group.id, "session", session.id]),
59
73
  group,
60
74
  session,
61
75
  })),
76
+ ...(collapsed().has(preferenceKey(group))
77
+ ? []
78
+ : (group.servers ?? [])
79
+ .filter((server) => !group.sessions.some((session) => session.server?.serverId === server.serverId))
80
+ .map((server) => ({
81
+ key: JSON.stringify([group.id, "server", server.serverId]),
82
+ group,
83
+ server,
84
+ }))),
62
85
  ...(!collapsed().has(preferenceKey(group))
63
86
  ? (group.agents ?? []).map((agent, index) => ({
64
87
  key: JSON.stringify([group.id, "agent", agent.id]),
@@ -79,7 +102,10 @@ export function ApplicationMachineSidebar(props) {
79
102
  ? group
80
103
  : null;
81
104
  };
82
- const activity = (row) => summarizeFleetActivity((row.group.agents ?? []).filter((agent) => !row.session || agent.sessionName === row.session.name), row.group.state === "ready" && row.group.agentsAvailable !== false && !row.session?.disabled);
105
+ const activity = (row) => summarizeFleetActivity((row.group.agents ?? []).filter((agent) => !row.session ||
106
+ (agent.sessionName === row.session.name &&
107
+ agent.server?.serverId === row.session.server?.serverId &&
108
+ agent.server?.generation === row.session.server?.generation)), row.group.state === "ready" && row.group.agentsAvailable !== false && !row.session?.disabled);
83
109
  const connectionDetail = (group) => {
84
110
  const diagnostic = group.diagnostic;
85
111
  if (!diagnostic)
@@ -111,6 +137,14 @@ export function ApplicationMachineSidebar(props) {
111
137
  setSelectedKey(row.key);
112
138
  setLocalFocused(true);
113
139
  props.model.onFocus?.();
140
+ if (row.server) {
141
+ if (row.server.state === "online")
142
+ props.model.onSelectServer?.(row.group.id, {
143
+ serverId: row.server.serverId,
144
+ generation: row.server.generation,
145
+ });
146
+ return;
147
+ }
114
148
  if (row.agent) {
115
149
  if (row.group.state === "ready" &&
116
150
  !row.agent.disabled &&
@@ -123,7 +157,7 @@ export function ApplicationMachineSidebar(props) {
123
157
  }
124
158
  else if (row.session) {
125
159
  if (row.group.state === "ready" && !row.session.disabled)
126
- props.model.onOpen(row.group.id, row.session.name, source);
160
+ props.model.onOpen(row.group.id, row.session.name, source, row.session.id);
127
161
  }
128
162
  else if (row.group.state !== "ready" ||
129
163
  (row.group.sessions.length === 0 && !row.group.agents?.length))
@@ -134,7 +168,7 @@ export function ApplicationMachineSidebar(props) {
134
168
  const revealFocusedRow = () => {
135
169
  if (!focused() || !scroll)
136
170
  return;
137
- const rowHeight = (row) => (row.agent ? (row.agentHeading ? 3 : 2) : 1);
171
+ const rowHeight = (row) => row.agent ? (row.agentHeading ? 3 : 2) : row.serverHeading ? 2 : 1;
138
172
  const y = rows()
139
173
  .slice(0, index())
140
174
  .reduce((sum, row) => sum + rowHeight(row), 0);
@@ -298,48 +332,68 @@ export function ApplicationMachineSidebar(props) {
298
332
  get agent() {
299
333
  return current().agent;
300
334
  },
335
+ get server() {
336
+ return current().server;
337
+ },
338
+ get serverHeading() {
339
+ return current().serverHeading;
340
+ },
301
341
  get agentHeading() {
302
342
  return current().agentHeading;
303
343
  },
304
344
  };
305
- return (<box width={Math.max(1, props.width - 1)} height={row.agent ? (row.agentHeading ? 3 : 2) : 1} flexShrink={0} flexDirection="column">
345
+ return (<box width={Math.max(1, props.width - 1)} height={row.agent ? (row.agentHeading ? 3 : 2) : row.serverHeading ? 2 : 1} flexShrink={0} flexDirection="column">
346
+ <Show when={row.serverHeading}>
347
+ <NavigationRow theme={props.theme} width={Math.max(1, props.width - 1)} id={`server:${row.group.id}:${row.session?.server?.serverId}`} label={row.serverHeading ?? "Server"} marker=" ▾" onActivate={() => {
348
+ if (row.session?.server)
349
+ props.model.onSelectServer?.(row.group.id, row.session.server);
350
+ }}/>
351
+ </Show>
306
352
  <Show when={row.agentHeading}>
307
353
  <text height={1} fg={props.theme.roles.text.secondary}>
308
354
  {" "}
309
355
  Agents
310
356
  </text>
311
357
  </Show>
312
- <NavigationRow theme={props.theme} id={`machine:${row.key}`} width={Math.max(1, props.width - 1)} labelColor={!row.agent && !row.session ? fleetHostColor(row.group) : undefined} label={row.agent
313
- ? row.agent.name
314
- : row.session
315
- ? friendlySessionLabel(row.session.name)
316
- : row.group.label} marker={row.agent
317
- ? active(row)
318
- ? row.agent.attention
319
- ? "›!"
320
- : "›"
321
- : row.agent.attention
322
- ? "!"
323
- : "•"
324
- : row.session
325
- ? props.model.favorites?.().includes(row.session.id)
326
- ? " ★"
327
- : active(row)
328
- ? " ›"
329
- : " "
330
- : collapsed().has(preferenceKey(row.group))
331
- ? "▸"
332
- : "▾"} detail={row.agent
333
- ? row.group.state !== "ready" || row.agent.disabled
334
- ? "unavailable"
335
- : `[${terminalAgentStatusLabel(row.agent.activity)}]`
336
- : row.session
337
- ? row.group.state !== "ready" || row.session.disabled
358
+ <NavigationRow theme={props.theme} id={`machine:${row.key}`} width={Math.max(1, props.width - 1)} labelColor={!row.agent && !row.session ? fleetHostColor(row.group) : undefined} label={row.server
359
+ ? ` ${row.server.label} · ${row.server.serverId.slice(-6)}`
360
+ : row.agent
361
+ ? row.agent.name
362
+ : row.session
363
+ ? friendlySessionLabel(row.session.name)
364
+ : row.group.label} marker={row.server
365
+ ? " "
366
+ : row.agent
367
+ ? active(row)
368
+ ? row.agent.attention
369
+ ? "›!"
370
+ : "›"
371
+ : row.agent.attention
372
+ ? "!"
373
+ : "•"
374
+ : row.session
375
+ ? props.model.favorites?.().includes(row.session.id)
376
+ ? " ★"
377
+ : active(row)
378
+ ? " ›"
379
+ : " "
380
+ : collapsed().has(preferenceKey(row.group))
381
+ ? "▸"
382
+ : "▾"} detail={row.server
383
+ ? row.server.state === "offline"
384
+ ? "offline"
385
+ : "empty"
386
+ : row.agent
387
+ ? row.group.state !== "ready" || row.agent.disabled
338
388
  ? "unavailable"
339
- : `${row.session.paneCount}p${row.group.agents?.length ? ` ${activity(row).label}` : ""}`
340
- : row.group.agents?.length && row.group.state === "ready"
341
- ? activity(row).label
342
- : `${connectionDetail(row.group)}${row.group.agents?.length ? " ?" : ""}`} selected={Boolean((row.session || row.agent) && active(row))} focused={Boolean(focused() && row.key === selectedKey())} attention={row.agent?.attention ?? activity(row).kind === "attention"} onActivate={(source) => activate(row, source)}/>
389
+ : `[${terminalAgentStatusLabel(row.agent.activity)}]`
390
+ : row.session
391
+ ? row.group.state !== "ready" || row.session.disabled
392
+ ? "unavailable"
393
+ : `${row.session.paneCount}p${row.group.agents?.length ? ` ${activity(row).label}` : ""}`
394
+ : row.group.agents?.length && row.group.state === "ready"
395
+ ? activity(row).label
396
+ : `${connectionDetail(row.group)}${row.group.agents?.length ? " ?" : ""}`} selected={Boolean((row.session || row.agent) && active(row))} focused={Boolean(focused() && row.key === selectedKey())} attention={row.agent?.attention ?? activity(row).kind === "attention"} onActivate={(source) => activate(row, source)}/>
343
397
  <Show when={row.agent}>
344
398
  {(agent) => (<text height={1} fg={props.theme.roles.text.muted} content={clipTerminal(` ${friendlySessionLabel(agent().sessionName)}`, Math.max(1, props.width - 1))}/>)}
345
399
  </Show>