tmux-ide 2.9.0-beta.21 → 2.9.0-beta.22
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.
- package/bin/cli.js +52253 -49070
- package/package.json +1 -1
- package/packages/contracts/src/__tests__/daemon-wire.test.ts +2 -2
- package/packages/contracts/src/__tests__/fixtures/daemon-wire-v3.json +35 -0
- package/packages/contracts/src/__tests__/issue-error.test.ts +1 -1
- package/packages/contracts/src/__tests__/multiplexer-verbs.test.ts +3 -1
- package/packages/contracts/src/__tests__/pane-stream.test.ts +17 -5
- package/packages/contracts/src/__tests__/tmux-server-scope.test.ts +166 -0
- package/packages/contracts/src/__tests__/window-links.test.ts +243 -0
- package/packages/contracts/src/actions-contract.ts +12 -0
- package/packages/contracts/src/daemon-resource-request.test.ts +1 -1
- package/packages/contracts/src/daemon-wire.ts +10 -5
- package/packages/contracts/src/fleet-client-state.ts +3 -0
- package/packages/contracts/src/index.ts +2 -0
- package/packages/contracts/src/interaction-receipts.ts +39 -0
- package/packages/contracts/src/issue-error.ts +1 -1
- package/packages/contracts/src/multiplexer-verbs.ts +25 -1
- package/packages/contracts/src/pane-stream.ts +51 -8
- package/packages/contracts/src/tmux-server-scope.ts +172 -0
- package/packages/contracts/src/window-links.ts +76 -0
- package/packages/contracts/src/workspace-multiplexer.ts +54 -0
- package/packages/core/src/interaction-receipts.ts +4 -0
- package/packages/daemon/dist/command-center/actions/command-definitions.js +6 -0
- package/packages/daemon/dist/command-center/actions/handlers/workspace-multiplexer.js +2 -0
- package/packages/daemon/dist/command-center/actions/registry.js +13 -1
- package/packages/daemon/dist/command-center/discovery.js +2 -9
- package/packages/daemon/dist/command-center/server.js +21 -1
- package/packages/daemon/dist/command-center/tmux-server-native-backing.js +37 -0
- package/packages/daemon/dist/command-center/tmux-servers.js +345 -0
- package/packages/daemon/dist/lib/canonical-daemon-bootstrap.js +64 -8
- package/packages/daemon/dist/lib/canonical-daemon.js +5 -2
- package/packages/daemon/dist/lib/daemon-embed.js +198 -30
- package/packages/daemon/dist/lib/embedded-tmux-server-owners.js +147 -0
- package/packages/daemon/dist/lib/fleet-lifecycle-authority.js +16 -10
- package/packages/daemon/dist/lib/headless-daemon.js +10 -1
- package/packages/daemon/dist/lib/runtime-namespace.js +17 -3
- package/packages/daemon/dist/lib/tmux-client-execution.js +59 -0
- package/packages/daemon/dist/lib/tmux-named-socket-fence.js +4 -0
- package/packages/daemon/dist/lib/tmux-server-generation-runner.js +62 -0
- package/packages/daemon/dist/lib/tmux-server-owner.js +285 -0
- package/packages/daemon/dist/lib/tmux-server-owners.js +192 -0
- package/packages/daemon/dist/lib/tmux-server-proof.js +82 -0
- package/packages/daemon/dist/lib/tmux-server-registration.js +85 -0
- package/packages/daemon/dist/lib/tmux-server-session-create.js +43 -0
- package/packages/daemon/dist/lib/tmux-server-session-open.js +101 -0
- package/packages/daemon/dist/lib/tmux-servers-cli.js +134 -0
- package/packages/daemon/dist/lib/tmux-session-mutation-fence.js +55 -0
- package/packages/daemon/dist/lib/tmux-window-link-guard.js +88 -0
- package/packages/daemon/dist/lib/workspace-config-loader.js +3 -0
- package/packages/daemon/dist/lib/workspace-multiplexer-verbs.js +82 -1
- package/packages/daemon/dist/lib/workspace-pane-creation.js +13 -57
- package/packages/daemon/dist/lib/workspace-promotion.js +91 -12
- package/packages/daemon/dist/lib/workspace-registry.js +4 -0
- package/packages/daemon/dist/server/pane-stream-upgrade.js +20 -6
- package/packages/daemon/dist/terminal/attachments/native-runtime.js +61 -17
- package/packages/daemon/dist/terminal/mirror/__tests__/scripted-channel.js +9 -1
- package/packages/daemon/dist/terminal/mirror/__tests__/simulated-channel.js +11 -2
- package/packages/daemon/dist/terminal/mirror/control-channel.js +65 -6
- package/packages/daemon/dist/terminal/mirror/mirror-service.js +44 -0
- package/packages/daemon/dist/terminal/mirror/session-channel.js +210 -50
- package/packages/daemon/dist/terminal/mirror/window-link-authority.js +160 -0
- package/packages/daemon/dist/terminal/pane-stream/lease-manager.js +3 -3
- package/packages/daemon/dist/terminal/pane-stream/pane-stream-websocket.js +40 -6
- package/packages/daemon/dist/terminal/protocol/live-session-identity.js +9 -0
- package/packages/daemon/dist/terminal/protocol/native-backing-client.js +2 -1
- package/packages/daemon/dist/terminal/session-runtime/interaction-receipt-facts.js +9 -0
- package/packages/daemon/dist/terminal/session-runtime/registry.js +6 -0
- package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-executor.js +1 -1
- package/packages/daemon/dist/terminal/session-runtime/semantic-mutation-resource-changes.js +1 -0
- package/packages/daemon/dist/tui/mirror/application-shell-daemon-connection.js +7 -2
- package/packages/daemon/dist/tui/mirror/application-shell-server-connection.js +114 -0
- package/packages/daemon/dist/tui/mirror/menu-model.js +1 -1
- package/packages/daemon/dist/tui/mirror/open-tui-verified-routing.js +34 -4
- package/packages/daemon/dist/tui/mirror/open-tui-workspace-runtime-port.js +29 -8
- package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog-owner.js +11 -5
- package/packages/daemon/dist/tui/mirror/runtime/application-home-catalog.js +140 -75
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-catalog.js +12 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +190 -31
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +91 -37
- package/packages/daemon/dist/tui/mirror/runtime/application-root-configuration.js +9 -3
- package/packages/daemon/dist/tui/mirror/runtime/application-route-connection.js +95 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-interaction-controller.js +158 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace-policy.js +43 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-terminal-workspace.jsx +78 -25
- package/packages/daemon/dist/tui/mirror/runtime/fleet-lifecycle-client.js +42 -1
- package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +2 -1
- package/packages/daemon/dist/tui/mirror/runtime/terminal-pane-input-router.js +6 -3
- package/packages/daemon/dist/tui/mirror/workspace/application-command-description.js +11 -2
- package/packages/daemon/dist/tui/mirror/workspace/terminal-window-strip.jsx +2 -1
- package/packages/daemon/src/command-center/actions/command-definitions.ts +6 -0
- package/packages/daemon/src/command-center/actions/errors.ts +4 -0
- package/packages/daemon/src/command-center/actions/handlers/workspace-multiplexer.ts +2 -0
- package/packages/daemon/src/command-center/actions/registry.ts +14 -0
- package/packages/daemon/src/command-center/discovery.ts +2 -10
- package/packages/daemon/src/command-center/server.ts +29 -1
- package/packages/daemon/src/command-center/tmux-server-native-backing.ts +49 -0
- package/packages/daemon/src/command-center/tmux-servers.ts +468 -0
- package/packages/daemon/src/lib/canonical-daemon-bootstrap.ts +111 -4
- package/packages/daemon/src/lib/canonical-daemon.ts +15 -3
- package/packages/daemon/src/lib/daemon-embed.ts +241 -28
- package/packages/daemon/src/lib/embedded-tmux-server-owners.ts +190 -0
- package/packages/daemon/src/lib/fleet-lifecycle-authority.ts +16 -8
- package/packages/daemon/src/lib/headless-daemon.ts +18 -1
- package/packages/daemon/src/lib/runtime-namespace.ts +24 -3
- package/packages/daemon/src/lib/tmux-client-execution.ts +56 -0
- package/packages/daemon/src/lib/tmux-named-socket-fence.ts +5 -0
- package/packages/daemon/src/lib/tmux-server-generation-runner.ts +81 -0
- package/packages/daemon/src/lib/tmux-server-owner.ts +336 -0
- package/packages/daemon/src/lib/tmux-server-owners.ts +228 -0
- package/packages/daemon/src/lib/tmux-server-proof.ts +95 -0
- package/packages/daemon/src/lib/tmux-server-registration.ts +105 -0
- package/packages/daemon/src/lib/tmux-server-session-create.ts +52 -0
- package/packages/daemon/src/lib/tmux-server-session-open.ts +106 -0
- package/packages/daemon/src/lib/tmux-servers-cli.ts +186 -0
- package/packages/daemon/src/lib/tmux-session-mutation-fence.ts +62 -0
- package/packages/daemon/src/lib/tmux-window-link-guard.ts +112 -0
- package/packages/daemon/src/lib/workspace-config-loader.ts +3 -0
- package/packages/daemon/src/lib/workspace-multiplexer-verbs.ts +121 -1
- package/packages/daemon/src/lib/workspace-pane-creation.ts +16 -55
- package/packages/daemon/src/lib/workspace-promotion.ts +98 -12
- package/packages/daemon/src/lib/workspace-registry.ts +5 -0
- package/packages/daemon/src/server/pane-stream-upgrade.ts +25 -5
- package/packages/daemon/src/terminal/attachments/native-runtime.ts +70 -16
- package/packages/daemon/src/terminal/mirror/control-channel.ts +64 -7
- package/packages/daemon/src/terminal/mirror/events.ts +2 -0
- package/packages/daemon/src/terminal/mirror/mirror-service.ts +56 -0
- package/packages/daemon/src/terminal/mirror/session-channel.ts +245 -50
- package/packages/daemon/src/terminal/mirror/window-link-authority.ts +203 -0
- package/packages/daemon/src/terminal/pane-stream/lease-manager.ts +3 -3
- package/packages/daemon/src/terminal/pane-stream/pane-stream-websocket.ts +50 -6
- package/packages/daemon/src/terminal/protocol/live-session-identity.ts +14 -0
- package/packages/daemon/src/terminal/protocol/native-backing-client.ts +4 -1
- package/packages/daemon/src/terminal/session-runtime/interaction-receipt-facts.ts +9 -0
- package/packages/daemon/src/terminal/session-runtime/registry.ts +9 -0
- package/packages/daemon/src/terminal/session-runtime/semantic-mutation-executor.ts +2 -2
- package/packages/daemon/src/terminal/session-runtime/semantic-mutation-resource-changes.ts +1 -0
- package/packages/daemon/src/tui/mirror/application-shell-daemon-connection.ts +28 -2
- package/packages/daemon/src/tui/mirror/application-shell-server-connection.ts +154 -0
- package/packages/daemon/src/tui/mirror/menu-model.ts +1 -1
- package/packages/daemon/src/tui/mirror/open-tui-verified-routing.ts +41 -3
- package/packages/daemon/src/tui/mirror/open-tui-workspace-runtime-port.ts +47 -6
- package/packages/daemon/src/tui/mirror/runtime/application-entry.ts +2 -1
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-session-actions.tsx +10 -4
- package/packages/daemon/src/tui/mirror/runtime/application-fleet-tabs.ts +2 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-catalog-owner.ts +13 -3
- package/packages/daemon/src/tui/mirror/runtime/application-home-catalog.ts +172 -100
- package/packages/daemon/src/tui/mirror/runtime/application-machine-catalog.ts +19 -7
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +263 -36
- package/packages/daemon/src/tui/mirror/runtime/application-machine-overlays.tsx +1 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +124 -40
- package/packages/daemon/src/tui/mirror/runtime/application-palette-preview.tsx +3 -0
- package/packages/daemon/src/tui/mirror/runtime/application-root-configuration.ts +12 -3
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +24 -24
- package/packages/daemon/src/tui/mirror/runtime/application-route-connection.ts +93 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts +177 -2
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace-policy.ts +62 -0
- package/packages/daemon/src/tui/mirror/runtime/application-terminal-workspace.tsx +87 -30
- package/packages/daemon/src/tui/mirror/runtime/fleet-lifecycle-client.ts +52 -0
- package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +1 -1
- package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/terminal-pane-input-router.ts +15 -4
- package/packages/daemon/src/tui/mirror/workspace/application-command-description.ts +13 -2
- package/packages/daemon/src/tui/mirror/workspace/terminal-window-strip.tsx +5 -1
- package/packages/daemon-client/package.json +3 -1
- package/packages/daemon-client/src/index.ts +2 -0
- package/packages/daemon-client/src/pane-stream-client.test.ts +108 -38
- package/packages/daemon-client/src/pane-stream-client.ts +26 -0
- package/packages/daemon-client/src/scoped-tmux-server-transport.test.ts +114 -0
- package/packages/daemon-client/src/scoped-tmux-server-transport.ts +385 -0
- package/packages/daemon-client/src/tmux-server-client.test.ts +279 -0
- package/packages/daemon-client/src/tmux-server-client.ts +287 -0
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { OpenTuiGenerationHostSnapshot } from "./open-tui-generation-host.ts";
|
|
2
|
+
import type { TmuxServerScope } from "@tmux-ide/contracts";
|
|
1
3
|
import { fleetHostColor } from "./fleet-presentation.ts";
|
|
2
4
|
import type { ApplicationPaletteCommand } from "./application-palette-input.ts";
|
|
3
5
|
import { createFleetTabs, type FleetTabTarget } from "./application-fleet-tabs.ts";
|
|
@@ -13,13 +15,30 @@ import { createApplicationMachineCatalog } from "./application-machine-catalog.t
|
|
|
13
15
|
import { ephemeralMachineProfile } from "./application-machine-startup.ts";
|
|
14
16
|
import type { ApplicationMachineSidebarModel } from "./application-machine-sidebar.tsx";
|
|
15
17
|
|
|
18
|
+
/** Exact attached authority; an unscoped default connection still proves its incarnation. */
|
|
19
|
+
function attachedMachineNavigationTarget(active: OpenTuiGenerationHostSnapshot | null) {
|
|
20
|
+
if (active?.status !== "live" || !active.connection?.liveSessionId || !active.daemonGeneration)
|
|
21
|
+
return null;
|
|
22
|
+
return {
|
|
23
|
+
liveSessionId: active.connection.liveSessionId,
|
|
24
|
+
daemonGeneration: active.daemonGeneration,
|
|
25
|
+
...(active.connection.server ? { server: active.connection.server } : {}),
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
16
29
|
export function createApplicationMachineNavigation(options: {
|
|
17
|
-
resetWorkspace(machineId: string, expectedLiveSessionId?: string): void;
|
|
30
|
+
resetWorkspace(machineId: string, expectedLiveSessionId?: string, server?: TmuxServerScope): void;
|
|
18
31
|
openAgent?(agent: ApplicationMachineAgent, source: "keyboard" | "mouse"): Promise<unknown> | void;
|
|
19
32
|
cancelOpen(): void;
|
|
20
33
|
openSession(name: string, source: "keyboard" | "mouse"): Promise<unknown>;
|
|
21
34
|
sessionName(): string | null;
|
|
22
35
|
activePaneId?(): string | null;
|
|
36
|
+
attachedGeneration?(): OpenTuiGenerationHostSnapshot | null;
|
|
37
|
+
attachedTarget?(): {
|
|
38
|
+
liveSessionId: string;
|
|
39
|
+
daemonGeneration: string;
|
|
40
|
+
server?: TmuxServerScope;
|
|
41
|
+
} | null;
|
|
23
42
|
setSurface(value: "home" | "terminals"): void;
|
|
24
43
|
setNote(value: string | null | ((current: string | null) => string | null)): void;
|
|
25
44
|
}) {
|
|
@@ -45,10 +64,15 @@ export function createApplicationMachineNavigation(options: {
|
|
|
45
64
|
const [snapshot, setSnapshot] = createSignal(catalog.getSnapshot());
|
|
46
65
|
const [focused, setFocused] = createSignal(false);
|
|
47
66
|
const [switching, setSwitching] = createSignal(false);
|
|
67
|
+
const [serverFocus, setServerFocus] = createSignal<{
|
|
68
|
+
machineId: string;
|
|
69
|
+
server: TmuxServerScope;
|
|
70
|
+
} | null>(null);
|
|
48
71
|
const [attentionOnly, setAttentionOnly] = createSignal(false);
|
|
49
72
|
const showSwitcher = (attention: boolean) => {
|
|
50
73
|
navigation++;
|
|
51
74
|
options.cancelOpen();
|
|
75
|
+
setServerFocus(null);
|
|
52
76
|
setAttentionOnly(attention);
|
|
53
77
|
setFocused(false);
|
|
54
78
|
setSwitching(true);
|
|
@@ -56,21 +80,41 @@ export function createApplicationMachineNavigation(options: {
|
|
|
56
80
|
const [adding, setAdding] = createSignal(false);
|
|
57
81
|
const [alias, setAlias] = createSignal("");
|
|
58
82
|
const [error, setError] = createSignal<string | null>(null);
|
|
59
|
-
|
|
83
|
+
let attemptStartup: (() => void) | null = null;
|
|
84
|
+
const stop = catalog.subscribe((value) => {
|
|
85
|
+
setSnapshot(value);
|
|
86
|
+
attemptStartup?.();
|
|
87
|
+
});
|
|
60
88
|
let navigation = 0;
|
|
89
|
+
const [activeSessionKey, setActiveSessionKey] = createSignal<string | null>(null);
|
|
61
90
|
const [tabRevision, setTabRevision] = createSignal(0);
|
|
62
91
|
const history: string[] = [...saved().recent].reverse();
|
|
63
92
|
let historyIndex = history.length - 1;
|
|
64
|
-
const select = (id: string, expectedLiveSessionId?: string) => {
|
|
93
|
+
const select = (id: string, expectedLiveSessionId?: string, server?: TmuxServerScope) => {
|
|
65
94
|
if (!manager.getMachine(id)) return false;
|
|
66
95
|
if (manager.snapshot().selectedMachineId !== id) {
|
|
67
96
|
navigation++;
|
|
68
97
|
options.cancelOpen();
|
|
69
98
|
// Synchronously remove the old input/shell owner before changing the route.
|
|
70
|
-
options.resetWorkspace(id, expectedLiveSessionId);
|
|
99
|
+
if (server) options.resetWorkspace(id, expectedLiveSessionId, server);
|
|
100
|
+
else options.resetWorkspace(id, expectedLiveSessionId);
|
|
71
101
|
manager.select(id);
|
|
72
102
|
} else if (expectedLiveSessionId) {
|
|
73
|
-
|
|
103
|
+
const attached =
|
|
104
|
+
options.attachedTarget?.() ??
|
|
105
|
+
attachedMachineNavigationTarget(options.attachedGeneration?.() ?? null);
|
|
106
|
+
const sameAuthority =
|
|
107
|
+
attached?.liveSessionId === expectedLiveSessionId &&
|
|
108
|
+
(server
|
|
109
|
+
? attached.server
|
|
110
|
+
? attached.server.serverId === server.serverId &&
|
|
111
|
+
attached.server.generation === server.generation
|
|
112
|
+
: attached.daemonGeneration === server.generation &&
|
|
113
|
+
manager.getMachine(id)?.read()?.instanceId === server.generation
|
|
114
|
+
: !attached.server);
|
|
115
|
+
if (sameAuthority) return true;
|
|
116
|
+
if (server) options.resetWorkspace(id, expectedLiveSessionId, server);
|
|
117
|
+
else options.resetWorkspace(id, expectedLiveSessionId);
|
|
74
118
|
}
|
|
75
119
|
return true;
|
|
76
120
|
};
|
|
@@ -80,18 +124,44 @@ export function createApplicationMachineNavigation(options: {
|
|
|
80
124
|
source: "keyboard" | "mouse",
|
|
81
125
|
remember = true,
|
|
82
126
|
expectedLiveSessionId?: string,
|
|
127
|
+
server?: TmuxServerScope,
|
|
128
|
+
sessionKey?: string,
|
|
83
129
|
) => {
|
|
84
130
|
// A same-machine session click supersedes any pending exact-agent focus too.
|
|
85
131
|
if (manager.snapshot().selectedMachineId === id) options.cancelOpen();
|
|
86
132
|
// A sidebar selection can leave a tab whose route is pinned to a different
|
|
87
133
|
// live incarnation on this same machine. Select a new exact owner before
|
|
88
134
|
// preparation; never apply that old tab's identity fence to the new name.
|
|
89
|
-
if (
|
|
90
|
-
|
|
135
|
+
if (manager.getMachine(id)?.endpoint().state !== "ready") {
|
|
136
|
+
select(id);
|
|
137
|
+
options.setNote("This machine is disconnected. Select the session after reconnecting.");
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const candidates =
|
|
141
|
+
snapshot()
|
|
91
142
|
.groups.find((group) => group.id === id || group.routeIds?.includes(id))
|
|
92
|
-
?.sessions.
|
|
143
|
+
?.sessions.filter(
|
|
144
|
+
(row) =>
|
|
145
|
+
row.name === name &&
|
|
146
|
+
!row.disabled &&
|
|
147
|
+
(!sessionKey || row.id === sessionKey) &&
|
|
148
|
+
(!expectedLiveSessionId || row.liveSessionId === expectedLiveSessionId) &&
|
|
149
|
+
(!server ||
|
|
150
|
+
(row.server?.serverId === server.serverId &&
|
|
151
|
+
row.server.generation === server.generation)),
|
|
152
|
+
) ?? [];
|
|
153
|
+
if (candidates.length !== 1) {
|
|
154
|
+
options.setNote(
|
|
155
|
+
"Select the session under its tmux server; this target is ambiguous or has changed.",
|
|
156
|
+
);
|
|
157
|
+
return false;
|
|
93
158
|
}
|
|
94
|
-
|
|
159
|
+
const selected = candidates[0]!;
|
|
160
|
+
const sameSelection = activeSessionKey() === selected.id && options.sessionName() === name;
|
|
161
|
+
setActiveSessionKey(selected.id);
|
|
162
|
+
expectedLiveSessionId = selected.liveSessionId;
|
|
163
|
+
server = selected.server;
|
|
164
|
+
if (!select(id, sameSelection ? undefined : expectedLiveSessionId, server)) return;
|
|
95
165
|
const token = ++navigation;
|
|
96
166
|
setFocused(false);
|
|
97
167
|
options.setSurface("terminals");
|
|
@@ -111,15 +181,21 @@ export function createApplicationMachineNavigation(options: {
|
|
|
111
181
|
if (token === navigation) {
|
|
112
182
|
const session = snapshot()
|
|
113
183
|
.groups.find((group) => group.id === id || group.routeIds?.includes(id))
|
|
114
|
-
?.sessions.find((row) => row.
|
|
184
|
+
?.sessions.find((row) => row.id === selected.id && !row.disabled);
|
|
115
185
|
if (session) {
|
|
116
186
|
if (session.liveSessionId)
|
|
117
187
|
tabs.remember({
|
|
118
|
-
key: JSON.stringify([
|
|
188
|
+
key: JSON.stringify([
|
|
189
|
+
id,
|
|
190
|
+
session.server?.serverId,
|
|
191
|
+
session.server?.generation,
|
|
192
|
+
session.liveSessionId,
|
|
193
|
+
]),
|
|
194
|
+
server: session.server,
|
|
119
195
|
machineId: id,
|
|
120
196
|
liveSessionId: session.liveSessionId,
|
|
121
197
|
label: session.name,
|
|
122
|
-
hostLabel: manager.getMachine(id)?.label ?? id
|
|
198
|
+
hostLabel: `${manager.getMachine(id)?.label ?? id}${session.serverLabel ? ` / ${session.serverLabel}` : ""}`,
|
|
123
199
|
});
|
|
124
200
|
preferences.change({ type: "visit", key: session.id });
|
|
125
201
|
if (remember && history[historyIndex] !== session.id) {
|
|
@@ -138,30 +214,57 @@ export function createApplicationMachineNavigation(options: {
|
|
|
138
214
|
(g) => g.id === target.machineId || g.routeIds?.includes(target.machineId),
|
|
139
215
|
);
|
|
140
216
|
const row = group?.sessions.find(
|
|
141
|
-
(s) =>
|
|
217
|
+
(s) =>
|
|
218
|
+
s.liveSessionId === target.liveSessionId &&
|
|
219
|
+
!s.disabled &&
|
|
220
|
+
s.server?.serverId === target.server?.serverId &&
|
|
221
|
+
s.server?.generation === target.server?.generation,
|
|
142
222
|
);
|
|
143
223
|
return row ? { ...target, label: row.name } : null;
|
|
144
224
|
};
|
|
145
225
|
const tabs = createFleetTabs({
|
|
146
226
|
resolve: resolveTab,
|
|
147
227
|
retireActive: () => {
|
|
228
|
+
setActiveSessionKey(null);
|
|
148
229
|
navigation++;
|
|
149
230
|
options.cancelOpen();
|
|
150
231
|
options.resetWorkspace(manager.snapshot().selectedMachineId);
|
|
151
232
|
options.setSurface("home");
|
|
152
233
|
},
|
|
153
234
|
open: async (target) =>
|
|
154
|
-
!!(await open(
|
|
235
|
+
!!(await open(
|
|
236
|
+
target.machineId,
|
|
237
|
+
target.label,
|
|
238
|
+
"keyboard",
|
|
239
|
+
true,
|
|
240
|
+
target.liveSessionId,
|
|
241
|
+
target.server,
|
|
242
|
+
)),
|
|
155
243
|
publish: () => setTabRevision((value) => value + 1),
|
|
156
244
|
unavailable: () => options.setNote("That tab's session is unavailable or has been replaced."),
|
|
157
245
|
});
|
|
246
|
+
const isDefaultSession = (machineId: string, session: { server?: TmuxServerScope }) =>
|
|
247
|
+
!session.server ||
|
|
248
|
+
session.server.generation === manager.getMachine(machineId)?.read()?.instanceId;
|
|
158
249
|
const sidebar: ApplicationMachineSidebarModel = {
|
|
159
250
|
groups: () =>
|
|
160
251
|
snapshot().groups.map((group) => ({
|
|
161
252
|
...group,
|
|
162
253
|
agentsAvailable:
|
|
163
|
-
|
|
164
|
-
|
|
254
|
+
group.sessions.some((session) => isDefaultSession(group.id, session)) &&
|
|
255
|
+
(agentGroups().find((value) => value.machineId === group.id)?.available ?? false),
|
|
256
|
+
agents: !group.sessions.some((session) => isDefaultSession(group.id, session))
|
|
257
|
+
? []
|
|
258
|
+
: (agentGroups().find((value) => value.machineId === group.id)?.agents ?? []).map(
|
|
259
|
+
(agent) => ({
|
|
260
|
+
...agent,
|
|
261
|
+
server: group.sessions.find(
|
|
262
|
+
(session) =>
|
|
263
|
+
session.liveSessionId === agent.liveSessionId &&
|
|
264
|
+
isDefaultSession(group.id, session),
|
|
265
|
+
)?.server,
|
|
266
|
+
}),
|
|
267
|
+
),
|
|
165
268
|
})),
|
|
166
269
|
tabs: () => {
|
|
167
270
|
tabRevision();
|
|
@@ -182,6 +285,7 @@ export function createApplicationMachineNavigation(options: {
|
|
|
182
285
|
onCollapse: (key, enabled) => preferences.change({ type: "collapse", key, enabled }),
|
|
183
286
|
activeMachineId: () => snapshot().selectedMachineId,
|
|
184
287
|
activeSessionName: options.sessionName,
|
|
288
|
+
activeSessionKey,
|
|
185
289
|
activePaneId: options.activePaneId,
|
|
186
290
|
focused,
|
|
187
291
|
onFocus: () => {
|
|
@@ -190,8 +294,17 @@ export function createApplicationMachineNavigation(options: {
|
|
|
190
294
|
setFocused(true);
|
|
191
295
|
},
|
|
192
296
|
onBlur: () => setFocused(false),
|
|
193
|
-
onOpen: (id, name, source) =>
|
|
297
|
+
onOpen: (id, name, source, sessionKey) =>
|
|
298
|
+
void open(id, name, source, true, undefined, undefined, sessionKey),
|
|
194
299
|
onOpenAgent: (id, sessionName, paneId, source) => {
|
|
300
|
+
const sessions = snapshot().groups.find((group) => group.id === id)?.sessions ?? [];
|
|
301
|
+
const selectedSession = sessions.find(
|
|
302
|
+
(session) => session.name === sessionName && isDefaultSession(id, session),
|
|
303
|
+
);
|
|
304
|
+
if (sessions.length && !selectedSession) {
|
|
305
|
+
options.setNote("Open this agent through its server session first.");
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
195
308
|
const row = agentGroups()
|
|
196
309
|
.find((group) => group.machineId === id)
|
|
197
310
|
?.agents.find((agent) => agent.sessionName === sessionName && agent.paneId === paneId);
|
|
@@ -202,11 +315,16 @@ export function createApplicationMachineNavigation(options: {
|
|
|
202
315
|
return;
|
|
203
316
|
}
|
|
204
317
|
options.cancelOpen();
|
|
205
|
-
if (!select(id)) return;
|
|
318
|
+
if (!select(id, selectedSession?.liveSessionId, selectedSession?.server)) return;
|
|
319
|
+
if (selectedSession) setActiveSessionKey(selectedSession.id);
|
|
206
320
|
navigation++;
|
|
207
321
|
setFocused(false);
|
|
208
322
|
void options.openAgent?.(row, source);
|
|
209
323
|
},
|
|
324
|
+
onSelectServer: (machineId, server) => {
|
|
325
|
+
showSwitcher(false);
|
|
326
|
+
setServerFocus({ machineId, server });
|
|
327
|
+
},
|
|
210
328
|
onSelectMachine: (id) => {
|
|
211
329
|
tabs.suspend();
|
|
212
330
|
navigation++;
|
|
@@ -242,6 +360,8 @@ export function createApplicationMachineNavigation(options: {
|
|
|
242
360
|
catalog,
|
|
243
361
|
agents,
|
|
244
362
|
selectedMachineId: () => manager.snapshot().selectedMachineId,
|
|
363
|
+
openSelectedSession: (name: string, source: "keyboard" | "mouse", key?: string) =>
|
|
364
|
+
open(manager.snapshot().selectedMachineId, name, source, true, undefined, undefined, key),
|
|
245
365
|
automaticOpen: manager.snapshot().machines.length === 1,
|
|
246
366
|
automaticOpenAllowed: () =>
|
|
247
367
|
navigation === 0 && manager.snapshot().selectedMachineId === "local",
|
|
@@ -262,7 +382,18 @@ export function createApplicationMachineNavigation(options: {
|
|
|
262
382
|
options.setNote("That session is unavailable or has been replaced.");
|
|
263
383
|
return;
|
|
264
384
|
}
|
|
265
|
-
if (
|
|
385
|
+
if (
|
|
386
|
+
await open(
|
|
387
|
+
group.id,
|
|
388
|
+
session.name,
|
|
389
|
+
"keyboard",
|
|
390
|
+
false,
|
|
391
|
+
session.liveSessionId,
|
|
392
|
+
session.server,
|
|
393
|
+
session.id,
|
|
394
|
+
)
|
|
395
|
+
)
|
|
396
|
+
historyIndex = next;
|
|
266
397
|
},
|
|
267
398
|
cycleTab(direction: -1 | 1, close = false) {
|
|
268
399
|
const value = tabs.snapshot();
|
|
@@ -276,10 +407,22 @@ export function createApplicationMachineNavigation(options: {
|
|
|
276
407
|
if (target) void tabs.activate(target.key);
|
|
277
408
|
},
|
|
278
409
|
closeSwitcher: () => setSwitching(false),
|
|
410
|
+
switcherFilter: (command: ApplicationPaletteCommand) => {
|
|
411
|
+
const focus = serverFocus();
|
|
412
|
+
return (
|
|
413
|
+
!focus ||
|
|
414
|
+
(typeof command === "object" &&
|
|
415
|
+
command.fleet?.machineId === focus.machineId &&
|
|
416
|
+
command.fleet?.server?.serverId === focus.server.serverId &&
|
|
417
|
+
command.fleet?.server?.generation === focus.server.generation)
|
|
418
|
+
);
|
|
419
|
+
},
|
|
279
420
|
paletteCommands(): readonly ApplicationPaletteCommand[] {
|
|
280
421
|
const groupsById = new Map(agentGroups().map((group) => [group.machineId, group]));
|
|
281
422
|
return snapshot().groups.flatMap((group) => {
|
|
282
|
-
const agentGroup =
|
|
423
|
+
const agentGroup = !group.sessions.some((session) => isDefaultSession(group.id, session))
|
|
424
|
+
? undefined
|
|
425
|
+
: groupsById.get(group.id);
|
|
283
426
|
const agentsBySession = new Map<string, ApplicationMachineAgent[]>();
|
|
284
427
|
for (const agent of agentGroup?.agents ?? []) {
|
|
285
428
|
if (!agent.liveSessionId || !agent.paneId) continue;
|
|
@@ -297,20 +440,25 @@ export function createApplicationMachineNavigation(options: {
|
|
|
297
440
|
? saved().recent.indexOf(session.id)
|
|
298
441
|
: 1000,
|
|
299
442
|
liveSessionId: session.liveSessionId,
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
443
|
+
server: session.server,
|
|
444
|
+
hostLabel: `${group.label}${session.serverLabel ? ` / ${session.serverLabel}` : ""}`,
|
|
445
|
+
agentActivities:
|
|
446
|
+
isDefaultSession(group.id, session) && agentGroup?.available
|
|
447
|
+
? (agentsBySession.get(session.liveSessionId) ?? []).map((a) => ({
|
|
448
|
+
paneId: a.paneId!,
|
|
449
|
+
attention: a.attention,
|
|
450
|
+
activity: a.activity,
|
|
451
|
+
}))
|
|
452
|
+
: undefined,
|
|
308
453
|
daemonInstanceId: daemon?.instanceId ?? "",
|
|
309
454
|
disabled: session.disabled || group.state !== "ready",
|
|
310
455
|
};
|
|
311
456
|
return [
|
|
312
457
|
{ kind: "open-session", sessionName: session.name, label: session.name, fleet },
|
|
313
|
-
...(
|
|
458
|
+
...(isDefaultSession(group.id, session)
|
|
459
|
+
? (agentsBySession.get(session.liveSessionId) ?? [])
|
|
460
|
+
: []
|
|
461
|
+
).map((a) => ({
|
|
314
462
|
kind: "jump-agent" as const,
|
|
315
463
|
sessionName: session.name,
|
|
316
464
|
paneId: a.paneId!,
|
|
@@ -332,6 +480,21 @@ export function createApplicationMachineNavigation(options: {
|
|
|
332
480
|
disabled: group.state !== "ready",
|
|
333
481
|
},
|
|
334
482
|
},
|
|
483
|
+
...(group.servers ?? []).map((server) => ({
|
|
484
|
+
kind: "open-machine" as const,
|
|
485
|
+
sessionName: "" as const,
|
|
486
|
+
label: `${server.label} · ${server.serverId.slice(-6)} · New session ^N`,
|
|
487
|
+
fleet: {
|
|
488
|
+
machineId: group.id,
|
|
489
|
+
hostLabel: `${group.label} / ${server.label}`,
|
|
490
|
+
liveSessionId: "",
|
|
491
|
+
daemonInstanceId: daemon?.instanceId ?? "",
|
|
492
|
+
disabled: group.state !== "ready" || server.state !== "online",
|
|
493
|
+
...(server.state === "online"
|
|
494
|
+
? { server: { serverId: server.serverId, generation: server.generation } }
|
|
495
|
+
: {}),
|
|
496
|
+
},
|
|
497
|
+
})),
|
|
335
498
|
...sessions,
|
|
336
499
|
];
|
|
337
500
|
});
|
|
@@ -341,7 +504,12 @@ export function createApplicationMachineNavigation(options: {
|
|
|
341
504
|
const target = command.fleet;
|
|
342
505
|
const session = snapshot()
|
|
343
506
|
.groups.find((g) => g.id === target.machineId)
|
|
344
|
-
?.sessions.find(
|
|
507
|
+
?.sessions.find(
|
|
508
|
+
(s) =>
|
|
509
|
+
s.liveSessionId === target.liveSessionId &&
|
|
510
|
+
s.server?.serverId === target.server?.serverId &&
|
|
511
|
+
s.server?.generation === target.server?.generation,
|
|
512
|
+
);
|
|
345
513
|
if (session)
|
|
346
514
|
preferences.change({
|
|
347
515
|
type: "favorite",
|
|
@@ -357,6 +525,22 @@ export function createApplicationMachineNavigation(options: {
|
|
|
357
525
|
if (!target) return;
|
|
358
526
|
const handle = manager.getMachine(target.machineId);
|
|
359
527
|
if (command.kind === "open-machine") {
|
|
528
|
+
if (target.server) {
|
|
529
|
+
const descriptor = snapshot()
|
|
530
|
+
.groups.find((group) => group.id === target.machineId)
|
|
531
|
+
?.servers?.find(
|
|
532
|
+
(server) =>
|
|
533
|
+
server.serverId === target.server?.serverId &&
|
|
534
|
+
server.generation === target.server.generation &&
|
|
535
|
+
server.state === "online",
|
|
536
|
+
);
|
|
537
|
+
if (!descriptor || handle?.read()?.instanceId !== target.daemonInstanceId) {
|
|
538
|
+
options.setNote("That server has changed. Select it again.");
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
sidebar.onSelectServer?.(target.machineId, target.server);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
360
544
|
if (
|
|
361
545
|
handle?.endpoint().state === "ready" &&
|
|
362
546
|
handle.read()?.instanceId === target.daemonInstanceId
|
|
@@ -369,7 +553,11 @@ export function createApplicationMachineNavigation(options: {
|
|
|
369
553
|
(g) => g.id === target.machineId || g.routeIds?.includes(target.machineId),
|
|
370
554
|
);
|
|
371
555
|
const session = group?.sessions.find(
|
|
372
|
-
(s) =>
|
|
556
|
+
(s) =>
|
|
557
|
+
s.liveSessionId === target.liveSessionId &&
|
|
558
|
+
!s.disabled &&
|
|
559
|
+
s.server?.serverId === target.server?.serverId &&
|
|
560
|
+
s.server?.generation === target.server?.generation,
|
|
373
561
|
);
|
|
374
562
|
if (
|
|
375
563
|
!session ||
|
|
@@ -381,7 +569,15 @@ export function createApplicationMachineNavigation(options: {
|
|
|
381
569
|
}
|
|
382
570
|
if (command.kind === "jump-agent")
|
|
383
571
|
sidebar.onOpenAgent?.(target.machineId, session.name, command.paneId, source);
|
|
384
|
-
else
|
|
572
|
+
else
|
|
573
|
+
await open(
|
|
574
|
+
target.machineId,
|
|
575
|
+
session.name,
|
|
576
|
+
source,
|
|
577
|
+
true,
|
|
578
|
+
target.liveSessionId,
|
|
579
|
+
target.server,
|
|
580
|
+
);
|
|
385
581
|
},
|
|
386
582
|
adding,
|
|
387
583
|
alias,
|
|
@@ -433,16 +629,47 @@ export function createApplicationMachineNavigation(options: {
|
|
|
433
629
|
setError(cause instanceof Error ? cause.message : "Could not add this machine.");
|
|
434
630
|
}
|
|
435
631
|
},
|
|
436
|
-
start(target: string | null) {
|
|
632
|
+
start(target: string | null, serverId?: string | null) {
|
|
437
633
|
agents.start();
|
|
438
634
|
catalog.start();
|
|
439
|
-
if (!target) return;
|
|
635
|
+
if (!target && !serverId) return;
|
|
440
636
|
const id = manager.snapshot().selectedMachineId;
|
|
441
637
|
const token = navigation;
|
|
638
|
+
|
|
639
|
+
let machineReady = false;
|
|
640
|
+
const attempt = () => {
|
|
641
|
+
if (!machineReady) return;
|
|
642
|
+
if (token !== navigation) {
|
|
643
|
+
attemptStartup = null;
|
|
644
|
+
return;
|
|
645
|
+
}
|
|
646
|
+
const group = snapshot().groups.find((group) => group.id === id);
|
|
647
|
+
if (group?.state !== "ready") return;
|
|
648
|
+
const matches = group.sessions.filter(
|
|
649
|
+
(row) =>
|
|
650
|
+
!row.disabled &&
|
|
651
|
+
(!serverId || row.server?.serverId === serverId) &&
|
|
652
|
+
(!target || row.name === target),
|
|
653
|
+
);
|
|
654
|
+
if (matches.length !== 1) {
|
|
655
|
+
options.setNote(
|
|
656
|
+
matches.length
|
|
657
|
+
? "Select a session under the requested tmux server."
|
|
658
|
+
: "Requested server or session is unavailable. Select it again.",
|
|
659
|
+
);
|
|
660
|
+
attemptStartup = null;
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const row = matches[0]!;
|
|
664
|
+
attemptStartup = null;
|
|
665
|
+
void open(id, row.name, "keyboard", true, row.liveSessionId, row.server, row.id);
|
|
666
|
+
};
|
|
667
|
+
attemptStartup = attempt;
|
|
442
668
|
void manager.getMachine(id)?.ready.then((ready) => {
|
|
443
|
-
|
|
444
|
-
|
|
669
|
+
machineReady = ready;
|
|
670
|
+
attemptStartup?.();
|
|
445
671
|
});
|
|
672
|
+
attempt();
|
|
446
673
|
},
|
|
447
674
|
};
|
|
448
675
|
}
|
|
@@ -17,7 +17,7 @@ export function ApplicationMachineOverlays(props: {
|
|
|
17
17
|
open={true}
|
|
18
18
|
attentionOnly={props.machines.attentionOnly()}
|
|
19
19
|
rows={[]}
|
|
20
|
-
commands={props.machines.paletteCommands()}
|
|
20
|
+
commands={props.machines.paletteCommands().filter(props.machines.switcherFilter)}
|
|
21
21
|
onActivate={(c) => {
|
|
22
22
|
if (typeof c === "object") void props.machines.openPalette(c, "keyboard");
|
|
23
23
|
}}
|