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.
- package/bin/cli.js +46791 -43589
- package/package.json +2 -2
- 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/fleet-lifecycle.ts +5 -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 +351 -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 +31 -11
- 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-guided-tour-integration.jsx +119 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-guided-tour-owner.js +132 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +21 -9
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-transport.js +63 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents-owner.js +8 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agents.js +3 -1
- 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-home-experience.js +40 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +132 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-agents.js +9 -0
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-catalog.js +12 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-navigation.js +211 -31
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +91 -37
- package/packages/daemon/dist/tui/mirror/runtime/application-performance-log.js +20 -7
- 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-shell-home.jsx +6 -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/application-theme-repaint.js +9 -0
- package/packages/daemon/dist/tui/mirror/runtime/fleet-lifecycle-client.js +44 -1
- package/packages/daemon/dist/tui/mirror/runtime/guided-tour-coach.jsx +54 -0
- package/packages/daemon/dist/tui/mirror/runtime/guided-tour-storage.js +67 -0
- package/packages/daemon/dist/tui/mirror/runtime/guided-tour.js +113 -0
- package/packages/daemon/dist/tui/mirror/runtime/open-tui-generation-host.js +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/resize-terminal-pane.js +4 -0
- package/packages/daemon/dist/tui/mirror/runtime/select-terminal-pane.js +2 -1
- package/packages/daemon/dist/tui/mirror/runtime/semantic-shell-viewport-resize.js +12 -4
- 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 +479 -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 +33 -9
- 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-guided-tour-integration.tsx +199 -0
- package/packages/daemon/src/tui/mirror/runtime/application-guided-tour-owner.ts +162 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +36 -10
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-transport.ts +69 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +12 -2
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents.ts +13 -2
- 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-home-experience.ts +59 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +175 -0
- package/packages/daemon/src/tui/mirror/runtime/application-machine-agents.ts +9 -1
- package/packages/daemon/src/tui/mirror/runtime/application-machine-catalog.ts +19 -7
- package/packages/daemon/src/tui/mirror/runtime/application-machine-navigation.ts +285 -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-performance-log.ts +18 -6
- package/packages/daemon/src/tui/mirror/runtime/application-root-configuration.ts +12 -3
- package/packages/daemon/src/tui/mirror/runtime/application-root-v2.tsx +40 -40
- package/packages/daemon/src/tui/mirror/runtime/application-route-connection.ts +93 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-catalog.tsx +4 -0
- package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +20 -1
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +10 -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/application-theme-repaint.ts +19 -0
- package/packages/daemon/src/tui/mirror/runtime/fleet-lifecycle-client.ts +55 -0
- package/packages/daemon/src/tui/mirror/runtime/guided-tour-coach.tsx +118 -0
- package/packages/daemon/src/tui/mirror/runtime/guided-tour-storage.ts +72 -0
- package/packages/daemon/src/tui/mirror/runtime/guided-tour.ts +147 -0
- package/packages/daemon/src/tui/mirror/runtime/open-tui-generation-host.ts +1 -1
- package/packages/daemon/src/tui/mirror/runtime/resize-terminal-pane.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/select-terminal-pane.ts +3 -0
- package/packages/daemon/src/tui/mirror/runtime/semantic-shell-viewport-resize.ts +11 -4
- 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,8 +1,8 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import { createWorkspaceEventSupervisor, } from "@tmux-ide/daemon-client/workspace-event-supervisor";
|
|
1
3
|
import { readApplicationDaemonInfo as readCanonicalDaemonInfo } from "./application-daemon-authority.js";
|
|
2
|
-
import {
|
|
4
|
+
import { TmuxServersResourceSchemaZ, TmuxServerSessionsResourceSchemaZ, } from "@tmux-ide/contracts";
|
|
3
5
|
import { createPushResourceSession, } from "@tmux-ide/daemon-client/push-resource-session";
|
|
4
|
-
import { createWorkspaceEventSupervisor, } from "@tmux-ide/daemon-client/workspace-event-supervisor";
|
|
5
|
-
import WebSocket from "ws";
|
|
6
6
|
import { canonicalDaemonUrl } from "../../../lib/canonical-daemon.js";
|
|
7
7
|
function sameDaemon(left, right) {
|
|
8
8
|
return (left?.protocolVersion === right.protocolVersion &&
|
|
@@ -22,7 +22,7 @@ export function closeApplicationHomeCatalogTransport(subscription, supervisor) {
|
|
|
22
22
|
subscription.close();
|
|
23
23
|
supervisor.dispose();
|
|
24
24
|
}
|
|
25
|
-
function createApplicationHomeCatalogAdapter(
|
|
25
|
+
function createApplicationHomeCatalogAdapter(_onTransportRetired) {
|
|
26
26
|
return {
|
|
27
27
|
validateTarget(value) {
|
|
28
28
|
if (!value ||
|
|
@@ -42,10 +42,9 @@ function createApplicationHomeCatalogAdapter(onTransportRetired) {
|
|
|
42
42
|
};
|
|
43
43
|
},
|
|
44
44
|
async fetch(target, _key, signal) {
|
|
45
|
-
const
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
response = await fetch(url, {
|
|
45
|
+
const base = canonicalDaemonUrl("http", target.daemon.bindHostname, target.daemon.port);
|
|
46
|
+
const read = async (path) => {
|
|
47
|
+
const response = await fetch(base + path, {
|
|
49
48
|
headers: {
|
|
50
49
|
accept: "application/json",
|
|
51
50
|
...(target.daemon.authToken
|
|
@@ -55,75 +54,129 @@ function createApplicationHomeCatalogAdapter(onTransportRetired) {
|
|
|
55
54
|
credentials: "omit",
|
|
56
55
|
cache: "no-store",
|
|
57
56
|
redirect: "error",
|
|
58
|
-
signal,
|
|
57
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(3_000)]),
|
|
59
58
|
});
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
return
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
if (!response.ok)
|
|
60
|
+
throw new Error(`Discovery returned HTTP ${response.status}`);
|
|
61
|
+
return response.json();
|
|
62
|
+
};
|
|
63
|
+
try {
|
|
64
|
+
const inventory = TmuxServersResourceSchemaZ.parse(await read("/api/v1/tmux-servers"));
|
|
65
|
+
const sessions = await Promise.allSettled(inventory.servers.map(async (descriptor) => {
|
|
66
|
+
if (descriptor.state !== "online")
|
|
67
|
+
return [];
|
|
68
|
+
const server = { serverId: descriptor.serverId, generation: descriptor.generation };
|
|
69
|
+
const result = TmuxServerSessionsResourceSchemaZ.parse(await read(`/api/v1/tmux-servers/${server.serverId}/${server.generation}/sessions`));
|
|
70
|
+
if (result.server.serverId !== server.serverId ||
|
|
71
|
+
result.server.generation !== server.generation)
|
|
72
|
+
throw new Error("Server discovery identity changed");
|
|
73
|
+
return result.sessions.map((session) => ({
|
|
74
|
+
id: JSON.stringify([server.serverId, server.generation, session.liveSessionId]),
|
|
75
|
+
server,
|
|
76
|
+
serverLabel: descriptor.label,
|
|
77
|
+
liveSessionId: session.liveSessionId,
|
|
78
|
+
name: session.sessionName,
|
|
79
|
+
...(session.workspaceName ? { workspaceName: session.workspaceName } : {}),
|
|
80
|
+
paneCount: session.paneCount,
|
|
81
|
+
}));
|
|
82
|
+
}));
|
|
68
83
|
return {
|
|
69
|
-
status: "
|
|
70
|
-
|
|
84
|
+
status: "ok",
|
|
85
|
+
resource: {
|
|
86
|
+
kind: "live-catalog",
|
|
87
|
+
daemonInstanceId: target.daemon.instanceId,
|
|
88
|
+
servers: inventory.servers.map((server, index) => sessions[index]?.status === "rejected"
|
|
89
|
+
? {
|
|
90
|
+
serverId: server.serverId,
|
|
91
|
+
label: server.label,
|
|
92
|
+
state: "offline",
|
|
93
|
+
generation: null,
|
|
94
|
+
}
|
|
95
|
+
: server),
|
|
96
|
+
scopedSessions: sessions.flatMap((result) => result.status === "fulfilled" ? result.value : []),
|
|
97
|
+
},
|
|
71
98
|
};
|
|
72
|
-
let body;
|
|
73
|
-
try {
|
|
74
|
-
body = await response.json();
|
|
75
99
|
}
|
|
76
100
|
catch {
|
|
77
101
|
return {
|
|
78
102
|
status: "failed",
|
|
79
|
-
failure: catalogFailure("
|
|
103
|
+
failure: catalogFailure("network", "Server session discovery failed.", true),
|
|
80
104
|
};
|
|
81
105
|
}
|
|
82
|
-
const parsed = WorkspaceCatalogResourceV3SchemaZ.safeParse(body);
|
|
83
|
-
if (!parsed.success || !sameDaemon(target.daemon, parsed.data.daemon))
|
|
84
|
-
return {
|
|
85
|
-
status: "failed",
|
|
86
|
-
failure: catalogFailure("schema", "Live session discovery failed daemon identity validation.", false),
|
|
87
|
-
};
|
|
88
|
-
return { status: "ok", resource: { kind: "live-catalog", value: parsed.data } };
|
|
89
106
|
},
|
|
90
107
|
async connect(target, _interests, handlers, signal) {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
headers: target.daemon.authToken
|
|
94
|
-
? { Authorization: `Bearer ${target.daemon.authToken}` }
|
|
95
|
-
: undefined,
|
|
96
|
-
});
|
|
97
|
-
const supervisor = createWorkspaceEventSupervisor({
|
|
98
|
-
socket,
|
|
99
|
-
daemon: target.daemon,
|
|
100
|
-
workspaceName: target.workspaceName,
|
|
101
|
-
sessionName: target.workspaceName,
|
|
102
|
-
fetchTerminalRuntimeInventory: () => Promise.reject(new Error("Home catalog owns no terminal inventory.")),
|
|
103
|
-
onRetired: onTransportRetired,
|
|
104
|
-
});
|
|
105
|
-
const subscription = supervisor.connectWorkspaceCatalog(() => handlers.invalidate(["live-catalog"]), { terminalFirst: false });
|
|
108
|
+
// Root push is an advisory invalidation only: every refresh still resolves
|
|
109
|
+
// the full scoped inventory. The timer discovers changes on other owners.
|
|
106
110
|
let closed = false;
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
let pending = false;
|
|
112
|
+
let closePush = null;
|
|
113
|
+
const invalidate = () => {
|
|
114
|
+
if (!closed)
|
|
115
|
+
handlers.invalidate(["live-catalog"]);
|
|
116
|
+
};
|
|
117
|
+
const startPush = async () => {
|
|
118
|
+
if (closed || pending || closePush)
|
|
109
119
|
return;
|
|
120
|
+
pending = true;
|
|
121
|
+
const socket = new WebSocket(canonicalDaemonUrl("ws", target.daemon.bindHostname, target.daemon.port, "/ws/events?mode=semantic"), {
|
|
122
|
+
headers: target.daemon.authToken
|
|
123
|
+
? { Authorization: `Bearer ${target.daemon.authToken}` }
|
|
124
|
+
: undefined,
|
|
125
|
+
});
|
|
126
|
+
const supervisor = createWorkspaceEventSupervisor({
|
|
127
|
+
socket,
|
|
128
|
+
daemon: target.daemon,
|
|
129
|
+
workspaceName: target.workspaceName,
|
|
130
|
+
sessionName: target.workspaceName,
|
|
131
|
+
fetchTerminalRuntimeInventory: () => Promise.reject(new Error("Home catalog owns no terminal inventory.")),
|
|
132
|
+
onRetired: () => {
|
|
133
|
+
const cleanup = closePush;
|
|
134
|
+
closePush = null;
|
|
135
|
+
cleanup?.();
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
const subscription = supervisor.connectWorkspaceCatalog(invalidate, {
|
|
139
|
+
terminalFirst: false,
|
|
140
|
+
});
|
|
141
|
+
const cleanup = () => closeApplicationHomeCatalogTransport(subscription, supervisor);
|
|
142
|
+
closePush = cleanup;
|
|
143
|
+
try {
|
|
144
|
+
await subscription.ready;
|
|
145
|
+
}
|
|
146
|
+
catch {
|
|
147
|
+
if (closePush === cleanup)
|
|
148
|
+
closePush = null;
|
|
149
|
+
cleanup();
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
pending = false;
|
|
153
|
+
if (closed) {
|
|
154
|
+
cleanup();
|
|
155
|
+
closePush = null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const timer = setInterval(() => {
|
|
160
|
+
invalidate();
|
|
161
|
+
void startPush().catch(() => {
|
|
162
|
+
pending = false;
|
|
163
|
+
});
|
|
164
|
+
}, 5_000);
|
|
165
|
+
const close = () => {
|
|
110
166
|
closed = true;
|
|
167
|
+
clearInterval(timer);
|
|
111
168
|
signal.removeEventListener("abort", close);
|
|
112
|
-
|
|
113
|
-
|
|
169
|
+
const cleanup = closePush;
|
|
170
|
+
closePush = null;
|
|
171
|
+
cleanup?.();
|
|
114
172
|
};
|
|
115
173
|
signal.addEventListener("abort", close, { once: true });
|
|
116
|
-
|
|
117
|
-
await subscription.ready;
|
|
118
|
-
}
|
|
119
|
-
catch (error) {
|
|
174
|
+
if (signal.aborted)
|
|
120
175
|
close();
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
throw signal.reason;
|
|
126
|
-
}
|
|
176
|
+
else
|
|
177
|
+
void startPush().catch(() => {
|
|
178
|
+
pending = false;
|
|
179
|
+
});
|
|
127
180
|
return { status: "connected", close };
|
|
128
181
|
},
|
|
129
182
|
rejectionFailure: () => catalogFailure("unavailable", "Live session discovery was rejected.", true),
|
|
@@ -145,29 +198,31 @@ const initialSnapshot = () => ({
|
|
|
145
198
|
sessions: [],
|
|
146
199
|
note: "Discovering live tmux sessions…",
|
|
147
200
|
});
|
|
148
|
-
function projectCatalog(resource) {
|
|
201
|
+
function projectCatalog(resource, scopedSessions) {
|
|
149
202
|
return {
|
|
150
203
|
phase: "live",
|
|
151
204
|
daemonInstanceId: resource.daemon.instanceId,
|
|
152
|
-
sessions:
|
|
153
|
-
.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
205
|
+
sessions: scopedSessions ??
|
|
206
|
+
resource.liveSessions
|
|
207
|
+
.map(({ liveSessionId, sessionName, paneCount }) => ({
|
|
208
|
+
id: `${resource.daemon.instanceId}:${liveSessionId}`,
|
|
209
|
+
liveSessionId,
|
|
210
|
+
workspaceName: resource.intents.find((intent) => intent.sessionName === sessionName)
|
|
211
|
+
?.workspaceName,
|
|
212
|
+
name: sessionName,
|
|
213
|
+
paneCount,
|
|
214
|
+
}))
|
|
215
|
+
.sort((left, right) => left.name.localeCompare(right.name)),
|
|
216
|
+
note: (scopedSessions?.length ?? resource.liveSessions.length) === 0
|
|
163
217
|
? "No live tmux sessions yet. This list updates automatically."
|
|
164
218
|
: null,
|
|
165
219
|
};
|
|
166
220
|
}
|
|
167
221
|
/**
|
|
168
|
-
* Daemon-authoritative
|
|
169
|
-
*
|
|
170
|
-
*
|
|
222
|
+
* Daemon-authoritative fleet metadata. Root push preserves immediate updates,
|
|
223
|
+
* while a five-second refresh discovers independent
|
|
224
|
+
* server owners without opening pane streams. Failed reads use bounded backoff;
|
|
225
|
+
* each failed server is excluded without suppressing healthy siblings.
|
|
171
226
|
*/
|
|
172
227
|
export function createApplicationHomeCatalog(overrides = {}) {
|
|
173
228
|
const dependencies = { ...DEFAULT_DEPENDENCIES, ...overrides };
|
|
@@ -253,7 +308,17 @@ export function createApplicationHomeCatalog(overrides = {}) {
|
|
|
253
308
|
if (slot?.status === "loaded" && !slot.refreshing && slot.resource.kind === "live-catalog") {
|
|
254
309
|
clearRetry();
|
|
255
310
|
retryAttempt = 0;
|
|
256
|
-
publish(
|
|
311
|
+
publish(slot.resource.value
|
|
312
|
+
? projectCatalog(slot.resource.value)
|
|
313
|
+
: {
|
|
314
|
+
phase: "live",
|
|
315
|
+
daemonInstanceId: slot.resource.daemonInstanceId ?? null,
|
|
316
|
+
sessions: slot.resource.scopedSessions ?? [],
|
|
317
|
+
servers: slot.resource.servers,
|
|
318
|
+
note: slot.resource.scopedSessions?.length
|
|
319
|
+
? null
|
|
320
|
+
: "No live tmux sessions yet. This list updates automatically.",
|
|
321
|
+
});
|
|
257
322
|
return;
|
|
258
323
|
}
|
|
259
324
|
if (slot?.status === "error") {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { createApplicationHomeFleetOwner } from "./application-home-fleet.js";
|
|
2
|
+
import { createApplicationHomeNavigationOwner } from "./application-home-agents-owner.js";
|
|
3
|
+
import { createApplicationGuidedTourIntegration } from "./application-guided-tour-integration.jsx";
|
|
4
|
+
/** Compose Home's fleet, navigation and learning experience without additional transports. */
|
|
5
|
+
export function createApplicationHomeExperience(options) {
|
|
6
|
+
const { machines, appearance } = options;
|
|
7
|
+
const paletteOpen = () => options.shell().semantic?.focus.palette.open ?? options.shell().localPaletteOpen;
|
|
8
|
+
const fleetHome = createApplicationHomeFleetOwner({
|
|
9
|
+
catalog: machines.catalog,
|
|
10
|
+
agents: machines.agents,
|
|
11
|
+
inputActive: () => options.activeSurface() === "home" &&
|
|
12
|
+
options.rendererFocused() &&
|
|
13
|
+
!options.paletteModalOpen() &&
|
|
14
|
+
!machines.switching() &&
|
|
15
|
+
!machines.adding() &&
|
|
16
|
+
!machines.focused() &&
|
|
17
|
+
!paletteOpen() &&
|
|
18
|
+
!appearance.pickerOpen(),
|
|
19
|
+
open: (_machineId, agent, source) => machines.openHomeAgent(agent, source),
|
|
20
|
+
});
|
|
21
|
+
const navigation = createApplicationHomeNavigationOwner({
|
|
22
|
+
...options,
|
|
23
|
+
fleetHome: fleetHome.presentation,
|
|
24
|
+
fleetCommands: machines.paletteCommands,
|
|
25
|
+
openFleet: machines.openPalette,
|
|
26
|
+
});
|
|
27
|
+
const tour = createApplicationGuidedTourIntegration({
|
|
28
|
+
...options,
|
|
29
|
+
sessionName: () => options.sessionOwner()?.sessionName() ?? null,
|
|
30
|
+
paletteOpen,
|
|
31
|
+
paletteCommands: navigation.paletteCommands,
|
|
32
|
+
blocked: () => options.paletteModalOpen() ||
|
|
33
|
+
!!navigation.paneRename.draft() ||
|
|
34
|
+
paletteOpen() ||
|
|
35
|
+
appearance.pickerOpen() ||
|
|
36
|
+
machines.switching() ||
|
|
37
|
+
machines.adding(),
|
|
38
|
+
});
|
|
39
|
+
return { ...navigation, tour };
|
|
40
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js";
|
|
2
|
+
import { createHomeAgentSelectionOwner } from "./application-home-agent-selection.js";
|
|
3
|
+
export function projectHomeFleet(catalog, groups, filter) {
|
|
4
|
+
const machines = catalog.groups.filter((group) => filter.machineId === null || group.id === filter.machineId);
|
|
5
|
+
const rows = [];
|
|
6
|
+
const unavailable = new Set();
|
|
7
|
+
let observedSessions = 0, totalSessions = 0, loadingSessions = 0, unavailableSessions = 0, truncatedSessions = 0;
|
|
8
|
+
const panes = new Set();
|
|
9
|
+
for (const machine of machines) {
|
|
10
|
+
const group = groups.find((group) => group.machineId === machine.id);
|
|
11
|
+
totalSessions += machine.sessions.length;
|
|
12
|
+
const observation = machine.state === "ready" ? group?.observation : undefined;
|
|
13
|
+
observedSessions +=
|
|
14
|
+
observation?.observedSessions ?? (group?.available ? machine.sessions.length : 0);
|
|
15
|
+
loadingSessions +=
|
|
16
|
+
observation?.loadingSessions ??
|
|
17
|
+
(machine.state === "connecting" ? machine.sessions.length : 0);
|
|
18
|
+
unavailableSessions +=
|
|
19
|
+
observation?.unavailableSessions ??
|
|
20
|
+
(machine.state === "disconnected" ? machine.sessions.length : 0);
|
|
21
|
+
truncatedSessions += observation?.truncatedSessions ?? 0;
|
|
22
|
+
for (const key of observation?.unavailableSessionKeys ?? [])
|
|
23
|
+
unavailable.add(JSON.stringify([machine.id, key]));
|
|
24
|
+
for (const agent of [...(group?.agents ?? [])].sort((a, b) => Number(a.disabled) - Number(b.disabled) || a.id.localeCompare(b.id))) {
|
|
25
|
+
const sessionKey = JSON.stringify([machine.id, agent.sessionKey]);
|
|
26
|
+
if (agent.disabled)
|
|
27
|
+
unavailable.add(sessionKey);
|
|
28
|
+
if (filter.attentionOnly &&
|
|
29
|
+
!(agent.attention || agent.activity === "waiting" || agent.activity === "failed"))
|
|
30
|
+
continue;
|
|
31
|
+
const paneKey = agent.paneId
|
|
32
|
+
? JSON.stringify([machine.id, agent.server?.serverId, agent.daemonInstanceId, agent.paneId])
|
|
33
|
+
: agent.id;
|
|
34
|
+
if (panes.has(paneKey))
|
|
35
|
+
continue;
|
|
36
|
+
panes.add(paneKey);
|
|
37
|
+
rows.push({ ...agent, key: agent.id, sessionKey, machineLabel: machine.label });
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
// Identity order never jumps when an activity signal changes.
|
|
41
|
+
rows.sort((a, b) => a.key.localeCompare(b.key));
|
|
42
|
+
const missing = machines.filter((machine) => machine.state !== "ready" ||
|
|
43
|
+
!groups.find((group) => group.machineId === machine.id)?.available);
|
|
44
|
+
return {
|
|
45
|
+
phase: missing.length
|
|
46
|
+
? rows.length || observedSessions
|
|
47
|
+
? "partial"
|
|
48
|
+
: machines.some((machine) => machine.state === "connecting")
|
|
49
|
+
? "loading"
|
|
50
|
+
: "unavailable"
|
|
51
|
+
: "live",
|
|
52
|
+
rows,
|
|
53
|
+
observedSessions,
|
|
54
|
+
totalSessions,
|
|
55
|
+
loadingSessions,
|
|
56
|
+
unavailableSessions,
|
|
57
|
+
truncatedSessions,
|
|
58
|
+
refreshingSessionKeys: [],
|
|
59
|
+
unavailableSessionKeys: [...unavailable],
|
|
60
|
+
note: missing.length
|
|
61
|
+
? `${missing.map((machine) => `${machine.label}: ${machine.state === "ready" ? "partial observations" : machine.state}`).join(" · ")} · unavailable rows show last observed activity`
|
|
62
|
+
: rows.length === 0 && filter.attentionOnly
|
|
63
|
+
? "No agents need attention in this view."
|
|
64
|
+
: null,
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
/** Reuses resident fleet observations, adding no daemon connections or pane streams. */
|
|
68
|
+
export function createApplicationHomeFleetOwner(options) {
|
|
69
|
+
const [catalog, setCatalog] = createSignal(options.catalog.getSnapshot());
|
|
70
|
+
const [groups, setGroups] = createSignal(options.agents.getSnapshot());
|
|
71
|
+
const [filter, setFilter] = createSignal({
|
|
72
|
+
machineId: null,
|
|
73
|
+
attentionOnly: false,
|
|
74
|
+
});
|
|
75
|
+
const selection = createHomeAgentSelectionOwner();
|
|
76
|
+
const [selected, setSelected] = createSignal(selection.snapshot());
|
|
77
|
+
const snapshot = createMemo(() => projectHomeFleet(catalog(), groups(), filter()));
|
|
78
|
+
const stops = [
|
|
79
|
+
options.catalog.subscribe(setCatalog),
|
|
80
|
+
options.agents.subscribe(setGroups),
|
|
81
|
+
selection.subscribe(setSelected),
|
|
82
|
+
];
|
|
83
|
+
createEffect(() => selection.setRows(snapshot().rows));
|
|
84
|
+
createEffect(() => {
|
|
85
|
+
const id = filter().machineId;
|
|
86
|
+
if (id && !catalog().groups.some((group) => group.id === id))
|
|
87
|
+
setFilter((value) => ({ ...value, machineId: null }));
|
|
88
|
+
});
|
|
89
|
+
onCleanup(() => {
|
|
90
|
+
for (const stop of stops)
|
|
91
|
+
stop();
|
|
92
|
+
selection.dispose();
|
|
93
|
+
});
|
|
94
|
+
const presentation = {
|
|
95
|
+
get agentRoster() {
|
|
96
|
+
return snapshot();
|
|
97
|
+
},
|
|
98
|
+
get agentSelection() {
|
|
99
|
+
return selected();
|
|
100
|
+
},
|
|
101
|
+
get agentInputActive() {
|
|
102
|
+
return options.inputActive();
|
|
103
|
+
},
|
|
104
|
+
get agentFilterLabel() {
|
|
105
|
+
return `${catalog().groups.find((group) => group.id === filter().machineId)?.label ?? "All machines"} · ${filter().attentionOnly ? "Needs attention" : "All agents"}`;
|
|
106
|
+
},
|
|
107
|
+
onCycleAgentMachine() {
|
|
108
|
+
const ids = [null, ...catalog().groups.map((group) => group.id)];
|
|
109
|
+
setFilter((value) => ({
|
|
110
|
+
...value,
|
|
111
|
+
machineId: ids[(ids.indexOf(value.machineId) + 1) % ids.length],
|
|
112
|
+
}));
|
|
113
|
+
},
|
|
114
|
+
onToggleAgentAttention() {
|
|
115
|
+
setFilter((value) => ({ ...value, attentionOnly: !value.attentionOnly }));
|
|
116
|
+
},
|
|
117
|
+
onRetryAgents: () => options.agents.retry?.(),
|
|
118
|
+
onLoadMoreAgents: () => options.agents.loadMore?.(),
|
|
119
|
+
onSelectAgent: selection.select,
|
|
120
|
+
onMoveAgent: selection.move,
|
|
121
|
+
onAgentViewport: selection.setViewport,
|
|
122
|
+
onOpenAgent(row, source) {
|
|
123
|
+
if (!options.inputActive())
|
|
124
|
+
return;
|
|
125
|
+
const group = groups().find((group) => group.machineId === row.machineId);
|
|
126
|
+
const agent = group?.agents.find((agent) => agent.id === row.key);
|
|
127
|
+
if (agent && !agent.disabled && agent.paneId)
|
|
128
|
+
void options.open(agent.machineId, agent, source);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
return { presentation };
|
|
132
|
+
}
|
|
@@ -34,6 +34,7 @@ export function createApplicationMachineAgents(options) {
|
|
|
34
34
|
const current = !!entry && entry.binding === bindingFor(entry.handle);
|
|
35
35
|
return Object.freeze({
|
|
36
36
|
machineId: group.id,
|
|
37
|
+
observation: entry?.observer?.getSnapshot(),
|
|
37
38
|
available: group.state === "ready" && current && entry?.observer?.getSnapshot().phase === "live",
|
|
38
39
|
agents: Object.freeze((entry?.rows ?? []).map((row) => Object.freeze({
|
|
39
40
|
...row,
|
|
@@ -139,6 +140,14 @@ export function createApplicationMachineAgents(options) {
|
|
|
139
140
|
entry.binding === bindingFor(entry.handle) &&
|
|
140
141
|
!!entry.observer?.isCurrentTarget(row));
|
|
141
142
|
},
|
|
143
|
+
retry() {
|
|
144
|
+
for (const entry of entries.values())
|
|
145
|
+
entry.observer?.retry();
|
|
146
|
+
},
|
|
147
|
+
loadMore() {
|
|
148
|
+
for (const entry of entries.values())
|
|
149
|
+
entry.observer?.loadMore();
|
|
150
|
+
},
|
|
142
151
|
start() {
|
|
143
152
|
if (started || disposed)
|
|
144
153
|
return;
|
|
@@ -53,6 +53,7 @@ export function createApplicationMachineCatalog(options = {}) {
|
|
|
53
53
|
note: diagnostic && diagnostic.phase !== "ready"
|
|
54
54
|
? fleetConnectionMessage(diagnostic)
|
|
55
55
|
: (entry?.snapshot.note ?? null),
|
|
56
|
+
servers: entry?.snapshot.servers,
|
|
56
57
|
sessions: (entry?.lastSessions ?? []).map((session) => ({
|
|
57
58
|
...session,
|
|
58
59
|
sourceId: session.id,
|
|
@@ -86,7 +87,13 @@ export function createApplicationMachineCatalog(options = {}) {
|
|
|
86
87
|
sessions: group.sessions.map((session) => ({
|
|
87
88
|
...session,
|
|
88
89
|
id: session.liveSessionId && joined.environmentId && !joined.conflict
|
|
89
|
-
? JSON.stringify([
|
|
90
|
+
? JSON.stringify([
|
|
91
|
+
joined.environmentId,
|
|
92
|
+
"session",
|
|
93
|
+
session.server?.serverId,
|
|
94
|
+
session.server?.generation,
|
|
95
|
+
session.liveSessionId,
|
|
96
|
+
])
|
|
90
97
|
: session.id,
|
|
91
98
|
})),
|
|
92
99
|
};
|
|
@@ -152,11 +159,14 @@ export function createApplicationMachineCatalog(options = {}) {
|
|
|
152
159
|
environmentId: entry.environmentId,
|
|
153
160
|
generation: entry.generation,
|
|
154
161
|
seenAt: entry.seenAt,
|
|
155
|
-
sessions: value.sessions
|
|
162
|
+
sessions: value.sessions
|
|
163
|
+
.slice(0, 64)
|
|
164
|
+
.map(({ id, liveSessionId, name, paneCount, server, serverLabel }) => ({
|
|
156
165
|
id,
|
|
157
166
|
...(liveSessionId ? { liveSessionId } : {}),
|
|
158
167
|
name,
|
|
159
168
|
paneCount,
|
|
169
|
+
...(server ? { server, serverLabel } : {}),
|
|
160
170
|
})),
|
|
161
171
|
});
|
|
162
172
|
}
|