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
package/packages/daemon/src/tui/mirror/runtime/application-terminal-interaction-controller.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { WorkspaceWindowLinkSelectResultSchemaZ } from "@tmux-ide/contracts";
|
|
2
|
+
import { windowLinkForPane, windowLinkTarget } from "./application-terminal-workspace-policy.ts";
|
|
1
3
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
4
|
import type { Accessor, Setter } from "solid-js";
|
|
3
|
-
import type { SessionRuntimeTerminalInput } from "@tmux-ide/contracts";
|
|
5
|
+
import type { WindowLinkTarget, SessionRuntimeTerminalInput } from "@tmux-ide/contracts";
|
|
4
6
|
import {
|
|
5
7
|
currentTuiPerformanceEventSink,
|
|
6
8
|
type TuiTerminalInputOrigin,
|
|
@@ -79,6 +81,8 @@ export interface ApplicationTerminalInteractionController {
|
|
|
79
81
|
adoptGeneration(snapshot: OpenTuiGenerationHostSnapshot | null): void;
|
|
80
82
|
adoptLayout(snapshot: OpenTuiWorkspaceLayoutSnapshot): void;
|
|
81
83
|
selectPane(paneId: string): void;
|
|
84
|
+
selectWindowLink(target: WindowLinkTarget): Promise<void>;
|
|
85
|
+
unlinkWindowLink(target: WindowLinkTarget): Promise<string>;
|
|
82
86
|
sendInput(
|
|
83
87
|
input: SessionRuntimeTerminalInput,
|
|
84
88
|
parserOrigin?: Omit<
|
|
@@ -628,6 +632,41 @@ export function createApplicationTerminalInteractionController(
|
|
|
628
632
|
}
|
|
629
633
|
};
|
|
630
634
|
|
|
635
|
+
let linkSelection: {
|
|
636
|
+
target: WindowLinkTarget;
|
|
637
|
+
paneId: string;
|
|
638
|
+
layout: OpenTuiWorkspaceLayoutSnapshot;
|
|
639
|
+
received: boolean;
|
|
640
|
+
resolve: (selected: boolean) => void;
|
|
641
|
+
timer: ReturnType<typeof setTimeout>;
|
|
642
|
+
} | null = null;
|
|
643
|
+
const finishLinkSelection = (selected: boolean) => {
|
|
644
|
+
const pending = linkSelection;
|
|
645
|
+
if (!pending) return;
|
|
646
|
+
linkSelection = null;
|
|
647
|
+
clearTimeout(pending.timer);
|
|
648
|
+
pending.resolve(selected);
|
|
649
|
+
};
|
|
650
|
+
const reconcileLinkSelection = (snapshot: OpenTuiWorkspaceLayoutSnapshot) => {
|
|
651
|
+
const pending = linkSelection;
|
|
652
|
+
if (!pending) return;
|
|
653
|
+
pending.layout = snapshot;
|
|
654
|
+
const observed = windowLinkTarget(snapshot, pending.target.linkId);
|
|
655
|
+
if (
|
|
656
|
+
!observed ||
|
|
657
|
+
observed.linkRevision !== pending.target.linkRevision ||
|
|
658
|
+
observed.liveSessionId !== pending.target.liveSessionId ||
|
|
659
|
+
observed.expectedSemanticWindowId !== pending.target.expectedSemanticWindowId
|
|
660
|
+
) {
|
|
661
|
+
finishLinkSelection(false);
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
if (!pending.received || snapshot.windowLinks?.activeLinkId !== pending.target.linkId) return;
|
|
665
|
+
finishLinkSelection(
|
|
666
|
+
snapshot.current !== null && paneForWindow(snapshot.current) === pending.paneId,
|
|
667
|
+
);
|
|
668
|
+
};
|
|
669
|
+
|
|
631
670
|
const paneInput = new TerminalPaneInputRouter<{
|
|
632
671
|
readonly input: SessionRuntimeTerminalInput;
|
|
633
672
|
readonly parserOrigin?: Omit<
|
|
@@ -636,6 +675,7 @@ export function createApplicationTerminalInteractionController(
|
|
|
636
675
|
>;
|
|
637
676
|
}>({
|
|
638
677
|
select: async (paneId) => {
|
|
678
|
+
finishLinkSelection(false);
|
|
639
679
|
const expected = liveSelectionTarget();
|
|
640
680
|
const selecting = pendingWindowSwitch;
|
|
641
681
|
const operationId = selecting?.traceId;
|
|
@@ -649,6 +689,7 @@ export function createApplicationTerminalInteractionController(
|
|
|
649
689
|
(failure) => {
|
|
650
690
|
selectionFailure.current = failure;
|
|
651
691
|
},
|
|
692
|
+
windowLinkForPane(options.layout(), paneId) ?? undefined,
|
|
652
693
|
)
|
|
653
694
|
: null;
|
|
654
695
|
if (selecting && pendingWindowSwitch === selecting) {
|
|
@@ -833,6 +874,7 @@ export function createApplicationTerminalInteractionController(
|
|
|
833
874
|
next.fastLane !== inputAuthorityIdentity.fastLane ||
|
|
834
875
|
next.adapter !== inputAuthorityIdentity.adapter);
|
|
835
876
|
if (next === null || replaced) {
|
|
877
|
+
finishLinkSelection(false);
|
|
836
878
|
paneInput.invalidateSelection();
|
|
837
879
|
pendingWindowSwitch = null;
|
|
838
880
|
pendingWindowRename = null;
|
|
@@ -846,6 +888,7 @@ export function createApplicationTerminalInteractionController(
|
|
|
846
888
|
inputAuthorityIdentity = next;
|
|
847
889
|
},
|
|
848
890
|
adoptLayout(snapshot) {
|
|
891
|
+
reconcileLinkSelection(snapshot);
|
|
849
892
|
paneInput.adoptCanonicalPane(snapshot.current ? paneForWindow(snapshot.current) : null);
|
|
850
893
|
const currentWindow = snapshot.current?.semanticWindowId ?? snapshot.current?.windowName;
|
|
851
894
|
const currentPane = snapshot.current ? paneForWindow(snapshot.current) : null;
|
|
@@ -933,7 +976,130 @@ export function createApplicationTerminalInteractionController(
|
|
|
933
976
|
maybeRequestWindowSwitchFrame();
|
|
934
977
|
maybeRequestPaneResizeFrame();
|
|
935
978
|
},
|
|
936
|
-
selectPane: (paneId) =>
|
|
979
|
+
selectPane: (paneId) => {
|
|
980
|
+
finishLinkSelection(false);
|
|
981
|
+
paneInput.selectPane(paneId);
|
|
982
|
+
},
|
|
983
|
+
async unlinkWindowLink(target) {
|
|
984
|
+
const expected = liveSelectionTarget();
|
|
985
|
+
const observed = windowLinkTarget(options.layout(), target.linkId);
|
|
986
|
+
if (
|
|
987
|
+
!expected ||
|
|
988
|
+
!observed ||
|
|
989
|
+
observed.liveSessionId !== target.liveSessionId ||
|
|
990
|
+
observed.linkRevision !== target.linkRevision ||
|
|
991
|
+
observed.expectedSemanticWindowId !== target.expectedSemanticWindowId
|
|
992
|
+
)
|
|
993
|
+
return "Window link changed; reopen its menu";
|
|
994
|
+
try {
|
|
995
|
+
if (
|
|
996
|
+
!expected.client.ownsRuntimeAuthority?.("input") &&
|
|
997
|
+
!(await expected.client.requestAuthority("input"))
|
|
998
|
+
)
|
|
999
|
+
return "Window unlink requires input control";
|
|
1000
|
+
const current = liveSelectionTarget();
|
|
1001
|
+
if (
|
|
1002
|
+
current?.client !== expected.client ||
|
|
1003
|
+
current.daemonGeneration !== expected.daemonGeneration
|
|
1004
|
+
)
|
|
1005
|
+
return "Session changed; reopen the window menu";
|
|
1006
|
+
await expected.client.dispatch({
|
|
1007
|
+
kind: "semantic-intent",
|
|
1008
|
+
intent: {
|
|
1009
|
+
verb: "workspace.window.link.unlink",
|
|
1010
|
+
workspaceName: expected.workspaceName,
|
|
1011
|
+
target,
|
|
1012
|
+
},
|
|
1013
|
+
});
|
|
1014
|
+
return "Window link removed";
|
|
1015
|
+
} catch {
|
|
1016
|
+
return "Window link could not be removed; it may be the last link or have changed";
|
|
1017
|
+
}
|
|
1018
|
+
},
|
|
1019
|
+
async selectWindowLink(target) {
|
|
1020
|
+
const expected = liveSelectionTarget();
|
|
1021
|
+
const snapshot = options.layout();
|
|
1022
|
+
const observed = windowLinkTarget(snapshot, target.linkId);
|
|
1023
|
+
const backing = snapshot.windows.find(
|
|
1024
|
+
(window) => window.semanticWindowId === target.expectedSemanticWindowId,
|
|
1025
|
+
);
|
|
1026
|
+
const paneId = backing && paneForWindow(backing);
|
|
1027
|
+
if (
|
|
1028
|
+
!expected ||
|
|
1029
|
+
!paneId ||
|
|
1030
|
+
!observed ||
|
|
1031
|
+
observed.liveSessionId !== target.liveSessionId ||
|
|
1032
|
+
observed.linkRevision !== target.linkRevision ||
|
|
1033
|
+
observed.expectedSemanticWindowId !== target.expectedSemanticWindowId
|
|
1034
|
+
)
|
|
1035
|
+
return;
|
|
1036
|
+
finishLinkSelection(false);
|
|
1037
|
+
let resolve!: (selected: boolean) => void;
|
|
1038
|
+
const settled = new Promise<boolean>((done) => {
|
|
1039
|
+
resolve = done;
|
|
1040
|
+
});
|
|
1041
|
+
const pending = {
|
|
1042
|
+
target,
|
|
1043
|
+
paneId,
|
|
1044
|
+
layout: snapshot,
|
|
1045
|
+
received: false,
|
|
1046
|
+
resolve,
|
|
1047
|
+
timer: setTimeout(() => {
|
|
1048
|
+
if (linkSelection === pending) finishLinkSelection(false);
|
|
1049
|
+
}, 5_000),
|
|
1050
|
+
};
|
|
1051
|
+
pending.timer.unref?.();
|
|
1052
|
+
linkSelection = pending;
|
|
1053
|
+
// Register input ordering synchronously, before authority or command awaits.
|
|
1054
|
+
paneInput.selectPane(paneId, { presentOptimistically: false, selection: () => settled });
|
|
1055
|
+
try {
|
|
1056
|
+
if (
|
|
1057
|
+
!expected.client.ownsRuntimeAuthority?.("input") &&
|
|
1058
|
+
!(await expected.client.requestAuthority("input"))
|
|
1059
|
+
) {
|
|
1060
|
+
if (linkSelection === pending) finishLinkSelection(false);
|
|
1061
|
+
return;
|
|
1062
|
+
}
|
|
1063
|
+
const current = liveSelectionTarget();
|
|
1064
|
+
if (
|
|
1065
|
+
linkSelection !== pending ||
|
|
1066
|
+
current?.client !== expected.client ||
|
|
1067
|
+
current.daemonGeneration !== expected.daemonGeneration
|
|
1068
|
+
) {
|
|
1069
|
+
if (linkSelection === pending) finishLinkSelection(false);
|
|
1070
|
+
return;
|
|
1071
|
+
}
|
|
1072
|
+
const wrapper = (await expected.client.dispatch({
|
|
1073
|
+
kind: "semantic-intent",
|
|
1074
|
+
intent: {
|
|
1075
|
+
verb: "workspace.window.link.select",
|
|
1076
|
+
workspaceName: expected.workspaceName,
|
|
1077
|
+
target,
|
|
1078
|
+
},
|
|
1079
|
+
})) as { kind?: string; operationId?: string; result?: unknown };
|
|
1080
|
+
const result = WorkspaceWindowLinkSelectResultSchemaZ.safeParse(wrapper.result);
|
|
1081
|
+
if (linkSelection !== pending) return;
|
|
1082
|
+
if (
|
|
1083
|
+
wrapper.kind !== "semantic-intent" ||
|
|
1084
|
+
!result.success ||
|
|
1085
|
+
result.data.operationId !== wrapper.operationId ||
|
|
1086
|
+
result.data.daemonInstanceId !== expected.daemonGeneration ||
|
|
1087
|
+
result.data.workspaceName !== expected.workspaceName ||
|
|
1088
|
+
result.data.target.linkId !== target.linkId ||
|
|
1089
|
+
result.data.target.liveSessionId !== target.liveSessionId ||
|
|
1090
|
+
result.data.target.linkRevision !== target.linkRevision ||
|
|
1091
|
+
result.data.target.expectedSemanticWindowId !== target.expectedSemanticWindowId
|
|
1092
|
+
) {
|
|
1093
|
+
finishLinkSelection(false);
|
|
1094
|
+
return;
|
|
1095
|
+
}
|
|
1096
|
+
pending.received = true;
|
|
1097
|
+
reconcileLinkSelection(pending.layout);
|
|
1098
|
+
await settled;
|
|
1099
|
+
} catch {
|
|
1100
|
+
if (linkSelection === pending) finishLinkSelection(false);
|
|
1101
|
+
}
|
|
1102
|
+
},
|
|
937
1103
|
sendInput: async (input, parserOrigin) => {
|
|
938
1104
|
await paneInput.sendInput({ input, parserOrigin });
|
|
939
1105
|
},
|
|
@@ -1152,6 +1318,15 @@ export function createApplicationTerminalInteractionController(
|
|
|
1152
1318
|
if (next) paneInput.selectPane(next);
|
|
1153
1319
|
},
|
|
1154
1320
|
cycleWindow() {
|
|
1321
|
+
const topology = options.layout().windowLinks;
|
|
1322
|
+
if (topology) {
|
|
1323
|
+
if (topology.links.length < 2) return;
|
|
1324
|
+
const current = topology.links.findIndex((link) => link.linkId === topology.activeLinkId);
|
|
1325
|
+
const next = topology.links[(current + 1) % topology.links.length]!;
|
|
1326
|
+
const target = windowLinkTarget(options.layout(), next.linkId);
|
|
1327
|
+
if (target) void this.selectWindowLink(target);
|
|
1328
|
+
return;
|
|
1329
|
+
}
|
|
1155
1330
|
const windows = options.layout().windows;
|
|
1156
1331
|
if (windows.length < 2) return;
|
|
1157
1332
|
const current = windows.findIndex((window) => window.currentWindow);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WindowLinkTarget } from "@tmux-ide/contracts";
|
|
1
2
|
import type { AgentActivity } from "@tmux-ide/contracts";
|
|
2
3
|
|
|
3
4
|
import type { OpenTuiWorkspaceLayoutSnapshot } from "../open-tui-workspace-runtime-port.ts";
|
|
@@ -309,3 +310,64 @@ export function terminalPaneResizePreview(
|
|
|
309
310
|
}),
|
|
310
311
|
});
|
|
311
312
|
}
|
|
313
|
+
|
|
314
|
+
/** Resolve only live opaque observations; native indexes are display metadata. */
|
|
315
|
+
export function windowLinkTarget(
|
|
316
|
+
snapshot: OpenTuiWorkspaceLayoutSnapshot,
|
|
317
|
+
linkId: string,
|
|
318
|
+
): WindowLinkTarget | null {
|
|
319
|
+
const topology = snapshot.windowLinks;
|
|
320
|
+
const link = topology?.links.find((candidate) => candidate.linkId === linkId);
|
|
321
|
+
if (!topology || !link) return null;
|
|
322
|
+
return {
|
|
323
|
+
liveSessionId: topology.liveSessionId,
|
|
324
|
+
linkRevision: topology.linkRevision,
|
|
325
|
+
linkId: link.linkId,
|
|
326
|
+
expectedSemanticWindowId: link.semanticWindowId,
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/** Pane navigation within the active link must not silently choose a sibling link. */
|
|
331
|
+
export function windowLinkForPane(
|
|
332
|
+
snapshot: OpenTuiWorkspaceLayoutSnapshot,
|
|
333
|
+
paneId: string,
|
|
334
|
+
): WindowLinkTarget | null {
|
|
335
|
+
const backing = snapshot.windows.find((window) =>
|
|
336
|
+
window.panes.some((pane) => pane.pane === paneId),
|
|
337
|
+
);
|
|
338
|
+
const topology = snapshot.windowLinks;
|
|
339
|
+
if (!backing || !topology) return null;
|
|
340
|
+
const links = topology.links.filter((link) => link.semanticWindowId === backing.semanticWindowId);
|
|
341
|
+
const link =
|
|
342
|
+
links.find((candidate) => candidate.linkId === topology.activeLinkId) ??
|
|
343
|
+
(links.length === 1 ? links[0] : undefined);
|
|
344
|
+
return link ? windowLinkTarget(snapshot, link.linkId) : null;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/** Root composition for backing zoom and link actions, all restoring host focus. */
|
|
348
|
+
export function terminalWindowActionCallbacks(
|
|
349
|
+
controller: Pick<
|
|
350
|
+
import("./application-terminal-interaction-controller.ts").ApplicationTerminalInteractionController,
|
|
351
|
+
"selectWindowLink" | "unlinkWindowLink" | "zoomPane" | "selectPane"
|
|
352
|
+
>,
|
|
353
|
+
notify: (message: string) => void,
|
|
354
|
+
recover: import("./application-host-focus-presentation.ts").ApplicationHostFocusRecovery,
|
|
355
|
+
cancelNavigation: () => void,
|
|
356
|
+
) {
|
|
357
|
+
return {
|
|
358
|
+
onSelectPane: recover((paneId: string) => {
|
|
359
|
+
cancelNavigation();
|
|
360
|
+
controller.selectPane(paneId);
|
|
361
|
+
}),
|
|
362
|
+
onZoomPane: recover((paneId?: string) => {
|
|
363
|
+
void controller.zoomPane(paneId).then(notify);
|
|
364
|
+
}),
|
|
365
|
+
onSelectWindowLink: recover((target: WindowLinkTarget) => {
|
|
366
|
+
cancelNavigation();
|
|
367
|
+
void controller.selectWindowLink(target);
|
|
368
|
+
}),
|
|
369
|
+
onUnlinkWindowLink: recover((target: WindowLinkTarget) => {
|
|
370
|
+
void controller.unlinkWindowLink(target).then(notify);
|
|
371
|
+
}),
|
|
372
|
+
};
|
|
373
|
+
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { Menu } from "../ui/index.ts";
|
|
2
|
+
import type { WindowLinkTarget } from "@tmux-ide/contracts";
|
|
3
|
+
import { windowLinkTarget } from "./application-terminal-workspace-policy.ts";
|
|
1
4
|
import type { PaneInteractionProjection } from "@tmux-ide/core";
|
|
2
5
|
/* @jsxImportSource @opentui/solid */
|
|
3
6
|
import {
|
|
@@ -61,7 +64,6 @@ import {
|
|
|
61
64
|
type TerminalSelectionRange,
|
|
62
65
|
} from "./terminal-selection.ts";
|
|
63
66
|
import {
|
|
64
|
-
retainedTerminalWindowKey,
|
|
65
67
|
terminalAgentStatusLabel,
|
|
66
68
|
terminalPaneDisplayTitle,
|
|
67
69
|
terminalPaneResizePreview,
|
|
@@ -168,6 +170,8 @@ export interface ApplicationTerminalWorkspaceProps {
|
|
|
168
170
|
/** Daemon-authored semantic agent state, keyed by durable pane identity. */
|
|
169
171
|
readonly agentIndicators?: Accessor<ReadonlyMap<string, ApplicationTerminalAgentIndicator>>;
|
|
170
172
|
readonly onSelectPane: (paneId: string) => void;
|
|
173
|
+
readonly onSelectWindowLink?: (target: WindowLinkTarget) => void;
|
|
174
|
+
readonly onUnlinkWindowLink?: (target: WindowLinkTarget) => void;
|
|
171
175
|
readonly onCreateWindow?: () => void;
|
|
172
176
|
readonly onPaneContextAction?: (
|
|
173
177
|
paneId: string,
|
|
@@ -332,37 +336,56 @@ export function ApplicationTerminalWorkspace(props: ApplicationTerminalWorkspace
|
|
|
332
336
|
),
|
|
333
337
|
);
|
|
334
338
|
const visibleFrames = createMemo(() => projectedFrames().filter((frame) => frame.visible));
|
|
335
|
-
const
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
339
|
+
const [windowLinkMenu, setWindowLinkMenu] = createSignal<WindowLinkTarget | null>(null);
|
|
340
|
+
const unlinkWindowTab = () => {
|
|
341
|
+
const target = windowLinkMenu();
|
|
342
|
+
setWindowLinkMenu(null);
|
|
343
|
+
if (target) props.onUnlinkWindowLink?.(target);
|
|
344
|
+
};
|
|
345
|
+
createEffect(() => {
|
|
346
|
+
const target = windowLinkMenu();
|
|
347
|
+
const observed = target && windowLinkTarget(layout(), target.linkId);
|
|
348
|
+
if (
|
|
349
|
+
target &&
|
|
350
|
+
(!observed ||
|
|
351
|
+
observed.liveSessionId !== target.liveSessionId ||
|
|
352
|
+
observed.linkRevision !== target.linkRevision)
|
|
353
|
+
)
|
|
354
|
+
setWindowLinkMenu(null);
|
|
355
|
+
});
|
|
356
|
+
const terminalWindowTabs = createMemo<readonly TerminalWindowTab[]>(() => {
|
|
357
|
+
const snapshot = layout();
|
|
358
|
+
if (!snapshot.windowLinks)
|
|
359
|
+
return snapshot.windows.map((window, index) => ({
|
|
355
360
|
index,
|
|
356
361
|
name: terminalWindowTitle(window),
|
|
357
362
|
active: window.currentWindow,
|
|
358
363
|
sync: false,
|
|
359
364
|
semanticWindowId: window.semanticWindowId,
|
|
360
365
|
activePaneId: terminalWindowPane(window),
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
+
disabled: true,
|
|
367
|
+
}));
|
|
368
|
+
return snapshot.windowLinks.links.flatMap((link) => {
|
|
369
|
+
const window = snapshot.windows.find(
|
|
370
|
+
(candidate) => candidate.semanticWindowId === link.semanticWindowId,
|
|
371
|
+
);
|
|
372
|
+
if (!window) return [];
|
|
373
|
+
const indicator = terminalWindowAgentIndicator(window, agentIndicators());
|
|
374
|
+
return [
|
|
375
|
+
{
|
|
376
|
+
linkId: link.linkId,
|
|
377
|
+
index: link.displayIndex,
|
|
378
|
+
name: terminalWindowTitle(window),
|
|
379
|
+
active: snapshot.windowLinks?.activeLinkId === link.linkId,
|
|
380
|
+
sync: false,
|
|
381
|
+
semanticWindowId: window.semanticWindowId,
|
|
382
|
+
activePaneId: terminalWindowPane(window),
|
|
383
|
+
status: indicator ? terminalAgentStatusLabel(indicator.activity) : undefined,
|
|
384
|
+
attention: indicator?.attention,
|
|
385
|
+
},
|
|
386
|
+
];
|
|
387
|
+
});
|
|
388
|
+
});
|
|
366
389
|
if (props.onWindowPresented)
|
|
367
390
|
createRenderEffect(() => {
|
|
368
391
|
const current = layout().current;
|
|
@@ -911,6 +934,12 @@ export function ApplicationTerminalWorkspace(props: ApplicationTerminalWorkspace
|
|
|
911
934
|
props.onSelectionCopyOwner?.(copySelection);
|
|
912
935
|
const handlePaneMenuKey: PaneMenuKeyHandler = (name, event) => {
|
|
913
936
|
if (props.interactive === false) return false;
|
|
937
|
+
if (windowLinkMenu()) {
|
|
938
|
+
if (event?.eventType === "release") return true;
|
|
939
|
+
if (name === "escape") setWindowLinkMenu(null);
|
|
940
|
+
else if (name === "return" || name === "enter" || name === "u") unlinkWindowTab();
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
914
943
|
if (paneMenu.handleKey(name, event)) return true;
|
|
915
944
|
const focused = props.focusedPane;
|
|
916
945
|
const keyboard = keyboardCopy();
|
|
@@ -1097,7 +1126,10 @@ export function ApplicationTerminalWorkspace(props: ApplicationTerminalWorkspace
|
|
|
1097
1126
|
},
|
|
1098
1127
|
() =>
|
|
1099
1128
|
props.interactive !== false &&
|
|
1100
|
-
(drag !== null ||
|
|
1129
|
+
(drag !== null ||
|
|
1130
|
+
windowLinkMenu() !== null ||
|
|
1131
|
+
paneMenu.ownsInput() ||
|
|
1132
|
+
keyboardCopy() !== null),
|
|
1101
1133
|
() => {
|
|
1102
1134
|
// Called only after global shortcuts, copy, and local navigation decline
|
|
1103
1135
|
// the event, immediately before terminal key or paste delivery.
|
|
@@ -1116,7 +1148,7 @@ export function ApplicationTerminalWorkspace(props: ApplicationTerminalWorkspace
|
|
|
1116
1148
|
// listener and the typed listener on a target, even after stopPropagation.
|
|
1117
1149
|
const routePointer = (event: WorkspaceMouseEvent): void => {
|
|
1118
1150
|
if (event.type === "down") wheelGesture.reset();
|
|
1119
|
-
if (paneMenu.ownsInput()) {
|
|
1151
|
+
if (windowLinkMenu() || paneMenu.ownsInput()) {
|
|
1120
1152
|
event.stopPropagation?.();
|
|
1121
1153
|
return;
|
|
1122
1154
|
}
|
|
@@ -1649,9 +1681,18 @@ export function ApplicationTerminalWorkspace(props: ApplicationTerminalWorkspace
|
|
|
1649
1681
|
tabs={terminalWindowTabs}
|
|
1650
1682
|
hoveredIndex={null}
|
|
1651
1683
|
onActivate={(index) => {
|
|
1652
|
-
const
|
|
1653
|
-
|
|
1684
|
+
const tab = terminalWindowTabs().find((candidate) => candidate.index === index);
|
|
1685
|
+
const target = tab?.linkId ? windowLinkTarget(layout(), tab.linkId) : null;
|
|
1686
|
+
if (target) props.onSelectWindowLink?.(target);
|
|
1654
1687
|
}}
|
|
1688
|
+
onWindowActions={
|
|
1689
|
+
props.onUnlinkWindowLink
|
|
1690
|
+
? (index) => {
|
|
1691
|
+
const tab = terminalWindowTabs().find((candidate) => candidate.index === index);
|
|
1692
|
+
if (tab?.linkId) setWindowLinkMenu(windowLinkTarget(layout(), tab.linkId));
|
|
1693
|
+
}
|
|
1694
|
+
: undefined
|
|
1695
|
+
}
|
|
1655
1696
|
onNewWindow={() => props.onCreateWindow?.()}
|
|
1656
1697
|
/>
|
|
1657
1698
|
</Show>
|
|
@@ -1805,6 +1846,22 @@ export function ApplicationTerminalWorkspace(props: ApplicationTerminalWorkspace
|
|
|
1805
1846
|
</box>
|
|
1806
1847
|
)}
|
|
1807
1848
|
</For>
|
|
1849
|
+
<Show when={windowLinkMenu()}>
|
|
1850
|
+
<Menu
|
|
1851
|
+
theme={props.theme}
|
|
1852
|
+
title="Window link"
|
|
1853
|
+
left={0}
|
|
1854
|
+
top={1}
|
|
1855
|
+
width={Math.min(36, props.width)}
|
|
1856
|
+
viewportWidth={props.width}
|
|
1857
|
+
viewportHeight={props.height}
|
|
1858
|
+
selectedId="unlink"
|
|
1859
|
+
items={[{ id: "unlink", label: "Unlink this tab", shortcut: "U" }]}
|
|
1860
|
+
footer="Other links keep their panes"
|
|
1861
|
+
onDismiss={() => setWindowLinkMenu(null)}
|
|
1862
|
+
onSelect={unlinkWindowTab}
|
|
1863
|
+
/>
|
|
1864
|
+
</Show>
|
|
1808
1865
|
<Show when={paneContextMenu()}>
|
|
1809
1866
|
{(menu) => (
|
|
1810
1867
|
<PaneActionMenu
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { createTmuxServerClient } from "@tmux-ide/daemon-client/tmux-server-client";
|
|
2
|
+
import type { TmuxServerScope } from "@tmux-ide/contracts";
|
|
1
3
|
import type { ApplicationMachineAuthorityHandle } from "./application-machine-authority.ts";
|
|
2
4
|
import type { ActionResult, WorkspaceSessionKillArguments } from "@tmux-ide/contracts";
|
|
3
5
|
import {
|
|
@@ -52,6 +54,7 @@ async function dispatch<
|
|
|
52
54
|
|
|
53
55
|
type FleetHandle = Pick<ApplicationMachineAuthorityHandle, "read" | "endpoint">;
|
|
54
56
|
export interface FleetSessionCloseTarget {
|
|
57
|
+
readonly server?: TmuxServerScope;
|
|
55
58
|
readonly daemonInstanceId: string;
|
|
56
59
|
readonly liveSessionId: string;
|
|
57
60
|
readonly sessionName: string;
|
|
@@ -91,10 +94,31 @@ async function dispatchOnHandle<Name extends "workspace.session.create" | "works
|
|
|
91
94
|
export function createFleetSession(
|
|
92
95
|
input: WorkspaceSessionCreateArguments | FleetHandle,
|
|
93
96
|
name?: string,
|
|
97
|
+
server?: TmuxServerScope,
|
|
94
98
|
): Promise<WorkspaceSessionCreateResult | null> {
|
|
95
99
|
if ("read" in input) {
|
|
96
100
|
const daemon = input.read();
|
|
97
101
|
if (!daemon || !name) return Promise.resolve(null);
|
|
102
|
+
if (server) {
|
|
103
|
+
if (!daemon.authToken || input.endpoint().state !== "ready") return Promise.resolve(null);
|
|
104
|
+
const epoch = input.endpoint().epoch;
|
|
105
|
+
const baseUrl = canonicalDaemonUrl("http", daemon.bindHostname, daemon.port);
|
|
106
|
+
const client = createTmuxServerClient(
|
|
107
|
+
{ baseUrl, ownerToken: daemon.authToken, hostClientId: HOST_CLIENT_ID, origin: baseUrl },
|
|
108
|
+
server,
|
|
109
|
+
);
|
|
110
|
+
return client
|
|
111
|
+
.createSession(randomUUID(), {
|
|
112
|
+
displayName: name,
|
|
113
|
+
expectedDaemonInstanceId: server.generation,
|
|
114
|
+
})
|
|
115
|
+
.then((result) =>
|
|
116
|
+
input.endpoint().epoch === epoch && input.read()?.instanceId === daemon.instanceId
|
|
117
|
+
? result
|
|
118
|
+
: null,
|
|
119
|
+
)
|
|
120
|
+
.finally(() => client.dispose());
|
|
121
|
+
}
|
|
98
122
|
return dispatchOnHandle(input, "workspace.session.create", {
|
|
99
123
|
displayName: name,
|
|
100
124
|
expectedDaemonInstanceId: daemon.instanceId,
|
|
@@ -109,6 +133,34 @@ export function closeFleetSession(
|
|
|
109
133
|
target: FleetSessionCloseTarget,
|
|
110
134
|
): Promise<ActionResult<"workspace.session.kill"> | null> {
|
|
111
135
|
if (handle.read()?.instanceId !== target.daemonInstanceId) return Promise.resolve(null);
|
|
136
|
+
if (target.server) {
|
|
137
|
+
const daemon = handle.read();
|
|
138
|
+
if (!daemon?.authToken || handle.endpoint().state !== "ready") return Promise.resolve(null);
|
|
139
|
+
const epoch = handle.endpoint().epoch;
|
|
140
|
+
const baseUrl = canonicalDaemonUrl("http", daemon.bindHostname, daemon.port);
|
|
141
|
+
const client = createTmuxServerClient(
|
|
142
|
+
{ baseUrl, ownerToken: daemon.authToken, hostClientId: HOST_CLIENT_ID, origin: baseUrl },
|
|
143
|
+
target.server,
|
|
144
|
+
);
|
|
145
|
+
return client
|
|
146
|
+
.mutate(randomUUID(), {
|
|
147
|
+
verb: "workspace.session.kill",
|
|
148
|
+
workspaceName: target.workspaceName ?? target.sessionName,
|
|
149
|
+
fleetTarget: {
|
|
150
|
+
daemonInstanceId: target.server.generation,
|
|
151
|
+
liveSessionId: target.liveSessionId,
|
|
152
|
+
sessionName: target.sessionName,
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
.then((result) =>
|
|
156
|
+
handle.endpoint().epoch === epoch &&
|
|
157
|
+
handle.read()?.instanceId === target.daemonInstanceId &&
|
|
158
|
+
result.verb === "workspace.session.kill"
|
|
159
|
+
? result
|
|
160
|
+
: null,
|
|
161
|
+
)
|
|
162
|
+
.finally(() => client.dispose());
|
|
163
|
+
}
|
|
112
164
|
return dispatchOnHandle(handle, "workspace.session.kill", {
|
|
113
165
|
workspaceName: target.workspaceName ?? target.sessionName,
|
|
114
166
|
fleetTarget: {
|
|
@@ -316,7 +316,7 @@ function buildProductionBundle(
|
|
|
316
316
|
requestTerminalRuntimeInventoryRefresh: () => {
|
|
317
317
|
connection.transport.refreshTerminalRuntimeInventory();
|
|
318
318
|
},
|
|
319
|
-
actions: productionOwnerActions(readDaemon),
|
|
319
|
+
actions: connection.ownerActions ?? productionOwnerActions(readDaemon),
|
|
320
320
|
},
|
|
321
321
|
});
|
|
322
322
|
fastLane = createOpenTuiWorkspaceTerminalFastLane(
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WindowLinkTarget } from "@tmux-ide/contracts";
|
|
1
2
|
import type { WorkspaceClientDispatch } from "@tmux-ide/daemon-client/workspace-client-types";
|
|
2
3
|
|
|
3
4
|
interface PaneSelectionClient {
|
|
@@ -40,6 +41,7 @@ export async function selectTerminalPane(
|
|
|
40
41
|
semanticPaneId: string,
|
|
41
42
|
operationId?: string,
|
|
42
43
|
onFailure?: (failure: PaneSelectionFailure) => void,
|
|
44
|
+
windowLink?: WindowLinkTarget,
|
|
43
45
|
): Promise<PaneSelectionReceipt | null> {
|
|
44
46
|
const fail = (failure: PaneSelectionFailure): null => {
|
|
45
47
|
try {
|
|
@@ -95,6 +97,7 @@ export async function selectTerminalPane(
|
|
|
95
97
|
verb: "workspace.pane.select",
|
|
96
98
|
workspaceName: expected.workspaceName,
|
|
97
99
|
semanticPaneId,
|
|
100
|
+
...(windowLink ? { windowLink } : {}),
|
|
98
101
|
},
|
|
99
102
|
});
|
|
100
103
|
} catch (error) {
|
|
@@ -44,14 +44,25 @@ export class TerminalPaneInputRouter<Input> {
|
|
|
44
44
|
if (this.#pending?.selected === true) this.#pending = null;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
selectPane(
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
selectPane(
|
|
48
|
+
paneId: string,
|
|
49
|
+
options: {
|
|
50
|
+
readonly presentOptimistically?: boolean;
|
|
51
|
+
readonly selection?: () => Promise<boolean>;
|
|
52
|
+
} = {},
|
|
53
|
+
): void {
|
|
54
|
+
if (!options.selection && this.#pending?.paneId === paneId) return;
|
|
55
|
+
if (
|
|
56
|
+
!options.selection &&
|
|
57
|
+
this.#pending === null &&
|
|
58
|
+
this.#canonicalPane === paneId &&
|
|
59
|
+
this.#focusedPane === paneId
|
|
60
|
+
)
|
|
50
61
|
return;
|
|
51
62
|
const token = ++this.#selectionToken;
|
|
52
63
|
const presentOptimistically = options.presentOptimistically !== false;
|
|
53
64
|
if (presentOptimistically) this.#setFocusedPane(paneId);
|
|
54
|
-
const settled = this.#options.select(paneId);
|
|
65
|
+
const settled = options.selection ? options.selection() : this.#options.select(paneId);
|
|
55
66
|
this.#pending = { token, paneId, presentOptimistically, settled, selected: null };
|
|
56
67
|
void settled.then((selected) => {
|
|
57
68
|
if (this.#pending?.token !== token) return;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
import type { TmuxServerScope } from "@tmux-ide/contracts";
|
|
1
2
|
import { fuzzyTermsMatch as commandSearchMatch } from "../../team/fuzzy.ts";
|
|
2
3
|
export { fuzzyTermsMatch as commandSearchMatch } from "../../team/fuzzy.ts";
|
|
3
4
|
import { PANE_ACTION_MENU_ITEMS } from "./pane-action-menu-model.ts";
|
|
4
5
|
export interface FleetPaletteTarget {
|
|
6
|
+
readonly server?: TmuxServerScope;
|
|
5
7
|
readonly machineId: string;
|
|
6
8
|
readonly liveSessionId: string;
|
|
7
9
|
readonly hostLabel: string;
|
|
@@ -51,8 +53,14 @@ export type ApplicationPaletteCommand =
|
|
|
51
53
|
export function applicationCommandDescription(command: ApplicationPaletteCommand) {
|
|
52
54
|
if (typeof command === "object" && command.kind === "open-machine")
|
|
53
55
|
return {
|
|
54
|
-
id: JSON.stringify([
|
|
55
|
-
|
|
56
|
+
id: JSON.stringify([
|
|
57
|
+
command.kind,
|
|
58
|
+
command.fleet.machineId,
|
|
59
|
+
...(command.fleet.server
|
|
60
|
+
? [command.fleet.server.serverId, command.fleet.server.generation]
|
|
61
|
+
: []),
|
|
62
|
+
]),
|
|
63
|
+
label: `${command.fleet.server ? "Server" : "Machine"} · ${command.label}`,
|
|
56
64
|
detail: command.fleet.disabled ? "Unavailable" : "Browse sessions or create on this host",
|
|
57
65
|
};
|
|
58
66
|
if (typeof command === "object") {
|
|
@@ -63,6 +71,9 @@ export function applicationCommandDescription(command: ApplicationPaletteCommand
|
|
|
63
71
|
? [
|
|
64
72
|
command.kind,
|
|
65
73
|
command.fleet.machineId,
|
|
74
|
+
...(command.fleet.server
|
|
75
|
+
? [command.fleet.server.serverId, command.fleet.server.generation]
|
|
76
|
+
: []),
|
|
66
77
|
command.fleet.liveSessionId,
|
|
67
78
|
session ? null : command.paneId,
|
|
68
79
|
]
|