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,6 +1,14 @@
|
|
|
1
|
+
import WebSocket from "ws";
|
|
2
|
+
import {
|
|
3
|
+
createWorkspaceEventSupervisor,
|
|
4
|
+
type WorkspaceEventSocket,
|
|
5
|
+
} from "@tmux-ide/daemon-client/workspace-event-supervisor";
|
|
1
6
|
import { readApplicationDaemonInfo as readCanonicalDaemonInfo } from "./application-daemon-authority.ts";
|
|
2
7
|
import {
|
|
3
|
-
|
|
8
|
+
TmuxServersResourceSchemaZ,
|
|
9
|
+
TmuxServerSessionsResourceSchemaZ,
|
|
10
|
+
type TmuxServerScope,
|
|
11
|
+
type TmuxServerDescriptor,
|
|
4
12
|
type CanonicalDaemonInfo,
|
|
5
13
|
type WorkspaceCatalogResourceV3,
|
|
6
14
|
} from "@tmux-ide/contracts";
|
|
@@ -9,18 +17,16 @@ import {
|
|
|
9
17
|
type PushResourceSessionAdapter,
|
|
10
18
|
type PushResourceSessionOptions,
|
|
11
19
|
} from "@tmux-ide/daemon-client/push-resource-session";
|
|
12
|
-
import {
|
|
13
|
-
createWorkspaceEventSupervisor,
|
|
14
|
-
type WorkspaceEventSocket,
|
|
15
|
-
} from "@tmux-ide/daemon-client/workspace-event-supervisor";
|
|
16
|
-
import WebSocket from "ws";
|
|
17
20
|
|
|
18
21
|
import { canonicalDaemonUrl } from "../../../lib/canonical-daemon.ts";
|
|
19
22
|
|
|
20
23
|
export type ApplicationHomeCatalogResourceKey = "live-catalog";
|
|
21
24
|
export type ApplicationHomeCatalogResource = {
|
|
22
25
|
readonly kind: "live-catalog";
|
|
23
|
-
readonly value
|
|
26
|
+
readonly value?: WorkspaceCatalogResourceV3;
|
|
27
|
+
readonly daemonInstanceId?: string;
|
|
28
|
+
readonly servers?: readonly TmuxServerDescriptor[];
|
|
29
|
+
readonly scopedSessions?: readonly ApplicationHomeCatalogSession[];
|
|
24
30
|
};
|
|
25
31
|
export interface ApplicationHomeCatalogTarget {
|
|
26
32
|
readonly daemon: CanonicalDaemonInfo;
|
|
@@ -35,6 +41,8 @@ export interface ApplicationHomeCatalogFailure {
|
|
|
35
41
|
|
|
36
42
|
export interface ApplicationHomeCatalogSession {
|
|
37
43
|
readonly id: string;
|
|
44
|
+
readonly server?: TmuxServerScope;
|
|
45
|
+
readonly serverLabel?: string;
|
|
38
46
|
readonly liveSessionId?: string;
|
|
39
47
|
readonly workspaceName?: string;
|
|
40
48
|
readonly name: string;
|
|
@@ -45,6 +53,7 @@ export interface ApplicationHomeCatalogSnapshot {
|
|
|
45
53
|
readonly phase: "loading" | "live" | "unavailable";
|
|
46
54
|
readonly daemonInstanceId: string | null;
|
|
47
55
|
readonly sessions: readonly ApplicationHomeCatalogSession[];
|
|
56
|
+
readonly servers?: readonly TmuxServerDescriptor[];
|
|
48
57
|
readonly note: string | null;
|
|
49
58
|
}
|
|
50
59
|
|
|
@@ -117,7 +126,7 @@ export function closeApplicationHomeCatalogTransport(
|
|
|
117
126
|
supervisor.dispose();
|
|
118
127
|
}
|
|
119
128
|
|
|
120
|
-
function createApplicationHomeCatalogAdapter(
|
|
129
|
+
function createApplicationHomeCatalogAdapter(_onTransportRetired: () => void): HomeCatalogAdapter {
|
|
121
130
|
return {
|
|
122
131
|
validateTarget(value) {
|
|
123
132
|
if (
|
|
@@ -139,15 +148,9 @@ function createApplicationHomeCatalogAdapter(onTransportRetired: () => void): Ho
|
|
|
139
148
|
};
|
|
140
149
|
},
|
|
141
150
|
async fetch(target, _key, signal) {
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
target.daemon.port,
|
|
146
|
-
"/api/resources/workspace-catalog?version=3",
|
|
147
|
-
);
|
|
148
|
-
let response: Response;
|
|
149
|
-
try {
|
|
150
|
-
response = await fetch(url, {
|
|
151
|
+
const base = canonicalDaemonUrl("http", target.daemon.bindHostname, target.daemon.port);
|
|
152
|
+
const read = async (path: string) => {
|
|
153
|
+
const response = await fetch(base + path, {
|
|
151
154
|
headers: {
|
|
152
155
|
accept: "application/json",
|
|
153
156
|
...(target.daemon.authToken
|
|
@@ -157,88 +160,139 @@ function createApplicationHomeCatalogAdapter(onTransportRetired: () => void): Ho
|
|
|
157
160
|
credentials: "omit",
|
|
158
161
|
cache: "no-store",
|
|
159
162
|
redirect: "error",
|
|
160
|
-
signal,
|
|
163
|
+
signal: AbortSignal.any([signal, AbortSignal.timeout(3_000)]),
|
|
161
164
|
});
|
|
162
|
-
|
|
163
|
-
return
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
165
|
+
if (!response.ok) throw new Error(`Discovery returned HTTP ${response.status}`);
|
|
166
|
+
return response.json();
|
|
167
|
+
};
|
|
168
|
+
try {
|
|
169
|
+
const inventory = TmuxServersResourceSchemaZ.parse(await read("/api/v1/tmux-servers"));
|
|
170
|
+
const sessions = await Promise.allSettled(
|
|
171
|
+
inventory.servers.map(async (descriptor) => {
|
|
172
|
+
if (descriptor.state !== "online") return [];
|
|
173
|
+
const server = { serverId: descriptor.serverId, generation: descriptor.generation };
|
|
174
|
+
const result = TmuxServerSessionsResourceSchemaZ.parse(
|
|
175
|
+
await read(`/api/v1/tmux-servers/${server.serverId}/${server.generation}/sessions`),
|
|
176
|
+
);
|
|
177
|
+
if (
|
|
178
|
+
result.server.serverId !== server.serverId ||
|
|
179
|
+
result.server.generation !== server.generation
|
|
180
|
+
)
|
|
181
|
+
throw new Error("Server discovery identity changed");
|
|
182
|
+
return result.sessions.map((session) => ({
|
|
183
|
+
id: JSON.stringify([server.serverId, server.generation, session.liveSessionId]),
|
|
184
|
+
server,
|
|
185
|
+
serverLabel: descriptor.label,
|
|
186
|
+
liveSessionId: session.liveSessionId,
|
|
187
|
+
name: session.sessionName,
|
|
188
|
+
...(session.workspaceName ? { workspaceName: session.workspaceName } : {}),
|
|
189
|
+
paneCount: session.paneCount,
|
|
190
|
+
}));
|
|
191
|
+
}),
|
|
192
|
+
);
|
|
169
193
|
return {
|
|
170
|
-
status: "
|
|
171
|
-
|
|
172
|
-
"
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
194
|
+
status: "ok",
|
|
195
|
+
resource: {
|
|
196
|
+
kind: "live-catalog",
|
|
197
|
+
daemonInstanceId: target.daemon.instanceId,
|
|
198
|
+
servers: inventory.servers.map((server, index) =>
|
|
199
|
+
sessions[index]?.status === "rejected"
|
|
200
|
+
? {
|
|
201
|
+
serverId: server.serverId,
|
|
202
|
+
label: server.label,
|
|
203
|
+
state: "offline" as const,
|
|
204
|
+
generation: null,
|
|
205
|
+
}
|
|
206
|
+
: server,
|
|
207
|
+
),
|
|
208
|
+
scopedSessions: sessions.flatMap((result) =>
|
|
209
|
+
result.status === "fulfilled" ? result.value : [],
|
|
210
|
+
),
|
|
211
|
+
},
|
|
176
212
|
};
|
|
177
|
-
let body: unknown;
|
|
178
|
-
try {
|
|
179
|
-
body = await response.json();
|
|
180
213
|
} catch {
|
|
181
214
|
return {
|
|
182
215
|
status: "failed",
|
|
183
|
-
failure: catalogFailure("
|
|
216
|
+
failure: catalogFailure("network", "Server session discovery failed.", true),
|
|
184
217
|
};
|
|
185
218
|
}
|
|
186
|
-
const parsed = WorkspaceCatalogResourceV3SchemaZ.safeParse(body);
|
|
187
|
-
if (!parsed.success || !sameDaemon(target.daemon, parsed.data.daemon))
|
|
188
|
-
return {
|
|
189
|
-
status: "failed",
|
|
190
|
-
failure: catalogFailure(
|
|
191
|
-
"schema",
|
|
192
|
-
"Live session discovery failed daemon identity validation.",
|
|
193
|
-
false,
|
|
194
|
-
),
|
|
195
|
-
};
|
|
196
|
-
return { status: "ok", resource: { kind: "live-catalog", value: parsed.data } };
|
|
197
219
|
},
|
|
198
220
|
async connect(target, _interests, handlers, signal) {
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
target.daemon.bindHostname,
|
|
202
|
-
target.daemon.port,
|
|
203
|
-
"/ws/events?mode=semantic",
|
|
204
|
-
);
|
|
205
|
-
const socket = new WebSocket(socketUrl, {
|
|
206
|
-
headers: target.daemon.authToken
|
|
207
|
-
? { Authorization: `Bearer ${target.daemon.authToken}` }
|
|
208
|
-
: undefined,
|
|
209
|
-
}) as unknown as WorkspaceEventSocket;
|
|
210
|
-
const supervisor = createWorkspaceEventSupervisor({
|
|
211
|
-
socket,
|
|
212
|
-
daemon: target.daemon,
|
|
213
|
-
workspaceName: target.workspaceName,
|
|
214
|
-
sessionName: target.workspaceName,
|
|
215
|
-
fetchTerminalRuntimeInventory: () =>
|
|
216
|
-
Promise.reject(new Error("Home catalog owns no terminal inventory.")),
|
|
217
|
-
onRetired: onTransportRetired,
|
|
218
|
-
});
|
|
219
|
-
const subscription = supervisor.connectWorkspaceCatalog(
|
|
220
|
-
() => handlers.invalidate(["live-catalog"]),
|
|
221
|
-
{ terminalFirst: false },
|
|
222
|
-
);
|
|
221
|
+
// Root push is an advisory invalidation only: every refresh still resolves
|
|
222
|
+
// the full scoped inventory. The timer discovers changes on other owners.
|
|
223
223
|
let closed = false;
|
|
224
|
-
|
|
225
|
-
|
|
224
|
+
let pending = false;
|
|
225
|
+
let closePush: (() => void) | null = null;
|
|
226
|
+
const invalidate = () => {
|
|
227
|
+
if (!closed) handlers.invalidate(["live-catalog"]);
|
|
228
|
+
};
|
|
229
|
+
const startPush = async () => {
|
|
230
|
+
if (closed || pending || closePush) return;
|
|
231
|
+
pending = true;
|
|
232
|
+
const socket = new WebSocket(
|
|
233
|
+
canonicalDaemonUrl(
|
|
234
|
+
"ws",
|
|
235
|
+
target.daemon.bindHostname,
|
|
236
|
+
target.daemon.port,
|
|
237
|
+
"/ws/events?mode=semantic",
|
|
238
|
+
),
|
|
239
|
+
{
|
|
240
|
+
headers: target.daemon.authToken
|
|
241
|
+
? { Authorization: `Bearer ${target.daemon.authToken}` }
|
|
242
|
+
: undefined,
|
|
243
|
+
},
|
|
244
|
+
) as unknown as WorkspaceEventSocket;
|
|
245
|
+
const supervisor = createWorkspaceEventSupervisor({
|
|
246
|
+
socket,
|
|
247
|
+
daemon: target.daemon,
|
|
248
|
+
workspaceName: target.workspaceName,
|
|
249
|
+
sessionName: target.workspaceName,
|
|
250
|
+
fetchTerminalRuntimeInventory: () =>
|
|
251
|
+
Promise.reject(new Error("Home catalog owns no terminal inventory.")),
|
|
252
|
+
onRetired: () => {
|
|
253
|
+
const cleanup = closePush;
|
|
254
|
+
closePush = null;
|
|
255
|
+
cleanup?.();
|
|
256
|
+
},
|
|
257
|
+
});
|
|
258
|
+
const subscription = supervisor.connectWorkspaceCatalog(invalidate, {
|
|
259
|
+
terminalFirst: false,
|
|
260
|
+
});
|
|
261
|
+
const cleanup = () => closeApplicationHomeCatalogTransport(subscription, supervisor);
|
|
262
|
+
closePush = cleanup;
|
|
263
|
+
try {
|
|
264
|
+
await subscription.ready;
|
|
265
|
+
} catch {
|
|
266
|
+
if (closePush === cleanup) closePush = null;
|
|
267
|
+
cleanup();
|
|
268
|
+
} finally {
|
|
269
|
+
pending = false;
|
|
270
|
+
if (closed) {
|
|
271
|
+
cleanup();
|
|
272
|
+
closePush = null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
const timer = setInterval(() => {
|
|
277
|
+
invalidate();
|
|
278
|
+
void startPush().catch(() => {
|
|
279
|
+
pending = false;
|
|
280
|
+
});
|
|
281
|
+
}, 5_000);
|
|
282
|
+
const close = () => {
|
|
226
283
|
closed = true;
|
|
284
|
+
clearInterval(timer);
|
|
227
285
|
signal.removeEventListener("abort", close);
|
|
228
|
-
|
|
229
|
-
|
|
286
|
+
const cleanup = closePush;
|
|
287
|
+
closePush = null;
|
|
288
|
+
cleanup?.();
|
|
230
289
|
};
|
|
231
290
|
signal.addEventListener("abort", close, { once: true });
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
}
|
|
238
|
-
if (signal.aborted) {
|
|
239
|
-
close();
|
|
240
|
-
throw signal.reason;
|
|
241
|
-
}
|
|
291
|
+
if (signal.aborted) close();
|
|
292
|
+
else
|
|
293
|
+
void startPush().catch(() => {
|
|
294
|
+
pending = false;
|
|
295
|
+
});
|
|
242
296
|
return { status: "connected", close };
|
|
243
297
|
},
|
|
244
298
|
rejectionFailure: () =>
|
|
@@ -264,31 +318,37 @@ const initialSnapshot = (): ApplicationHomeCatalogSnapshot => ({
|
|
|
264
318
|
note: "Discovering live tmux sessions…",
|
|
265
319
|
});
|
|
266
320
|
|
|
267
|
-
function projectCatalog(
|
|
321
|
+
function projectCatalog(
|
|
322
|
+
resource: WorkspaceCatalogResourceV3,
|
|
323
|
+
scopedSessions?: readonly ApplicationHomeCatalogSession[],
|
|
324
|
+
): ApplicationHomeCatalogSnapshot {
|
|
268
325
|
return {
|
|
269
326
|
phase: "live",
|
|
270
327
|
daemonInstanceId: resource.daemon.instanceId,
|
|
271
|
-
sessions:
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
liveSessionId,
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
328
|
+
sessions:
|
|
329
|
+
scopedSessions ??
|
|
330
|
+
resource.liveSessions
|
|
331
|
+
.map(({ liveSessionId, sessionName, paneCount }) => ({
|
|
332
|
+
id: `${resource.daemon.instanceId}:${liveSessionId}`,
|
|
333
|
+
liveSessionId,
|
|
334
|
+
workspaceName: resource.intents.find((intent) => intent.sessionName === sessionName)
|
|
335
|
+
?.workspaceName,
|
|
336
|
+
name: sessionName,
|
|
337
|
+
paneCount,
|
|
338
|
+
}))
|
|
339
|
+
.sort((left, right) => left.name.localeCompare(right.name)),
|
|
281
340
|
note:
|
|
282
|
-
resource.liveSessions.length === 0
|
|
341
|
+
(scopedSessions?.length ?? resource.liveSessions.length) === 0
|
|
283
342
|
? "No live tmux sessions yet. This list updates automatically."
|
|
284
343
|
: null,
|
|
285
344
|
};
|
|
286
345
|
}
|
|
287
346
|
|
|
288
347
|
/**
|
|
289
|
-
* Daemon-authoritative
|
|
290
|
-
*
|
|
291
|
-
*
|
|
348
|
+
* Daemon-authoritative fleet metadata. Root push preserves immediate updates,
|
|
349
|
+
* while a five-second refresh discovers independent
|
|
350
|
+
* server owners without opening pane streams. Failed reads use bounded backoff;
|
|
351
|
+
* each failed server is excluded without suppressing healthy siblings.
|
|
292
352
|
*/
|
|
293
353
|
export function createApplicationHomeCatalog(
|
|
294
354
|
overrides: Partial<ApplicationHomeCatalogDependencies> = {},
|
|
@@ -373,7 +433,19 @@ export function createApplicationHomeCatalog(
|
|
|
373
433
|
if (slot?.status === "loaded" && !slot.refreshing && slot.resource.kind === "live-catalog") {
|
|
374
434
|
clearRetry();
|
|
375
435
|
retryAttempt = 0;
|
|
376
|
-
publish(
|
|
436
|
+
publish(
|
|
437
|
+
slot.resource.value
|
|
438
|
+
? projectCatalog(slot.resource.value)
|
|
439
|
+
: {
|
|
440
|
+
phase: "live",
|
|
441
|
+
daemonInstanceId: slot.resource.daemonInstanceId ?? null,
|
|
442
|
+
sessions: slot.resource.scopedSessions ?? [],
|
|
443
|
+
servers: slot.resource.servers,
|
|
444
|
+
note: slot.resource.scopedSessions?.length
|
|
445
|
+
? null
|
|
446
|
+
: "No live tmux sessions yet. This list updates automatically.",
|
|
447
|
+
},
|
|
448
|
+
);
|
|
377
449
|
return;
|
|
378
450
|
}
|
|
379
451
|
if (slot?.status === "error") {
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Accessor } from "solid-js";
|
|
2
|
+
import { createApplicationHomeFleetOwner } from "./application-home-fleet.ts";
|
|
3
|
+
import { createApplicationHomeNavigationOwner } from "./application-home-agents-owner.ts";
|
|
4
|
+
import { createApplicationGuidedTourIntegration } from "./application-guided-tour-integration.tsx";
|
|
5
|
+
|
|
6
|
+
type NavigationOptions = Parameters<typeof createApplicationHomeNavigationOwner>[0];
|
|
7
|
+
type TourOptions = Parameters<typeof createApplicationGuidedTourIntegration>[0];
|
|
8
|
+
/** Compose Home's fleet, navigation and learning experience without additional transports. */
|
|
9
|
+
export function createApplicationHomeExperience(
|
|
10
|
+
options: Omit<NavigationOptions, "fleetHome" | "fleetCommands" | "openFleet"> &
|
|
11
|
+
Pick<
|
|
12
|
+
TourOptions,
|
|
13
|
+
| "machines"
|
|
14
|
+
| "lifecycle"
|
|
15
|
+
| "generation"
|
|
16
|
+
| "generationMachineId"
|
|
17
|
+
| "layoutSnapshot"
|
|
18
|
+
| "appearance"
|
|
19
|
+
| "dimensions"
|
|
20
|
+
> & { paletteModalOpen: Accessor<boolean> },
|
|
21
|
+
) {
|
|
22
|
+
const { machines, appearance } = options;
|
|
23
|
+
const paletteOpen = () =>
|
|
24
|
+
options.shell().semantic?.focus.palette.open ?? options.shell().localPaletteOpen;
|
|
25
|
+
const fleetHome = createApplicationHomeFleetOwner({
|
|
26
|
+
catalog: machines.catalog,
|
|
27
|
+
agents: machines.agents,
|
|
28
|
+
inputActive: () =>
|
|
29
|
+
options.activeSurface() === "home" &&
|
|
30
|
+
options.rendererFocused() &&
|
|
31
|
+
!options.paletteModalOpen() &&
|
|
32
|
+
!machines.switching() &&
|
|
33
|
+
!machines.adding() &&
|
|
34
|
+
!machines.focused() &&
|
|
35
|
+
!paletteOpen() &&
|
|
36
|
+
!appearance.pickerOpen(),
|
|
37
|
+
open: (_machineId, agent, source) => machines.openHomeAgent(agent, source),
|
|
38
|
+
});
|
|
39
|
+
const navigation = createApplicationHomeNavigationOwner({
|
|
40
|
+
...options,
|
|
41
|
+
fleetHome: fleetHome.presentation,
|
|
42
|
+
fleetCommands: machines.paletteCommands,
|
|
43
|
+
openFleet: machines.openPalette,
|
|
44
|
+
});
|
|
45
|
+
const tour = createApplicationGuidedTourIntegration({
|
|
46
|
+
...options,
|
|
47
|
+
sessionName: () => options.sessionOwner()?.sessionName() ?? null,
|
|
48
|
+
paletteOpen,
|
|
49
|
+
paletteCommands: navigation.paletteCommands,
|
|
50
|
+
blocked: () =>
|
|
51
|
+
options.paletteModalOpen() ||
|
|
52
|
+
!!navigation.paneRename.draft() ||
|
|
53
|
+
paletteOpen() ||
|
|
54
|
+
appearance.pickerOpen() ||
|
|
55
|
+
machines.switching() ||
|
|
56
|
+
machines.adding(),
|
|
57
|
+
});
|
|
58
|
+
return { ...navigation, tour };
|
|
59
|
+
}
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { createEffect, createMemo, createSignal, onCleanup } from "solid-js";
|
|
2
|
+
import type {
|
|
3
|
+
ApplicationMachineAgent,
|
|
4
|
+
ApplicationMachineAgentGroup,
|
|
5
|
+
} from "./application-machine-agents.ts";
|
|
6
|
+
import type { ApplicationMachineCatalogSnapshot } from "./application-machine-catalog.ts";
|
|
7
|
+
import type { HomeAgentRow, HomeAgentSnapshot } from "./application-home-agents.ts";
|
|
8
|
+
import { createHomeAgentSelectionOwner } from "./application-home-agent-selection.ts";
|
|
9
|
+
import type { ApplicationHomeAgentPresentation } from "./application-home-agents-owner.ts";
|
|
10
|
+
|
|
11
|
+
export interface HomeFleetFilter {
|
|
12
|
+
readonly machineId: string | null;
|
|
13
|
+
readonly attentionOnly: boolean;
|
|
14
|
+
}
|
|
15
|
+
export function projectHomeFleet(
|
|
16
|
+
catalog: ApplicationMachineCatalogSnapshot,
|
|
17
|
+
groups: readonly ApplicationMachineAgentGroup[],
|
|
18
|
+
filter: HomeFleetFilter,
|
|
19
|
+
): HomeAgentSnapshot {
|
|
20
|
+
const machines = catalog.groups.filter(
|
|
21
|
+
(group) => filter.machineId === null || group.id === filter.machineId,
|
|
22
|
+
);
|
|
23
|
+
const rows: HomeAgentRow[] = [];
|
|
24
|
+
const unavailable = new Set<string>();
|
|
25
|
+
let observedSessions = 0,
|
|
26
|
+
totalSessions = 0,
|
|
27
|
+
loadingSessions = 0,
|
|
28
|
+
unavailableSessions = 0,
|
|
29
|
+
truncatedSessions = 0;
|
|
30
|
+
const panes = new Set<string>();
|
|
31
|
+
for (const machine of machines) {
|
|
32
|
+
const group = groups.find((group) => group.machineId === machine.id);
|
|
33
|
+
totalSessions += machine.sessions.length;
|
|
34
|
+
const observation = machine.state === "ready" ? group?.observation : undefined;
|
|
35
|
+
observedSessions +=
|
|
36
|
+
observation?.observedSessions ?? (group?.available ? machine.sessions.length : 0);
|
|
37
|
+
loadingSessions +=
|
|
38
|
+
observation?.loadingSessions ??
|
|
39
|
+
(machine.state === "connecting" ? machine.sessions.length : 0);
|
|
40
|
+
unavailableSessions +=
|
|
41
|
+
observation?.unavailableSessions ??
|
|
42
|
+
(machine.state === "disconnected" ? machine.sessions.length : 0);
|
|
43
|
+
truncatedSessions += observation?.truncatedSessions ?? 0;
|
|
44
|
+
for (const key of observation?.unavailableSessionKeys ?? [])
|
|
45
|
+
unavailable.add(JSON.stringify([machine.id, key]));
|
|
46
|
+
for (const agent of [...(group?.agents ?? [])].sort(
|
|
47
|
+
(a, b) => Number(a.disabled) - Number(b.disabled) || a.id.localeCompare(b.id),
|
|
48
|
+
)) {
|
|
49
|
+
const sessionKey = JSON.stringify([machine.id, agent.sessionKey]);
|
|
50
|
+
if (agent.disabled) unavailable.add(sessionKey);
|
|
51
|
+
if (
|
|
52
|
+
filter.attentionOnly &&
|
|
53
|
+
!(agent.attention || agent.activity === "waiting" || agent.activity === "failed")
|
|
54
|
+
)
|
|
55
|
+
continue;
|
|
56
|
+
const paneKey = agent.paneId
|
|
57
|
+
? JSON.stringify([machine.id, agent.server?.serverId, agent.daemonInstanceId, agent.paneId])
|
|
58
|
+
: agent.id;
|
|
59
|
+
if (panes.has(paneKey)) continue;
|
|
60
|
+
panes.add(paneKey);
|
|
61
|
+
rows.push({ ...agent, key: agent.id, sessionKey, machineLabel: machine.label });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
// Identity order never jumps when an activity signal changes.
|
|
65
|
+
rows.sort((a, b) => a.key.localeCompare(b.key));
|
|
66
|
+
const missing = machines.filter(
|
|
67
|
+
(machine) =>
|
|
68
|
+
machine.state !== "ready" ||
|
|
69
|
+
!groups.find((group) => group.machineId === machine.id)?.available,
|
|
70
|
+
);
|
|
71
|
+
return {
|
|
72
|
+
phase: missing.length
|
|
73
|
+
? rows.length || observedSessions
|
|
74
|
+
? "partial"
|
|
75
|
+
: machines.some((machine) => machine.state === "connecting")
|
|
76
|
+
? "loading"
|
|
77
|
+
: "unavailable"
|
|
78
|
+
: "live",
|
|
79
|
+
rows,
|
|
80
|
+
observedSessions,
|
|
81
|
+
totalSessions,
|
|
82
|
+
loadingSessions,
|
|
83
|
+
unavailableSessions,
|
|
84
|
+
truncatedSessions,
|
|
85
|
+
refreshingSessionKeys: [],
|
|
86
|
+
unavailableSessionKeys: [...unavailable],
|
|
87
|
+
note: missing.length
|
|
88
|
+
? `${missing.map((machine) => `${machine.label}: ${machine.state === "ready" ? "partial observations" : machine.state}`).join(" · ")} · unavailable rows show last observed activity`
|
|
89
|
+
: rows.length === 0 && filter.attentionOnly
|
|
90
|
+
? "No agents need attention in this view."
|
|
91
|
+
: null,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Reuses resident fleet observations, adding no daemon connections or pane streams. */
|
|
96
|
+
export function createApplicationHomeFleetOwner(options: {
|
|
97
|
+
readonly catalog: {
|
|
98
|
+
getSnapshot(): ApplicationMachineCatalogSnapshot;
|
|
99
|
+
subscribe(listener: (value: ApplicationMachineCatalogSnapshot) => void): () => void;
|
|
100
|
+
};
|
|
101
|
+
readonly agents: {
|
|
102
|
+
retry?(): void;
|
|
103
|
+
loadMore?(): void;
|
|
104
|
+
getSnapshot(): readonly ApplicationMachineAgentGroup[];
|
|
105
|
+
subscribe(listener: (value: readonly ApplicationMachineAgentGroup[]) => void): () => void;
|
|
106
|
+
};
|
|
107
|
+
readonly inputActive: () => boolean;
|
|
108
|
+
readonly open: (
|
|
109
|
+
machineId: string,
|
|
110
|
+
agent: ApplicationMachineAgent,
|
|
111
|
+
source: "keyboard" | "mouse",
|
|
112
|
+
) => Promise<unknown> | void;
|
|
113
|
+
}) {
|
|
114
|
+
const [catalog, setCatalog] = createSignal(options.catalog.getSnapshot());
|
|
115
|
+
const [groups, setGroups] = createSignal(options.agents.getSnapshot());
|
|
116
|
+
const [filter, setFilter] = createSignal<HomeFleetFilter>({
|
|
117
|
+
machineId: null,
|
|
118
|
+
attentionOnly: false,
|
|
119
|
+
});
|
|
120
|
+
const selection = createHomeAgentSelectionOwner();
|
|
121
|
+
const [selected, setSelected] = createSignal(selection.snapshot());
|
|
122
|
+
const snapshot = createMemo(() => projectHomeFleet(catalog(), groups(), filter()));
|
|
123
|
+
const stops = [
|
|
124
|
+
options.catalog.subscribe(setCatalog),
|
|
125
|
+
options.agents.subscribe(setGroups),
|
|
126
|
+
selection.subscribe(setSelected),
|
|
127
|
+
];
|
|
128
|
+
createEffect(() => selection.setRows(snapshot().rows));
|
|
129
|
+
createEffect(() => {
|
|
130
|
+
const id = filter().machineId;
|
|
131
|
+
if (id && !catalog().groups.some((group) => group.id === id))
|
|
132
|
+
setFilter((value) => ({ ...value, machineId: null }));
|
|
133
|
+
});
|
|
134
|
+
onCleanup(() => {
|
|
135
|
+
for (const stop of stops) stop();
|
|
136
|
+
selection.dispose();
|
|
137
|
+
});
|
|
138
|
+
const presentation: ApplicationHomeAgentPresentation = {
|
|
139
|
+
get agentRoster() {
|
|
140
|
+
return snapshot();
|
|
141
|
+
},
|
|
142
|
+
get agentSelection() {
|
|
143
|
+
return selected();
|
|
144
|
+
},
|
|
145
|
+
get agentInputActive() {
|
|
146
|
+
return options.inputActive();
|
|
147
|
+
},
|
|
148
|
+
get agentFilterLabel() {
|
|
149
|
+
return `${catalog().groups.find((group) => group.id === filter().machineId)?.label ?? "All machines"} · ${filter().attentionOnly ? "Needs attention" : "All agents"}`;
|
|
150
|
+
},
|
|
151
|
+
onCycleAgentMachine() {
|
|
152
|
+
const ids = [null, ...catalog().groups.map((group) => group.id)];
|
|
153
|
+
setFilter((value) => ({
|
|
154
|
+
...value,
|
|
155
|
+
machineId: ids[(ids.indexOf(value.machineId) + 1) % ids.length]!,
|
|
156
|
+
}));
|
|
157
|
+
},
|
|
158
|
+
onToggleAgentAttention() {
|
|
159
|
+
setFilter((value) => ({ ...value, attentionOnly: !value.attentionOnly }));
|
|
160
|
+
},
|
|
161
|
+
onRetryAgents: () => options.agents.retry?.(),
|
|
162
|
+
onLoadMoreAgents: () => options.agents.loadMore?.(),
|
|
163
|
+
onSelectAgent: selection.select,
|
|
164
|
+
onMoveAgent: selection.move,
|
|
165
|
+
onAgentViewport: selection.setViewport,
|
|
166
|
+
onOpenAgent(row, source) {
|
|
167
|
+
if (!options.inputActive()) return;
|
|
168
|
+
const group = groups().find((group) => group.machineId === row.machineId);
|
|
169
|
+
const agent = group?.agents.find((agent) => agent.id === row.key);
|
|
170
|
+
if (agent && !agent.disabled && agent.paneId)
|
|
171
|
+
void options.open(agent.machineId, agent, source);
|
|
172
|
+
},
|
|
173
|
+
};
|
|
174
|
+
return { presentation };
|
|
175
|
+
}
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type ApplicationHomeAgentObserver,
|
|
11
11
|
} from "./application-home-agent-observer.ts";
|
|
12
12
|
import { createApplicationHomeAgentTransport } from "./application-home-agent-transport.ts";
|
|
13
|
-
import type { HomeAgentRow } from "./application-home-agents.ts";
|
|
13
|
+
import type { HomeAgentRow, HomeAgentSnapshot } from "./application-home-agents.ts";
|
|
14
14
|
|
|
15
15
|
export interface ApplicationMachineAgent extends HomeAgentRow {
|
|
16
16
|
readonly id: string;
|
|
@@ -19,6 +19,7 @@ export interface ApplicationMachineAgent extends HomeAgentRow {
|
|
|
19
19
|
}
|
|
20
20
|
export interface ApplicationMachineAgentGroup {
|
|
21
21
|
readonly available?: boolean;
|
|
22
|
+
readonly observation?: HomeAgentSnapshot;
|
|
22
23
|
readonly machineId: string;
|
|
23
24
|
readonly agents: readonly ApplicationMachineAgent[];
|
|
24
25
|
}
|
|
@@ -74,6 +75,7 @@ export function createApplicationMachineAgents(options: {
|
|
|
74
75
|
const current = !!entry && entry.binding === bindingFor(entry.handle);
|
|
75
76
|
return Object.freeze({
|
|
76
77
|
machineId: group.id,
|
|
78
|
+
observation: entry?.observer?.getSnapshot(),
|
|
77
79
|
available:
|
|
78
80
|
group.state === "ready" && current && entry?.observer?.getSnapshot().phase === "live",
|
|
79
81
|
agents: Object.freeze(
|
|
@@ -185,6 +187,12 @@ export function createApplicationMachineAgents(options: {
|
|
|
185
187
|
!!entry.observer?.isCurrentTarget(row)
|
|
186
188
|
);
|
|
187
189
|
},
|
|
190
|
+
retry() {
|
|
191
|
+
for (const entry of entries.values()) entry.observer?.retry();
|
|
192
|
+
},
|
|
193
|
+
loadMore() {
|
|
194
|
+
for (const entry of entries.values()) entry.observer?.loadMore();
|
|
195
|
+
},
|
|
188
196
|
start() {
|
|
189
197
|
if (started || disposed) return;
|
|
190
198
|
started = true;
|