tmux-ide 2.9.0-beta.41 → 2.9.0-beta.44
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 +1 -1
- package/package.json +1 -1
- package/packages/daemon/dist/tui/mirror/runtime/application-home-agent-roster.jsx +47 -90
- package/packages/daemon/dist/tui/mirror/runtime/application-machine-sidebar.jsx +82 -109
- package/packages/daemon/dist/tui/mirror/runtime/application-shell-home.jsx +43 -13
- package/packages/daemon/dist/tui/mirror/shell-chrome.js +5 -7
- package/packages/daemon/dist/tui/mirror/ui/agent-row.jsx +40 -0
- package/packages/daemon/dist/tui/mirror/ui/home-agent-search.jsx +8 -5
- package/packages/daemon/dist/tui/mirror/ui/input-surface.jsx +11 -0
- package/packages/daemon/dist/tui/mirror/ui/navigation-row.jsx +7 -2
- package/packages/daemon/dist/tui/mirror/ui/section-heading.jsx +6 -0
- package/packages/daemon/dist/tui/mirror/ui/tabs.jsx +22 -5
- package/packages/daemon/src/tui/mirror/__snapshots__/shell-chrome-renderer.test.tsx.snap +3 -3
- package/packages/daemon/src/tui/mirror/runtime/__snapshots__/application-shell-view-renderer.test.tsx.snap +8 -8
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +114 -178
- package/packages/daemon/src/tui/mirror/runtime/application-machine-sidebar.tsx +108 -148
- package/packages/daemon/src/tui/mirror/runtime/application-reference-sheet.tsx +9 -0
- package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +67 -17
- package/packages/daemon/src/tui/mirror/runtime/application-shell-view.tsx +2 -0
- package/packages/daemon/src/tui/mirror/shell-chrome.ts +4 -5
- package/packages/daemon/src/tui/mirror/ui/agent-row.tsx +92 -0
- package/packages/daemon/src/tui/mirror/ui/home-agent-search.tsx +17 -17
- package/packages/daemon/src/tui/mirror/ui/input-surface.tsx +39 -0
- package/packages/daemon/src/tui/mirror/ui/navigation-row.tsx +8 -1
- package/packages/daemon/src/tui/mirror/ui/overlay-search-field.tsx +5 -12
- package/packages/daemon/src/tui/mirror/ui/section-heading.tsx +15 -0
- package/packages/daemon/src/tui/mirror/ui/tabs.tsx +30 -7
- package/packages/daemon/src/tui/mirror/workspace/__snapshots__/application-shell-renderer.test.tsx.snap +3 -3
package/bin/cli.js
CHANGED
|
@@ -11279,7 +11279,7 @@ var require_package = __commonJS({
|
|
|
11279
11279
|
"package.json"(exports, module) {
|
|
11280
11280
|
module.exports = {
|
|
11281
11281
|
name: "tmux-ide",
|
|
11282
|
-
version: "2.9.0-beta.
|
|
11282
|
+
version: "2.9.0-beta.44",
|
|
11283
11283
|
description: "A visual, agent-aware IDE for any tmux session, with optional workspace presets",
|
|
11284
11284
|
type: "module",
|
|
11285
11285
|
bin: {
|
package/package.json
CHANGED
|
@@ -2,29 +2,13 @@
|
|
|
2
2
|
import { HOME_ACTIONS, HOME_ACTIVITY_ACTIONS, } from "../workspace/application-action-descriptions.js";
|
|
3
3
|
import { createEffect, createMemo, createSignal, For, Show } from "solid-js";
|
|
4
4
|
import { clipTerminal, terminalDisplayWidth } from "../terminal-text.js";
|
|
5
|
-
import {
|
|
5
|
+
import { AgentRow } from "../ui/agent-row.jsx";
|
|
6
6
|
import { KeyHint } from "../ui/key-hint.jsx";
|
|
7
7
|
import { TuiButton } from "../ui/button.jsx";
|
|
8
8
|
import { useKeyboardRoute } from "../ui/keyboard-router.jsx";
|
|
9
9
|
import { applicationPaneRenameKeyAction, applicationPaneRenamePaste, } from "./application-pane-rename-input.js";
|
|
10
10
|
import { HomeAgentSearch } from "../ui/home-agent-search.jsx";
|
|
11
|
-
import { createAgentStatusMarker } from "../ui/agent-status-marker.js";
|
|
12
11
|
import { ActivityIndicator } from "../ui/activity-indicator.jsx";
|
|
13
|
-
import { componentPalette } from "../ui/state.js";
|
|
14
|
-
import { homeAgentStatusLabel, } from "./application-home-agents.js";
|
|
15
|
-
function padCells(text, width) {
|
|
16
|
-
const clipped = clipTerminal(text, Math.max(0, width));
|
|
17
|
-
return clipped + " ".repeat(Math.max(0, width - terminalDisplayWidth(clipped)));
|
|
18
|
-
}
|
|
19
|
-
function statusTone(row) {
|
|
20
|
-
if (row.activity === "waiting" || row.activity === "failed")
|
|
21
|
-
return "blocked";
|
|
22
|
-
if (row.activity === "running")
|
|
23
|
-
return "working";
|
|
24
|
-
if (row.activity === "complete")
|
|
25
|
-
return "done";
|
|
26
|
-
return row.activity === "idle" ? "idle" : "unknown";
|
|
27
|
-
}
|
|
28
12
|
/** A flat projected roster; no subscriptions, navigation effects, or terminal input ingress. */
|
|
29
13
|
export function HomeAgentRoster(props) {
|
|
30
14
|
const [hovered, setHovered] = createSignal(null);
|
|
@@ -37,10 +21,12 @@ export function HomeAgentRoster(props) {
|
|
|
37
21
|
const showRecovery = () => Boolean(((props.snapshot.phase === "unavailable" || props.snapshot.unavailableSessions > 0) &&
|
|
38
22
|
props.onRetry) ||
|
|
39
23
|
(props.snapshot.truncatedSessions > 0 && props.onLoadMore));
|
|
40
|
-
const rowHeight = () => (
|
|
41
|
-
const
|
|
42
|
-
const
|
|
43
|
-
const
|
|
24
|
+
const rowHeight = () => (height() >= 14 ? 2 : 1);
|
|
25
|
+
const compact = () => height() < 8;
|
|
26
|
+
const filterRows = () => (compact() ? 0 : props.onSetActivityFilter && width() < 70 ? 2 : 1);
|
|
27
|
+
const searchRows = () => (props.onQueryChange ? (compact() ? 1 : 2) : 0);
|
|
28
|
+
const visibleCount = () => Math.max(0, Math.floor((height() - (compact() ? 1 : 4) - filterRows() - searchRows() - (showRecovery() ? 1 : 0)) /
|
|
29
|
+
rowHeight()));
|
|
44
30
|
const openSelected = (source = "keyboard") => {
|
|
45
31
|
const row = props.snapshot.rows.find((row) => row.key === props.selection.selectedKey);
|
|
46
32
|
if (props.inputActive && visibleCount() > 0 && row?.paneId && !stale(row))
|
|
@@ -53,12 +39,6 @@ export function HomeAgentRoster(props) {
|
|
|
53
39
|
const keys = createMemo(() => visible().map((row) => row.key), undefined, {
|
|
54
40
|
equals: (a, b) => a.length === b.length && a.every((key, index) => key === b[index]),
|
|
55
41
|
});
|
|
56
|
-
const columns = () => {
|
|
57
|
-
const status = Math.min(12, Math.max(0, width() - 4));
|
|
58
|
-
const label = Math.max(0, width() - status - 3);
|
|
59
|
-
const session = width() >= 44 ? Math.floor(label * 0.43) : 0;
|
|
60
|
-
return { status, agent: label - session, session };
|
|
61
|
-
};
|
|
62
42
|
const filtered = () => Boolean(props.query || (props.activityFilter && props.activityFilter !== "all"));
|
|
63
43
|
const title = () => {
|
|
64
44
|
if (props.snapshot.phase === "unavailable")
|
|
@@ -143,7 +123,9 @@ export function HomeAgentRoster(props) {
|
|
|
143
123
|
props.onMove(delta);
|
|
144
124
|
return true;
|
|
145
125
|
});
|
|
146
|
-
return (<box position="relative" width={width()} height={
|
|
126
|
+
return (<box position="relative" width={width()} height={props.fitContent && props.snapshot.rows.length > 0
|
|
127
|
+
? height() - (visibleCount() - visible().length) * rowHeight()
|
|
128
|
+
: height()} flexShrink={0} flexDirection="column" overflow="hidden" onMouseScroll={(event) => {
|
|
147
129
|
if (!props.inputActive)
|
|
148
130
|
return;
|
|
149
131
|
const direction = event.scroll?.direction;
|
|
@@ -154,16 +136,19 @@ export function HomeAgentRoster(props) {
|
|
|
154
136
|
props.onMove(direction === "up" ? -1 : 1);
|
|
155
137
|
}} onMouseOut={() => setHovered(null)}>
|
|
156
138
|
<box height={searchRows()} flexShrink={0}/>
|
|
157
|
-
<
|
|
158
|
-
<
|
|
159
|
-
<
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
{
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
139
|
+
<Show when={!compact()}>
|
|
140
|
+
<box width={width()} height={1} flexShrink={0} flexDirection="row">
|
|
141
|
+
<Show when={props.snapshot.phase === "loading" || props.snapshot.loadingSessions > 0}>
|
|
142
|
+
<ActivityIndicator theme={props.theme} active={props.inputActive}/>
|
|
143
|
+
</Show>
|
|
144
|
+
<text height={1} flexGrow={1} fg={props.theme.roles.text.muted}>
|
|
145
|
+
{clipTerminal(title(), width())}
|
|
146
|
+
</text>
|
|
147
|
+
</box>
|
|
148
|
+
</Show>
|
|
149
|
+
<Show when={!compact()}>
|
|
150
|
+
<box width={width()} height={filterRows()} flexShrink={0} flexDirection={filterRows() > 1 ? "column" : "row"} overflow="hidden">
|
|
151
|
+
<KeyHint theme={props.theme} keys={HOME_ACTIONS.machine.keys} label={props.filterLabel?.split(" · ")[0] ?? "All machines"} width={props.onSetActivityFilter && filterRows() === 1
|
|
167
152
|
? Math.max(1, width() - 47)
|
|
168
153
|
: props.onSetActivityFilter
|
|
169
154
|
? Math.min(width(), 28)
|
|
@@ -171,20 +156,21 @@ export function HomeAgentRoster(props) {
|
|
|
171
156
|
if (props.inputActive)
|
|
172
157
|
props.onCycleMachine?.();
|
|
173
158
|
}}/>
|
|
174
|
-
|
|
175
|
-
|
|
159
|
+
<box height={1} flexShrink={0} flexDirection="row" overflow="hidden" width={Math.min(width(), 47)}>
|
|
160
|
+
<Show when={props.onSetActivityFilter} fallback={<KeyHint theme={props.theme} keys={HOME_ACTIONS.attention.keys} label="Attention" quiet button onPress={() => {
|
|
176
161
|
if (props.inputActive)
|
|
177
162
|
props.onToggleAttention?.();
|
|
178
163
|
}}/>}>
|
|
179
|
-
|
|
180
|
-
|
|
164
|
+
<For each={HOME_ACTIVITY_ACTIONS}>
|
|
165
|
+
{(filter) => (<KeyHint theme={props.theme} keys={filter.keys} label={filter.value === "attention" && width() < 47 ? "Attention" : filter.label} quiet button selected={(props.activityFilter ?? "all") === filter.value} onPress={() => {
|
|
181
166
|
if (props.inputActive)
|
|
182
167
|
props.onSetActivityFilter?.(filter.value);
|
|
183
168
|
}}/>)}
|
|
184
|
-
|
|
185
|
-
|
|
169
|
+
</For>
|
|
170
|
+
</Show>
|
|
171
|
+
</box>
|
|
186
172
|
</box>
|
|
187
|
-
</
|
|
173
|
+
</Show>
|
|
188
174
|
<Show when={props.snapshot.rows.length > 0} fallback={<box height={Math.max(0, visibleCount() * rowHeight() + 1)} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
189
175
|
<Show when={props.snapshot.phase === "live" && !props.query}>
|
|
190
176
|
<text height={1} width={width()} fg={props.theme.roles.text.primary}>
|
|
@@ -197,51 +183,20 @@ export function HomeAgentRoster(props) {
|
|
|
197
183
|
</text>
|
|
198
184
|
</Show>
|
|
199
185
|
</box>}>
|
|
200
|
-
<box height={1} flexShrink={0}/>
|
|
201
|
-
<box height={visibleCount() * rowHeight()} width={width()} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
186
|
+
<box height={compact() ? 0 : 1} flexShrink={0}/>
|
|
187
|
+
<box height={(props.fitContent ? visible().length : visibleCount()) * rowHeight()} width={width()} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
202
188
|
<For each={keys()}>
|
|
203
189
|
{(key) => {
|
|
204
190
|
const row = () => byKey().get(key);
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
unavailable: () => stale(row()),
|
|
210
|
-
});
|
|
211
|
-
return (<box height={rowHeight()} flexDirection="column" backgroundColor={componentPalette(props.theme, {
|
|
212
|
-
selected: props.selection.selectedKey === key,
|
|
213
|
-
hovered: hovered() === key,
|
|
214
|
-
disabled: row().paneId === null || stale(row()),
|
|
215
|
-
}).background} width={width()} flexShrink={0} onMouseDown={(event) => {
|
|
216
|
-
if (!props.inputActive || event.button !== 0 || !row().paneId || stale(row()))
|
|
217
|
-
return;
|
|
218
|
-
event.preventDefault();
|
|
219
|
-
event.stopPropagation();
|
|
220
|
-
props.onSelect(key);
|
|
221
|
-
props.onOpen(row(), "mouse");
|
|
222
|
-
}} onMouseOver={() => setHovered(key)} onMouseMove={() => setHovered(key)}>
|
|
223
|
-
<NavigationRow theme={props.theme} id={`home-agent:${key}`} width={width()} label={rowHeight() > 1
|
|
224
|
-
? row().name
|
|
225
|
-
: padCells(row().name, columns().agent) +
|
|
226
|
-
padCells([row().machineLabel, row().serverLabel, row().sessionName]
|
|
227
|
-
.filter(Boolean)
|
|
228
|
-
.join(" / "), columns().session)} detail={padCells(`${marker()} ${stale(row()) ? "last seen" : homeAgentStatusLabel(row().activity).toLowerCase()}`, columns().status)} detailAlign="end" marker=" " selected={props.selection.selectedKey === key} focused={props.inputActive && !editing() && props.selection.selectedKey === key} hovered={hovered() === key} status={stale(row()) ? "unknown" : statusTone(row())} disabled={row().paneId === null || stale(row())} onActivate={(source) => {
|
|
191
|
+
return (<box height={rowHeight()} width={width()} flexShrink={0} onMouseOver={() => setHovered(key)} onMouseMove={() => setHovered(key)}>
|
|
192
|
+
<AgentRow surface="canvas" theme={props.theme} id={`home-agent:${key}`} name={row().name} context={[row().machineLabel, row().serverLabel, row().sessionName]
|
|
193
|
+
.filter(Boolean)
|
|
194
|
+
.join(" · ")} width={width()} compact={rowHeight() === 1} activity={row().activity} attention={row().attention} unavailable={row().paneId === null || stale(row())} selected={props.selection.selectedKey === key} focused={props.inputActive && !editing() && props.selection.selectedKey === key} hovered={hovered() === key} onOpen={(source) => {
|
|
229
195
|
if (!props.inputActive || row().paneId === null || stale(row()))
|
|
230
196
|
return;
|
|
231
197
|
props.onSelect(key);
|
|
232
198
|
props.onOpen(row(), source);
|
|
233
199
|
}}/>
|
|
234
|
-
<Show when={rowHeight() > 1}>
|
|
235
|
-
<text width={width()} height={1} fg={props.selection.selectedKey === key
|
|
236
|
-
? props.theme.roles.selection.selectionText
|
|
237
|
-
: props.theme.roles.text.muted} bg={componentPalette(props.theme, {
|
|
238
|
-
selected: props.selection.selectedKey === key,
|
|
239
|
-
hovered: hovered() === key,
|
|
240
|
-
disabled: row().paneId === null || stale(row()),
|
|
241
|
-
}).background}>
|
|
242
|
-
{clipTerminal(` ${[row().sessionName, row().machineLabel, row().serverLabel, row().harness].filter(Boolean).join(" · ")}`, width())}
|
|
243
|
-
</text>
|
|
244
|
-
</Show>
|
|
245
200
|
</box>);
|
|
246
201
|
}}
|
|
247
202
|
</For>
|
|
@@ -259,14 +214,16 @@ export function HomeAgentRoster(props) {
|
|
|
259
214
|
<text width={width()} height={1} flexShrink={0} fg={props.theme.roles.text.muted}>
|
|
260
215
|
{clipTerminal(coverage(), width())}
|
|
261
216
|
</text>
|
|
262
|
-
<
|
|
263
|
-
<
|
|
264
|
-
{
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
<
|
|
268
|
-
|
|
269
|
-
|
|
217
|
+
<Show when={!compact()}>
|
|
218
|
+
<box width={width()} height={1} flexShrink={0} flexDirection="row" overflow="hidden">
|
|
219
|
+
<text width={Math.min(Math.max(0, width() - (footer().endsWith("Enter open") && width() >= 20 ? 12 : 0)), terminalDisplayWidth(footer().replace(/ Enter open$/u, "")))} fg={props.theme.roles.text.muted}>
|
|
220
|
+
{clipTerminal(footer().replace(/ Enter open$/u, ""), width())}
|
|
221
|
+
</text>
|
|
222
|
+
<Show when={footer().endsWith("Enter open") && width() >= 20}>
|
|
223
|
+
<KeyHint theme={props.theme} keys={HOME_ACTIONS.open.keys} label={HOME_ACTIONS.open.label} quiet button onPress={() => openSelected("mouse")}/>
|
|
224
|
+
</Show>
|
|
225
|
+
</box>
|
|
226
|
+
</Show>
|
|
270
227
|
<Show when={showRecovery()}>
|
|
271
228
|
<box height={1} width={width()} flexShrink={0} flexDirection="row" gap={1}>
|
|
272
229
|
<Show when={(props.snapshot.phase === "unavailable" || props.snapshot.unavailableSessions > 0) &&
|
|
@@ -1,12 +1,10 @@
|
|
|
1
1
|
import { CHROME_ACTIONS, SIDEBAR_ACTIONS } from "../workspace/application-action-descriptions.js";
|
|
2
|
-
import {
|
|
3
|
-
import { fleetHostColor
|
|
4
|
-
import { WorkingSessions } from "../ui/working-sessions.jsx";
|
|
2
|
+
import { AgentRow } from "../ui/agent-row.jsx";
|
|
3
|
+
import { fleetHostColor } from "./fleet-presentation.js";
|
|
5
4
|
/* @jsxImportSource @opentui/solid */
|
|
6
5
|
import { fleetConnectionMessage, } from "@tmux-ide/daemon-client/fleet-connection-status";
|
|
7
|
-
import { terminalAgentStatusLabel } from "./application-terminal-workspace-policy.js";
|
|
8
6
|
import { For, Show, createEffect, createMemo, createSignal, } from "solid-js";
|
|
9
|
-
import {
|
|
7
|
+
import { friendlySessionLabel } from "../terminal-text.js";
|
|
10
8
|
import { KeyHint } from "../ui/key-hint.jsx";
|
|
11
9
|
import { NavigationRow } from "../ui/navigation-row.jsx";
|
|
12
10
|
import { Surface } from "../ui/surface.jsx";
|
|
@@ -24,22 +22,16 @@ export function ApplicationMachineSidebar(props) {
|
|
|
24
22
|
: 0;
|
|
25
23
|
const controlsHeight = () => (props.height >= 8 && controlGroup() ? 4 : 0);
|
|
26
24
|
const searchHeight = () => (props.model.onOpenSwitcher ? 1 : 0) + (props.model.onOpenAttention ? 1 : 0);
|
|
27
|
-
const [workingFocused, setWorkingFocused] = createSignal(true);
|
|
28
|
-
const tabHeight = () => (props.model.tabs?.().length ?? 0) > 0 && props.height >= 12
|
|
29
|
-
? 1 +
|
|
30
|
-
Math.min(props.model.tabs().length, Math.max(1, Math.floor((props.height / 2 - 1) / 3))) *
|
|
31
|
-
3
|
|
32
|
-
: 0;
|
|
33
25
|
const machineHeight = () => Math.max(0, props.height -
|
|
34
26
|
1 -
|
|
35
27
|
(props.model.onAddMachine ? 1 : 0) -
|
|
36
28
|
agentHeight() -
|
|
37
29
|
controlsHeight() -
|
|
38
|
-
searchHeight()
|
|
39
|
-
tabHeight());
|
|
30
|
+
searchHeight());
|
|
40
31
|
const active = (row) => row.group.id === props.model.activeMachineId() &&
|
|
41
32
|
(row.agent
|
|
42
33
|
? row.group.state === "ready" &&
|
|
34
|
+
row.group.agentsAvailable !== false &&
|
|
43
35
|
!row.agent.disabled &&
|
|
44
36
|
row.agent.sessionName === props.model.activeSessionName() &&
|
|
45
37
|
(!props.model.activeSessionKey ||
|
|
@@ -52,44 +44,50 @@ export function ApplicationMachineSidebar(props) {
|
|
|
52
44
|
? row.session?.id === props.model.activeSessionKey()
|
|
53
45
|
: row.session?.name === props.model.activeSessionName());
|
|
54
46
|
const preferenceKey = (group) => group.environmentId ?? group.id;
|
|
55
|
-
const rows = createMemo(() =>
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
.map((session, index, sessions) => ({
|
|
65
|
-
serverHeading: (group.servers?.length ??
|
|
66
|
-
new Set(group.sessions.map((row) => row.server?.serverId)).size) > 1 &&
|
|
67
|
-
session.server &&
|
|
68
|
-
(index === 0 || sessions[index - 1]?.server?.serverId !== session.server.serverId)
|
|
69
|
-
? `${session.serverLabel ?? "Server"} · ${session.server.serverId.slice(-6)}`
|
|
70
|
-
: undefined,
|
|
71
|
-
key: JSON.stringify([group.id, "session", session.id]),
|
|
47
|
+
const rows = createMemo(() => [
|
|
48
|
+
...props.model.groups().flatMap((group) => (group.agents ?? []).map((agent) => ({
|
|
49
|
+
key: JSON.stringify([
|
|
50
|
+
group.id,
|
|
51
|
+
"agent",
|
|
52
|
+
agent.server?.serverId,
|
|
53
|
+
agent.server?.generation,
|
|
54
|
+
agent.id,
|
|
55
|
+
]),
|
|
72
56
|
group,
|
|
73
|
-
|
|
74
|
-
})),
|
|
75
|
-
...(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
.filter((
|
|
79
|
-
.
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
57
|
+
agent,
|
|
58
|
+
}))),
|
|
59
|
+
...props.model.groups().flatMap((group) => [
|
|
60
|
+
{ key: JSON.stringify([group.id]), group },
|
|
61
|
+
...group.sessions
|
|
62
|
+
.filter((session) => !collapsed().has(preferenceKey(group)) ||
|
|
63
|
+
props.model.favorites?.().includes(session.id) ||
|
|
64
|
+
(group.id === props.model.activeMachineId() &&
|
|
65
|
+
(props.model.activeSessionKey
|
|
66
|
+
? session.id === props.model.activeSessionKey()
|
|
67
|
+
: session.name === props.model.activeSessionName())))
|
|
68
|
+
.map((session, index, sessions) => ({
|
|
69
|
+
serverHeading: (group.servers?.length ??
|
|
70
|
+
new Set(group.sessions.map((row) => row.server?.serverId)).size) > 1 &&
|
|
71
|
+
session.server &&
|
|
72
|
+
(index === 0 || sessions[index - 1]?.server?.serverId !== session.server.serverId)
|
|
73
|
+
? `${session.serverLabel ?? "Server"} · ${session.server.serverId.slice(-6)}`
|
|
74
|
+
: undefined,
|
|
75
|
+
key: JSON.stringify([group.id, "session", session.id]),
|
|
87
76
|
group,
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
77
|
+
session,
|
|
78
|
+
})),
|
|
79
|
+
...(collapsed().has(preferenceKey(group))
|
|
80
|
+
? []
|
|
81
|
+
: (group.servers ?? [])
|
|
82
|
+
.filter((server) => !group.sessions.some((session) => session.server?.serverId === server.serverId))
|
|
83
|
+
.map((server) => ({
|
|
84
|
+
key: JSON.stringify([group.id, "server", server.serverId]),
|
|
85
|
+
group,
|
|
86
|
+
server,
|
|
87
|
+
}))),
|
|
88
|
+
]),
|
|
89
|
+
]);
|
|
90
|
+
const hasAgents = createMemo(() => rows().some((row) => row.agent));
|
|
93
91
|
const rowsByKey = createMemo(() => new Map(rows().map((row) => [row.key, row])));
|
|
94
92
|
const index = () => Math.max(0, rows().findIndex((row) => row.key === selectedKey()));
|
|
95
93
|
const controlGroup = () => {
|
|
@@ -101,10 +99,6 @@ export function ApplicationMachineSidebar(props) {
|
|
|
101
99
|
? group
|
|
102
100
|
: null;
|
|
103
101
|
};
|
|
104
|
-
const activity = (row) => summarizeFleetActivity((row.group.agents ?? []).filter((agent) => !row.session ||
|
|
105
|
-
(agent.sessionName === row.session.name &&
|
|
106
|
-
agent.server?.serverId === row.session.server?.serverId &&
|
|
107
|
-
agent.server?.generation === row.session.server?.generation)), row.group.state === "ready" && row.group.agentsAvailable !== false && !row.session?.disabled);
|
|
108
102
|
const connectionDetail = (group) => {
|
|
109
103
|
const diagnostic = group.diagnostic;
|
|
110
104
|
if (!diagnostic)
|
|
@@ -133,7 +127,6 @@ export function ApplicationMachineSidebar(props) {
|
|
|
133
127
|
props.model.onCollapse?.(preferenceKey(group), value);
|
|
134
128
|
};
|
|
135
129
|
const activate = (row, source) => {
|
|
136
|
-
setWorkingFocused(false);
|
|
137
130
|
setSelectedKey(row.key);
|
|
138
131
|
setLocalFocused(true);
|
|
139
132
|
props.model.onFocus?.();
|
|
@@ -147,6 +140,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
147
140
|
}
|
|
148
141
|
if (row.agent) {
|
|
149
142
|
if (row.group.state === "ready" &&
|
|
143
|
+
row.group.agentsAvailable !== false &&
|
|
150
144
|
!row.agent.disabled &&
|
|
151
145
|
row.agent.paneId &&
|
|
152
146
|
props.model.onOpenAgent) {
|
|
@@ -165,12 +159,12 @@ export function ApplicationMachineSidebar(props) {
|
|
|
165
159
|
else
|
|
166
160
|
toggle(row.group);
|
|
167
161
|
};
|
|
168
|
-
const sectionGap = (row) => !row.session && !row.agent && !row.server
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
162
|
+
const sectionGap = (row) => (!row.session && !row.agent && !row.server ? 1 : 0);
|
|
163
|
+
const rowHeight = (row) => (row.agent ? 2 : row.serverHeading ? 2 : 1) +
|
|
164
|
+
sectionGap(row) +
|
|
165
|
+
(hasAgents() && row.key === JSON.stringify([props.model.groups()[0]?.id]) ? 1 : 0);
|
|
172
166
|
const revealFocusedRow = () => {
|
|
173
|
-
if (!focused() ||
|
|
167
|
+
if (!focused() || !scroll)
|
|
174
168
|
return;
|
|
175
169
|
const y = rows()
|
|
176
170
|
.slice(0, index())
|
|
@@ -192,14 +186,16 @@ export function ApplicationMachineSidebar(props) {
|
|
|
192
186
|
if (!focused() || event.eventType !== "press" || event.meta)
|
|
193
187
|
return false;
|
|
194
188
|
let key = event.name.toLowerCase();
|
|
195
|
-
if (key === "tab"
|
|
196
|
-
|
|
189
|
+
if (key === "tab") {
|
|
190
|
+
const target = rows()[index()]?.agent
|
|
191
|
+
? rows().find((row) => !row.agent)
|
|
192
|
+
: rows().find((row) => row.agent);
|
|
193
|
+
if (target)
|
|
194
|
+
setSelectedKey(target.key);
|
|
197
195
|
event.preventDefault();
|
|
198
196
|
event.stopPropagation();
|
|
199
197
|
return true;
|
|
200
198
|
}
|
|
201
|
-
if (workingFocused() && tabHeight() && !["escape", "?", "question", "/", "a"].includes(key))
|
|
202
|
-
return false;
|
|
203
199
|
if (event.ctrl && key === "d")
|
|
204
200
|
key = "halfdown";
|
|
205
201
|
else if (event.ctrl && key === "u")
|
|
@@ -306,22 +302,10 @@ export function ApplicationMachineSidebar(props) {
|
|
|
306
302
|
});
|
|
307
303
|
return (<Surface id="application-machine-sidebar" theme={props.theme} variant="panel" width={props.width} height={props.height} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
308
304
|
<box height={1} flexShrink={0} flexDirection="row" overflow="hidden">
|
|
309
|
-
<text fg={props.theme.roles.text.secondary}>
|
|
310
|
-
{tabHeight() ? " Workspace" : " Machines"}
|
|
311
|
-
</text>
|
|
305
|
+
<text fg={props.theme.roles.text.secondary}>{hasAgents() ? " Agents" : " Machines"}</text>
|
|
312
306
|
<box flexGrow={1}/>
|
|
313
|
-
<Show when={props.model.tabs?.().length && props.width >= 28}>
|
|
314
|
-
<KeyHint theme={props.theme} keys={CHROME_ACTIONS.tabs.keys} label={CHROME_ACTIONS.tabs.label} quiet/>
|
|
315
|
-
</Show>
|
|
316
307
|
<KeyHint theme={props.theme} keys={SIDEBAR_ACTIONS.help.keys} label={props.width >= 20 ? SIDEBAR_ACTIONS.help.label : undefined} quiet button onPress={() => props.onHelp?.("mouse")}/>
|
|
317
308
|
</box>
|
|
318
|
-
<Show when={tabHeight() > 0}>
|
|
319
|
-
<WorkingSessions theme={props.theme} rows={props.model.tabs?.() ?? []} width={props.width} height={tabHeight()} focused={focused() && workingFocused()} onFocus={() => {
|
|
320
|
-
setWorkingFocused(true);
|
|
321
|
-
setLocalFocused(true);
|
|
322
|
-
props.model.onFocus?.();
|
|
323
|
-
}} onOpen={(key) => props.model.onOpenTab?.(key)} onClose={props.model.onCloseTab}/>
|
|
324
|
-
</Show>
|
|
325
309
|
<scrollbox ref={(value) => {
|
|
326
310
|
scroll = value;
|
|
327
311
|
const resized = value.content.onSizeChange;
|
|
@@ -352,16 +336,7 @@ export function ApplicationMachineSidebar(props) {
|
|
|
352
336
|
get serverHeading() {
|
|
353
337
|
return current().serverHeading;
|
|
354
338
|
},
|
|
355
|
-
get agentHeading() {
|
|
356
|
-
return current().agentHeading;
|
|
357
|
-
},
|
|
358
339
|
};
|
|
359
|
-
const agentMarker = createAgentStatusMarker({
|
|
360
|
-
theme: () => props.theme,
|
|
361
|
-
status: () => row.agent?.activity,
|
|
362
|
-
attention: () => Boolean(row.agent?.attention),
|
|
363
|
-
unavailable: () => row.group.state !== "ready" || Boolean(row.agent?.disabled),
|
|
364
|
-
});
|
|
365
340
|
return (<box width={Math.max(1, props.width - 1)} height={rowHeight(row)} flexShrink={0} flexDirection="column">
|
|
366
341
|
<box height={sectionGap(row)} flexShrink={0}/>
|
|
367
342
|
<Show when={row.serverHeading}>
|
|
@@ -370,22 +345,18 @@ export function ApplicationMachineSidebar(props) {
|
|
|
370
345
|
props.model.onSelectServer?.(row.group.id, row.session.server);
|
|
371
346
|
}}/>
|
|
372
347
|
</Show>
|
|
373
|
-
<Show when={row.
|
|
348
|
+
<Show when={hasAgents() && row.key === JSON.stringify([props.model.groups()[0]?.id])}>
|
|
374
349
|
<text height={1} fg={props.theme.roles.text.secondary}>
|
|
375
350
|
{" "}
|
|
376
|
-
|
|
351
|
+
Machines
|
|
377
352
|
</text>
|
|
378
353
|
</Show>
|
|
379
|
-
<NavigationRow theme={props.theme} id={`machine:${row.key}`} width={Math.max(1, props.width - 1)} labelColor={!row.
|
|
380
|
-
|
|
381
|
-
: row.agent
|
|
382
|
-
? row.agent.name
|
|
354
|
+
<Show when={row.agent} fallback={<NavigationRow theme={props.theme} id={`machine:${row.key}`} width={Math.max(1, props.width - 1)} labelColor={!row.session ? fleetHostColor(row.group) : undefined} label={row.server
|
|
355
|
+
? ` ${row.server.label} · ${row.server.serverId.slice(-6)}`
|
|
383
356
|
: row.session
|
|
384
357
|
? friendlySessionLabel(row.session.name)
|
|
385
358
|
: row.group.label} marker={row.server
|
|
386
|
-
|
|
387
|
-
: row.agent
|
|
388
|
-
? agentMarker()
|
|
359
|
+
? " "
|
|
389
360
|
: row.session
|
|
390
361
|
? props.model.favorites?.().includes(row.session.id)
|
|
391
362
|
? " ★"
|
|
@@ -393,24 +364,26 @@ export function ApplicationMachineSidebar(props) {
|
|
|
393
364
|
: collapsed().has(preferenceKey(row.group))
|
|
394
365
|
? "▸"
|
|
395
366
|
: "▾"} detail={row.server
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
: row.agent
|
|
400
|
-
? row.group.state !== "ready" || row.agent.disabled
|
|
401
|
-
? "unavailable"
|
|
402
|
-
: terminalAgentStatusLabel(row.agent.activity).toLowerCase()
|
|
367
|
+
? row.server.state === "offline"
|
|
368
|
+
? "offline"
|
|
369
|
+
: "empty"
|
|
403
370
|
: row.session
|
|
404
371
|
? row.group.state !== "ready" || row.session.disabled
|
|
405
372
|
? "unavailable"
|
|
406
|
-
: `${row.session.paneCount}p
|
|
407
|
-
: row.group.
|
|
408
|
-
?
|
|
409
|
-
:
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
373
|
+
: `${row.session.paneCount}p`
|
|
374
|
+
: row.group.state === "ready"
|
|
375
|
+
? undefined
|
|
376
|
+
: connectionDetail(row.group)} selected={Boolean(row.session && active(row))} focused={focused() && row.key === selectedKey()} onActivate={(source) => activate(row, source)}/>}>
|
|
377
|
+
{(agent) => (<AgentRow theme={props.theme} id={`machine:${row.key}`} name={agent().name} context={[
|
|
378
|
+
row.group.label,
|
|
379
|
+
row.group.sessions.find((session) => session.server?.serverId === agent().server?.serverId)?.serverLabel,
|
|
380
|
+
friendlySessionLabel(agent().sessionName),
|
|
381
|
+
]
|
|
382
|
+
.filter(Boolean)
|
|
383
|
+
.join(" · ")} width={Math.max(1, props.width - 1)} activity={agent().activity} attention={agent().attention} unavailable={row.group.state !== "ready" ||
|
|
384
|
+
row.group.agentsAvailable === false ||
|
|
385
|
+
!!agent().disabled ||
|
|
386
|
+
!agent().paneId} selected={active(row)} focused={focused() && row.key === selectedKey()} onOpen={(source) => activate(row, source)}/>)}
|
|
414
387
|
</Show>
|
|
415
388
|
</box>);
|
|
416
389
|
}}
|