tmux-ide 2.9.0-beta.38 → 2.9.0-beta.39
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 +55 -25
- package/packages/daemon/dist/tui/mirror/runtime/application-home-fleet.js +19 -2
- package/packages/daemon/dist/tui/mirror/runtime/application-shell-home.jsx +2 -1
- package/packages/daemon/dist/tui/mirror/ui/key-hint.jsx +8 -4
- package/packages/daemon/src/tui/mirror/runtime/application-home-agent-roster.tsx +127 -40
- package/packages/daemon/src/tui/mirror/runtime/application-home-agents-owner.ts +2 -0
- package/packages/daemon/src/tui/mirror/runtime/application-home-fleet.ts +22 -2
- package/packages/daemon/src/tui/mirror/runtime/application-shell-home.tsx +5 -0
- package/packages/daemon/src/tui/mirror/ui/key-hint.tsx +8 -4
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.39",
|
|
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,6 +2,7 @@
|
|
|
2
2
|
import { createEffect, createMemo, createSignal, For, Show } from "solid-js";
|
|
3
3
|
import { clipTerminal, terminalDisplayWidth } from "../terminal-text.js";
|
|
4
4
|
import { NavigationRow } from "../ui/navigation-row.jsx";
|
|
5
|
+
import { KeyHint } from "../ui/key-hint.jsx";
|
|
5
6
|
import { TuiButton } from "../ui/button.jsx";
|
|
6
7
|
import { useKeyboardRoute } from "../ui/keyboard-router.jsx";
|
|
7
8
|
import { applicationPaneRenameKeyAction, applicationPaneRenamePaste, } from "./application-pane-rename-input.js";
|
|
@@ -36,12 +37,13 @@ export function HomeAgentRoster(props) {
|
|
|
36
37
|
props.onRetry) ||
|
|
37
38
|
(props.snapshot.truncatedSessions > 0 && props.onLoadMore));
|
|
38
39
|
const rowHeight = () => (width() >= 60 && height() >= 16 ? 2 : 1);
|
|
40
|
+
const filterRows = () => (props.onSetActivityFilter && width() < 70 ? 2 : 1);
|
|
39
41
|
const searchRows = () => (props.onQueryChange ? 2 : 0);
|
|
40
|
-
const visibleCount = () => Math.max(0, Math.floor((height() -
|
|
41
|
-
const openSelected = () => {
|
|
42
|
+
const visibleCount = () => Math.max(0, Math.floor((height() - 4 - filterRows() - searchRows() - (showRecovery() ? 1 : 0)) / rowHeight()));
|
|
43
|
+
const openSelected = (source = "keyboard") => {
|
|
42
44
|
const row = props.snapshot.rows.find((row) => row.key === props.selection.selectedKey);
|
|
43
45
|
if (props.inputActive && visibleCount() > 0 && row?.paneId && !stale(row))
|
|
44
|
-
props.onOpen(row,
|
|
46
|
+
props.onOpen(row, source);
|
|
45
47
|
};
|
|
46
48
|
createEffect(() => props.onViewport(visibleCount()));
|
|
47
49
|
const offset = () => Math.max(0, Math.min(props.selection.scrollOffset, Math.max(0, props.snapshot.rows.length - visibleCount())));
|
|
@@ -56,12 +58,13 @@ export function HomeAgentRoster(props) {
|
|
|
56
58
|
const session = width() >= 44 ? Math.floor(label * 0.43) : 0;
|
|
57
59
|
return { status, agent: label - session, session };
|
|
58
60
|
};
|
|
61
|
+
const filtered = () => Boolean(props.query || (props.activityFilter && props.activityFilter !== "all"));
|
|
59
62
|
const title = () => {
|
|
60
63
|
if (props.snapshot.phase === "unavailable")
|
|
61
64
|
return "Agent overview unavailable";
|
|
62
65
|
if (props.snapshot.phase === "loading" && props.snapshot.rows.length === 0)
|
|
63
66
|
return "Loading agent overview…";
|
|
64
|
-
if (
|
|
67
|
+
if (filtered() && props.snapshot.rows.length === 0)
|
|
65
68
|
return "No matching agents";
|
|
66
69
|
if (props.snapshot.phase === "live" && props.snapshot.rows.length === 0)
|
|
67
70
|
return "No agents reported";
|
|
@@ -97,7 +100,15 @@ export function HomeAgentRoster(props) {
|
|
|
97
100
|
return false;
|
|
98
101
|
const key = event.name.toLowerCase();
|
|
99
102
|
const filter = !editing() &&
|
|
100
|
-
(key === "f"
|
|
103
|
+
(key === "f"
|
|
104
|
+
? props.onCycleMachine
|
|
105
|
+
: key === "a"
|
|
106
|
+
? props.onToggleAttention
|
|
107
|
+
: key === "w" && props.onSetActivityFilter
|
|
108
|
+
? () => props.onSetActivityFilter?.("working")
|
|
109
|
+
: key === "0" && props.onSetActivityFilter
|
|
110
|
+
? () => props.onSetActivityFilter?.("all")
|
|
111
|
+
: undefined);
|
|
101
112
|
if (filter) {
|
|
102
113
|
event.preventDefault();
|
|
103
114
|
event.stopPropagation();
|
|
@@ -150,30 +161,42 @@ export function HomeAgentRoster(props) {
|
|
|
150
161
|
{clipTerminal(title(), width())}
|
|
151
162
|
</text>
|
|
152
163
|
</box>
|
|
153
|
-
<box width={width()} height={
|
|
154
|
-
<
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
props.onCycleMachine?.();
|
|
160
|
-
}}>
|
|
161
|
-
{clipTerminal(props.filterLabel ? `${props.filterLabel} · f machine` : "", Math.max(0, width() - (props.onToggleAttention ? 16 : 0)))}
|
|
162
|
-
</text>
|
|
163
|
-
<Show when={props.onToggleAttention}>
|
|
164
|
-
<TuiButton theme={props.theme} label="Attention" shortcut="a" size="compact" variant="ghost" background={props.theme.roles.surfaces.canvas} width={Math.min(16, width())} onPress={() => {
|
|
164
|
+
<box width={width()} height={filterRows()} flexShrink={0} flexDirection={filterRows() > 1 ? "column" : "row"} overflow="hidden">
|
|
165
|
+
<KeyHint theme={props.theme} keys="f" label={props.filterLabel?.split(" · ")[0] ?? "All machines"} width={props.onSetActivityFilter && filterRows() === 1
|
|
166
|
+
? Math.max(1, width() - 47)
|
|
167
|
+
: props.onSetActivityFilter
|
|
168
|
+
? Math.min(width(), 28)
|
|
169
|
+
: Math.max(1, Math.min(28, width() - 16))} quiet button onPress={() => {
|
|
165
170
|
if (props.inputActive)
|
|
166
|
-
props.
|
|
171
|
+
props.onCycleMachine?.();
|
|
167
172
|
}}/>
|
|
168
|
-
|
|
173
|
+
<box height={1} flexShrink={0} flexDirection="row" overflow="hidden" width={Math.min(width(), 47)}>
|
|
174
|
+
<Show when={props.onSetActivityFilter} fallback={<KeyHint theme={props.theme} keys="a" label="Attention" quiet button onPress={() => {
|
|
175
|
+
if (props.inputActive)
|
|
176
|
+
props.onToggleAttention?.();
|
|
177
|
+
}}/>}>
|
|
178
|
+
<For each={[
|
|
179
|
+
{ key: "0", label: "All", value: "all" },
|
|
180
|
+
{ key: "w", label: "Working", value: "working" },
|
|
181
|
+
{ key: "a", label: "Needs attention", value: "attention" },
|
|
182
|
+
]}>
|
|
183
|
+
{(filter) => (<KeyHint theme={props.theme} keys={filter.key} label={filter.value === "attention" && width() < 47 ? "Attention" : filter.label} quiet button selected={(props.activityFilter ?? "all") === filter.value} onPress={() => {
|
|
184
|
+
if (props.inputActive)
|
|
185
|
+
props.onSetActivityFilter?.(filter.value);
|
|
186
|
+
}}/>)}
|
|
187
|
+
</For>
|
|
188
|
+
</Show>
|
|
189
|
+
</box>
|
|
169
190
|
</box>
|
|
170
191
|
<Show when={props.snapshot.rows.length > 0} fallback={<box height={Math.max(0, visibleCount() * rowHeight() + 1)} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
171
192
|
<Show when={props.snapshot.phase === "live" && !props.query}>
|
|
172
193
|
<text height={1} width={width()} fg={props.theme.roles.text.primary}>
|
|
173
|
-
{clipTerminal("Your next session starts here", width())}
|
|
194
|
+
{clipTerminal(filtered() ? "No agents match these filters" : "Your next session starts here", width())}
|
|
174
195
|
</text>
|
|
175
196
|
<text height={1} width={width()} fg={props.theme.roles.text.muted}>
|
|
176
|
-
{clipTerminal(
|
|
197
|
+
{clipTerminal(filtered()
|
|
198
|
+
? "Choose All or change the machine filter to see more agents."
|
|
199
|
+
: "Open terminals, or use Commands to connect a machine.", width())}
|
|
177
200
|
</text>
|
|
178
201
|
</Show>
|
|
179
202
|
</box>}>
|
|
@@ -190,6 +213,7 @@ export function HomeAgentRoster(props) {
|
|
|
190
213
|
});
|
|
191
214
|
return (<box height={rowHeight()} flexDirection="column" backgroundColor={componentPalette(props.theme, {
|
|
192
215
|
selected: props.selection.selectedKey === key,
|
|
216
|
+
hovered: hovered() === key,
|
|
193
217
|
disabled: row().paneId === null || stale(row()),
|
|
194
218
|
}).background} width={width()} flexShrink={0} onMouseDown={(event) => {
|
|
195
219
|
if (!props.inputActive || event.button !== 0 || !row().paneId || stale(row()))
|
|
@@ -215,6 +239,7 @@ export function HomeAgentRoster(props) {
|
|
|
215
239
|
? props.theme.roles.selection.selectionText
|
|
216
240
|
: props.theme.roles.text.muted} bg={componentPalette(props.theme, {
|
|
217
241
|
selected: props.selection.selectedKey === key,
|
|
242
|
+
hovered: hovered() === key,
|
|
218
243
|
disabled: row().paneId === null || stale(row()),
|
|
219
244
|
}).background}>
|
|
220
245
|
{clipTerminal(` ${[row().sessionName, row().machineLabel, row().serverLabel, row().harness].filter(Boolean).join(" · ")}`, width())}
|
|
@@ -231,15 +256,20 @@ export function HomeAgentRoster(props) {
|
|
|
231
256
|
const action = applicationPaneRenameKeyAction(event, props.query ?? "");
|
|
232
257
|
if (action.kind === "update")
|
|
233
258
|
onQueryChange()(action.value);
|
|
234
|
-
}} onPaste={(bytes) => onQueryChange()(applicationPaneRenamePaste(props.query ?? "", bytes))} onEditing={setEditing} onSubmit={openSelected}/>
|
|
259
|
+
}} onPaste={(bytes) => onQueryChange()(applicationPaneRenamePaste(props.query ?? "", bytes))} onEditing={setEditing} onSubmit={() => openSelected()}/>
|
|
235
260
|
</box>)}
|
|
236
261
|
</Show>
|
|
237
262
|
<text width={width()} height={1} flexShrink={0} fg={props.theme.roles.text.muted}>
|
|
238
263
|
{clipTerminal(coverage(), width())}
|
|
239
264
|
</text>
|
|
240
|
-
<
|
|
241
|
-
{
|
|
242
|
-
|
|
265
|
+
<box width={width()} height={1} flexShrink={0} flexDirection="row" overflow="hidden">
|
|
266
|
+
<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}>
|
|
267
|
+
{clipTerminal(footer().replace(/ Enter open$/u, ""), width())}
|
|
268
|
+
</text>
|
|
269
|
+
<Show when={footer().endsWith("Enter open") && width() >= 20}>
|
|
270
|
+
<KeyHint theme={props.theme} keys="Enter" label="open" quiet button onPress={() => openSelected("mouse")}/>
|
|
271
|
+
</Show>
|
|
272
|
+
</box>
|
|
243
273
|
<Show when={showRecovery()}>
|
|
244
274
|
<box height={1} width={width()} flexShrink={0} flexDirection="row" gap={1}>
|
|
245
275
|
<Show when={(props.snapshot.phase === "unavailable" || props.snapshot.unavailableSessions > 0) &&
|
|
@@ -28,6 +28,13 @@ export function projectHomeFleet(catalog, groups, filter) {
|
|
|
28
28
|
if (filter.attentionOnly &&
|
|
29
29
|
!(agent.attention || agent.activity === "waiting" || agent.activity === "failed"))
|
|
30
30
|
continue;
|
|
31
|
+
if (filter.workingOnly &&
|
|
32
|
+
(agent.disabled ||
|
|
33
|
+
group?.available === false ||
|
|
34
|
+
unavailable.has(sessionKey) ||
|
|
35
|
+
machine.state !== "ready" ||
|
|
36
|
+
agent.activity !== "running"))
|
|
37
|
+
continue;
|
|
31
38
|
const paneKey = agent.paneId
|
|
32
39
|
? JSON.stringify([machine.id, agent.server?.serverId, agent.daemonInstanceId, agent.paneId])
|
|
33
40
|
: agent.id;
|
|
@@ -119,7 +126,7 @@ export function createApplicationHomeFleetOwner(options) {
|
|
|
119
126
|
return options.inputActive();
|
|
120
127
|
},
|
|
121
128
|
get agentFilterLabel() {
|
|
122
|
-
return `${catalog().groups.find((group) => group.id === filter().machineId)?.label ?? "All machines"} · ${filter().attentionOnly ? "Needs attention" : "All agents"}`;
|
|
129
|
+
return `${catalog().groups.find((group) => group.id === filter().machineId)?.label ?? "All machines"} · ${filter().attentionOnly ? "Needs attention" : filter().workingOnly ? "Working" : "All agents"}`;
|
|
123
130
|
},
|
|
124
131
|
onCycleAgentMachine() {
|
|
125
132
|
const ids = [null, ...catalog().groups.map((group) => group.id)];
|
|
@@ -128,8 +135,18 @@ export function createApplicationHomeFleetOwner(options) {
|
|
|
128
135
|
machineId: ids[(ids.indexOf(value.machineId) + 1) % ids.length],
|
|
129
136
|
}));
|
|
130
137
|
},
|
|
138
|
+
get agentActivityFilter() {
|
|
139
|
+
return filter().attentionOnly ? "attention" : filter().workingOnly ? "working" : "all";
|
|
140
|
+
},
|
|
141
|
+
onSetAgentActivityFilter(activity) {
|
|
142
|
+
setFilter((value) => ({
|
|
143
|
+
...value,
|
|
144
|
+
attentionOnly: activity === "attention",
|
|
145
|
+
workingOnly: activity === "working",
|
|
146
|
+
}));
|
|
147
|
+
},
|
|
131
148
|
onToggleAgentAttention() {
|
|
132
|
-
setFilter((value) => ({ ...value, attentionOnly: !value.attentionOnly }));
|
|
149
|
+
setFilter((value) => ({ ...value, attentionOnly: !value.attentionOnly, workingOnly: false }));
|
|
133
150
|
},
|
|
134
151
|
onRetryAgents: () => options.agents.retry?.(),
|
|
135
152
|
onLoadMoreAgents: () => options.agents.loadMore?.(),
|
|
@@ -19,6 +19,7 @@ export function ApplicationHomeSurface(props) {
|
|
|
19
19
|
props.agentRoster?.phase === "live" &&
|
|
20
20
|
props.agentRoster.rows.length === 0 &&
|
|
21
21
|
!props.agentQuery &&
|
|
22
|
+
(!props.agentActivityFilter || props.agentActivityFilter === "all") &&
|
|
22
23
|
applicationHomeBrandVariant(bodyWidth(), height()) === "ascii";
|
|
23
24
|
const brandRows = () => (showAscii() ? APPLICATION_HOME_WORDMARK.length : 1);
|
|
24
25
|
const spacious = () => height() >= 14;
|
|
@@ -116,7 +117,7 @@ export function ApplicationHomeSurface(props) {
|
|
|
116
117
|
{clipTerminal(summary(), bodyWidth())}
|
|
117
118
|
</text>
|
|
118
119
|
</>}>
|
|
119
|
-
{(snapshot) => (<HomeAgentRoster query={props.agentQuery} onQueryChange={props.onAgentQueryChange} filterLabel={props.agentFilterLabel} onCycleMachine={props.onCycleAgentMachine} onToggleAttention={props.onToggleAgentAttention} theme={props.theme} width={bodyWidth()} height={rosterHeight()} snapshot={snapshot()} selection={props.agentSelection ?? { selectedKey: null, scrollOffset: 0 }} inputActive={props.agentInputActive ?? false} onSelect={(key) => props.onSelectAgent?.(key)} onMove={(delta) => props.onMoveAgent?.(delta)} onViewport={(rows) => props.onAgentViewport?.(rows)} onOpen={(row, source) => props.onOpenAgent?.(row, source)} onRetry={props.onRetryAgents} onLoadMore={props.onLoadMoreAgents}/>)}
|
|
120
|
+
{(snapshot) => (<HomeAgentRoster query={props.agentQuery} onQueryChange={props.onAgentQueryChange} filterLabel={props.agentFilterLabel} activityFilter={props.agentActivityFilter} onSetActivityFilter={props.onSetAgentActivityFilter} onCycleMachine={props.onCycleAgentMachine} onToggleAttention={props.onToggleAgentAttention} theme={props.theme} width={bodyWidth()} height={rosterHeight()} snapshot={snapshot()} selection={props.agentSelection ?? { selectedKey: null, scrollOffset: 0 }} inputActive={props.agentInputActive ?? false} onSelect={(key) => props.onSelectAgent?.(key)} onMove={(delta) => props.onMoveAgent?.(delta)} onViewport={(rows) => props.onAgentViewport?.(rows)} onOpen={(row, source) => props.onOpenAgent?.(row, source)} onRetry={props.onRetryAgents} onLoadMore={props.onLoadMoreAgents}/>)}
|
|
120
121
|
</Show>
|
|
121
122
|
<Show when={activityHeight() > 0}>
|
|
122
123
|
<box width={bodyWidth()} height={activityHeight()} flexShrink={0} flexDirection="column" overflow="hidden">
|
|
@@ -6,13 +6,17 @@ import { useKeyboardRoute } from "./keyboard-router.jsx";
|
|
|
6
6
|
export function KeyHint(props) {
|
|
7
7
|
const palette = () => componentPalette(props.theme, props, "neutral");
|
|
8
8
|
const background = () => props.button
|
|
9
|
-
? props.
|
|
9
|
+
? props.selected
|
|
10
|
+
? props.theme.roles.selection.selection
|
|
11
|
+
: props.theme.roles.surfaces.panelRaised
|
|
10
12
|
: props.quiet && !props.focused && !props.hovered
|
|
11
13
|
? props.theme.roles.surfaces.panel
|
|
12
14
|
: palette().background;
|
|
13
|
-
const foreground = () => props.
|
|
14
|
-
? props.theme.roles.
|
|
15
|
-
:
|
|
15
|
+
const foreground = () => props.selected
|
|
16
|
+
? props.theme.roles.selection.selectionText
|
|
17
|
+
: props.quiet && !props.focused && !props.hovered
|
|
18
|
+
? props.theme.roles.text.muted
|
|
19
|
+
: palette().foreground;
|
|
16
20
|
const text = () => props.presentation ?? `${props.keys}${props.label ? ` ${props.label}` : ""}`;
|
|
17
21
|
const width = () => Math.max(1, Math.floor(props.width ?? terminalDisplayWidth(text()) + 2));
|
|
18
22
|
const content = () => clipTerminal(props.presentation ?? ` ${text()} `, width());
|
|
@@ -3,6 +3,7 @@ import { createEffect, createMemo, createSignal, For, Show } from "solid-js";
|
|
|
3
3
|
import type { SemanticThemeSnapshot } from "../theme.ts";
|
|
4
4
|
import { clipTerminal, terminalDisplayWidth } from "../terminal-text.ts";
|
|
5
5
|
import { NavigationRow } from "../ui/navigation-row.tsx";
|
|
6
|
+
import { KeyHint } from "../ui/key-hint.tsx";
|
|
6
7
|
import { TuiButton } from "../ui/button.tsx";
|
|
7
8
|
import { useKeyboardRoute } from "../ui/keyboard-router.tsx";
|
|
8
9
|
import {
|
|
@@ -24,6 +25,8 @@ export interface HomeAgentRosterProps {
|
|
|
24
25
|
readonly query?: string;
|
|
25
26
|
readonly onQueryChange?: (query: string) => void;
|
|
26
27
|
readonly filterLabel?: string;
|
|
28
|
+
readonly activityFilter?: "all" | "working" | "attention";
|
|
29
|
+
readonly onSetActivityFilter?: (value: "all" | "working" | "attention") => void;
|
|
27
30
|
readonly onCycleMachine?: () => void;
|
|
28
31
|
readonly onToggleAttention?: () => void;
|
|
29
32
|
readonly theme: SemanticThemeSnapshot;
|
|
@@ -69,13 +72,19 @@ export function HomeAgentRoster(props: HomeAgentRosterProps) {
|
|
|
69
72
|
(props.snapshot.truncatedSessions > 0 && props.onLoadMore),
|
|
70
73
|
);
|
|
71
74
|
const rowHeight = () => (width() >= 60 && height() >= 16 ? 2 : 1);
|
|
75
|
+
const filterRows = () => (props.onSetActivityFilter && width() < 70 ? 2 : 1);
|
|
72
76
|
const searchRows = () => (props.onQueryChange ? 2 : 0);
|
|
73
77
|
const visibleCount = () =>
|
|
74
|
-
Math.max(
|
|
75
|
-
|
|
78
|
+
Math.max(
|
|
79
|
+
0,
|
|
80
|
+
Math.floor(
|
|
81
|
+
(height() - 4 - filterRows() - searchRows() - (showRecovery() ? 1 : 0)) / rowHeight(),
|
|
82
|
+
),
|
|
83
|
+
);
|
|
84
|
+
const openSelected = (source: "keyboard" | "mouse" = "keyboard") => {
|
|
76
85
|
const row = props.snapshot.rows.find((row) => row.key === props.selection.selectedKey);
|
|
77
86
|
if (props.inputActive && visibleCount() > 0 && row?.paneId && !stale(row))
|
|
78
|
-
props.onOpen(row,
|
|
87
|
+
props.onOpen(row, source);
|
|
79
88
|
};
|
|
80
89
|
createEffect(() => props.onViewport(visibleCount()));
|
|
81
90
|
const offset = () =>
|
|
@@ -97,11 +106,13 @@ export function HomeAgentRoster(props: HomeAgentRosterProps) {
|
|
|
97
106
|
const session = width() >= 44 ? Math.floor(label * 0.43) : 0;
|
|
98
107
|
return { status, agent: label - session, session };
|
|
99
108
|
};
|
|
109
|
+
const filtered = () =>
|
|
110
|
+
Boolean(props.query || (props.activityFilter && props.activityFilter !== "all"));
|
|
100
111
|
const title = () => {
|
|
101
112
|
if (props.snapshot.phase === "unavailable") return "Agent overview unavailable";
|
|
102
113
|
if (props.snapshot.phase === "loading" && props.snapshot.rows.length === 0)
|
|
103
114
|
return "Loading agent overview…";
|
|
104
|
-
if (
|
|
115
|
+
if (filtered() && props.snapshot.rows.length === 0) return "No matching agents";
|
|
105
116
|
if (props.snapshot.phase === "live" && props.snapshot.rows.length === 0)
|
|
106
117
|
return "No agents reported";
|
|
107
118
|
const count = props.snapshot.rows.length;
|
|
@@ -140,7 +151,15 @@ export function HomeAgentRoster(props: HomeAgentRosterProps) {
|
|
|
140
151
|
const key = event.name.toLowerCase();
|
|
141
152
|
const filter =
|
|
142
153
|
!editing() &&
|
|
143
|
-
(key === "f"
|
|
154
|
+
(key === "f"
|
|
155
|
+
? props.onCycleMachine
|
|
156
|
+
: key === "a"
|
|
157
|
+
? props.onToggleAttention
|
|
158
|
+
: key === "w" && props.onSetActivityFilter
|
|
159
|
+
? () => props.onSetActivityFilter?.("working")
|
|
160
|
+
: key === "0" && props.onSetActivityFilter
|
|
161
|
+
? () => props.onSetActivityFilter?.("all")
|
|
162
|
+
: undefined);
|
|
144
163
|
if (filter) {
|
|
145
164
|
event.preventDefault();
|
|
146
165
|
event.stopPropagation();
|
|
@@ -204,37 +223,77 @@ export function HomeAgentRoster(props: HomeAgentRosterProps) {
|
|
|
204
223
|
{clipTerminal(title(), width())}
|
|
205
224
|
</text>
|
|
206
225
|
</box>
|
|
207
|
-
<box
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
226
|
+
<box
|
|
227
|
+
width={width()}
|
|
228
|
+
height={filterRows()}
|
|
229
|
+
flexShrink={0}
|
|
230
|
+
flexDirection={filterRows() > 1 ? "column" : "row"}
|
|
231
|
+
overflow="hidden"
|
|
232
|
+
>
|
|
233
|
+
<KeyHint
|
|
234
|
+
theme={props.theme}
|
|
235
|
+
keys="f"
|
|
236
|
+
label={props.filterLabel?.split(" · ")[0] ?? "All machines"}
|
|
237
|
+
width={
|
|
238
|
+
props.onSetActivityFilter && filterRows() === 1
|
|
239
|
+
? Math.max(1, width() - 47)
|
|
240
|
+
: props.onSetActivityFilter
|
|
241
|
+
? Math.min(width(), 28)
|
|
242
|
+
: Math.max(1, Math.min(28, width() - 16))
|
|
243
|
+
}
|
|
244
|
+
quiet
|
|
245
|
+
button
|
|
246
|
+
onPress={() => {
|
|
247
|
+
if (props.inputActive) props.onCycleMachine?.();
|
|
217
248
|
}}
|
|
249
|
+
/>
|
|
250
|
+
<box
|
|
251
|
+
height={1}
|
|
252
|
+
flexShrink={0}
|
|
253
|
+
flexDirection="row"
|
|
254
|
+
overflow="hidden"
|
|
255
|
+
width={Math.min(width(), 47)}
|
|
218
256
|
>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
257
|
+
<Show
|
|
258
|
+
when={props.onSetActivityFilter}
|
|
259
|
+
fallback={
|
|
260
|
+
<KeyHint
|
|
261
|
+
theme={props.theme}
|
|
262
|
+
keys="a"
|
|
263
|
+
label="Attention"
|
|
264
|
+
quiet
|
|
265
|
+
button
|
|
266
|
+
onPress={() => {
|
|
267
|
+
if (props.inputActive) props.onToggleAttention?.();
|
|
268
|
+
}}
|
|
269
|
+
/>
|
|
270
|
+
}
|
|
271
|
+
>
|
|
272
|
+
<For
|
|
273
|
+
each={
|
|
274
|
+
[
|
|
275
|
+
{ key: "0", label: "All", value: "all" },
|
|
276
|
+
{ key: "w", label: "Working", value: "working" },
|
|
277
|
+
{ key: "a", label: "Needs attention", value: "attention" },
|
|
278
|
+
] as const
|
|
279
|
+
}
|
|
280
|
+
>
|
|
281
|
+
{(filter) => (
|
|
282
|
+
<KeyHint
|
|
283
|
+
theme={props.theme}
|
|
284
|
+
keys={filter.key}
|
|
285
|
+
label={filter.value === "attention" && width() < 47 ? "Attention" : filter.label}
|
|
286
|
+
quiet
|
|
287
|
+
button
|
|
288
|
+
selected={(props.activityFilter ?? "all") === filter.value}
|
|
289
|
+
onPress={() => {
|
|
290
|
+
if (props.inputActive) props.onSetActivityFilter?.(filter.value);
|
|
291
|
+
}}
|
|
292
|
+
/>
|
|
293
|
+
)}
|
|
294
|
+
</For>
|
|
295
|
+
</Show>
|
|
296
|
+
</box>
|
|
238
297
|
</box>
|
|
239
298
|
<Show
|
|
240
299
|
when={props.snapshot.rows.length > 0}
|
|
@@ -247,10 +306,18 @@ export function HomeAgentRoster(props: HomeAgentRosterProps) {
|
|
|
247
306
|
>
|
|
248
307
|
<Show when={props.snapshot.phase === "live" && !props.query}>
|
|
249
308
|
<text height={1} width={width()} fg={props.theme.roles.text.primary}>
|
|
250
|
-
{clipTerminal(
|
|
309
|
+
{clipTerminal(
|
|
310
|
+
filtered() ? "No agents match these filters" : "Your next session starts here",
|
|
311
|
+
width(),
|
|
312
|
+
)}
|
|
251
313
|
</text>
|
|
252
314
|
<text height={1} width={width()} fg={props.theme.roles.text.muted}>
|
|
253
|
-
{clipTerminal(
|
|
315
|
+
{clipTerminal(
|
|
316
|
+
filtered()
|
|
317
|
+
? "Choose All or change the machine filter to see more agents."
|
|
318
|
+
: "Open terminals, or use Commands to connect a machine.",
|
|
319
|
+
width(),
|
|
320
|
+
)}
|
|
254
321
|
</text>
|
|
255
322
|
</Show>
|
|
256
323
|
</box>
|
|
@@ -280,6 +347,7 @@ export function HomeAgentRoster(props: HomeAgentRosterProps) {
|
|
|
280
347
|
backgroundColor={
|
|
281
348
|
componentPalette(props.theme, {
|
|
282
349
|
selected: props.selection.selectedKey === key,
|
|
350
|
+
hovered: hovered() === key,
|
|
283
351
|
disabled: row().paneId === null || stale(row()),
|
|
284
352
|
}).background
|
|
285
353
|
}
|
|
@@ -340,6 +408,7 @@ export function HomeAgentRoster(props: HomeAgentRosterProps) {
|
|
|
340
408
|
bg={
|
|
341
409
|
componentPalette(props.theme, {
|
|
342
410
|
selected: props.selection.selectedKey === key,
|
|
411
|
+
hovered: hovered() === key,
|
|
343
412
|
disabled: row().paneId === null || stale(row()),
|
|
344
413
|
}).background
|
|
345
414
|
}
|
|
@@ -373,7 +442,7 @@ export function HomeAgentRoster(props: HomeAgentRosterProps) {
|
|
|
373
442
|
onQueryChange()(applicationPaneRenamePaste(props.query ?? "", bytes))
|
|
374
443
|
}
|
|
375
444
|
onEditing={setEditing}
|
|
376
|
-
onSubmit={openSelected}
|
|
445
|
+
onSubmit={() => openSelected()}
|
|
377
446
|
/>
|
|
378
447
|
</box>
|
|
379
448
|
)}
|
|
@@ -381,9 +450,27 @@ export function HomeAgentRoster(props: HomeAgentRosterProps) {
|
|
|
381
450
|
<text width={width()} height={1} flexShrink={0} fg={props.theme.roles.text.muted}>
|
|
382
451
|
{clipTerminal(coverage(), width())}
|
|
383
452
|
</text>
|
|
384
|
-
<
|
|
385
|
-
|
|
386
|
-
|
|
453
|
+
<box width={width()} height={1} flexShrink={0} flexDirection="row" overflow="hidden">
|
|
454
|
+
<text
|
|
455
|
+
width={Math.min(
|
|
456
|
+
Math.max(0, width() - (footer().endsWith("Enter open") && width() >= 20 ? 12 : 0)),
|
|
457
|
+
terminalDisplayWidth(footer().replace(/ Enter open$/u, "")),
|
|
458
|
+
)}
|
|
459
|
+
fg={props.theme.roles.text.muted}
|
|
460
|
+
>
|
|
461
|
+
{clipTerminal(footer().replace(/ Enter open$/u, ""), width())}
|
|
462
|
+
</text>
|
|
463
|
+
<Show when={footer().endsWith("Enter open") && width() >= 20}>
|
|
464
|
+
<KeyHint
|
|
465
|
+
theme={props.theme}
|
|
466
|
+
keys="Enter"
|
|
467
|
+
label="open"
|
|
468
|
+
quiet
|
|
469
|
+
button
|
|
470
|
+
onPress={() => openSelected("mouse")}
|
|
471
|
+
/>
|
|
472
|
+
</Show>
|
|
473
|
+
</box>
|
|
387
474
|
<Show when={showRecovery()}>
|
|
388
475
|
<box height={1} width={width()} flexShrink={0} flexDirection="row" gap={1}>
|
|
389
476
|
<Show
|
|
@@ -11,6 +11,7 @@ import type { ApplicationHomeAgentPresentation } from "./application-home-agents
|
|
|
11
11
|
export interface HomeFleetFilter {
|
|
12
12
|
readonly machineId: string | null;
|
|
13
13
|
readonly attentionOnly: boolean;
|
|
14
|
+
readonly workingOnly?: boolean;
|
|
14
15
|
readonly query?: string;
|
|
15
16
|
}
|
|
16
17
|
export function projectHomeFleet(
|
|
@@ -54,6 +55,15 @@ export function projectHomeFleet(
|
|
|
54
55
|
!(agent.attention || agent.activity === "waiting" || agent.activity === "failed")
|
|
55
56
|
)
|
|
56
57
|
continue;
|
|
58
|
+
if (
|
|
59
|
+
filter.workingOnly &&
|
|
60
|
+
(agent.disabled ||
|
|
61
|
+
group?.available === false ||
|
|
62
|
+
unavailable.has(sessionKey) ||
|
|
63
|
+
machine.state !== "ready" ||
|
|
64
|
+
agent.activity !== "running")
|
|
65
|
+
)
|
|
66
|
+
continue;
|
|
57
67
|
const paneKey = agent.paneId
|
|
58
68
|
? JSON.stringify([machine.id, agent.server?.serverId, agent.daemonInstanceId, agent.paneId])
|
|
59
69
|
: agent.id;
|
|
@@ -166,7 +176,7 @@ export function createApplicationHomeFleetOwner(options: {
|
|
|
166
176
|
return options.inputActive();
|
|
167
177
|
},
|
|
168
178
|
get agentFilterLabel() {
|
|
169
|
-
return `${catalog().groups.find((group) => group.id === filter().machineId)?.label ?? "All machines"} · ${filter().attentionOnly ? "Needs attention" : "All agents"}`;
|
|
179
|
+
return `${catalog().groups.find((group) => group.id === filter().machineId)?.label ?? "All machines"} · ${filter().attentionOnly ? "Needs attention" : filter().workingOnly ? "Working" : "All agents"}`;
|
|
170
180
|
},
|
|
171
181
|
onCycleAgentMachine() {
|
|
172
182
|
const ids = [null, ...catalog().groups.map((group) => group.id)];
|
|
@@ -175,8 +185,18 @@ export function createApplicationHomeFleetOwner(options: {
|
|
|
175
185
|
machineId: ids[(ids.indexOf(value.machineId) + 1) % ids.length]!,
|
|
176
186
|
}));
|
|
177
187
|
},
|
|
188
|
+
get agentActivityFilter() {
|
|
189
|
+
return filter().attentionOnly ? "attention" : filter().workingOnly ? "working" : "all";
|
|
190
|
+
},
|
|
191
|
+
onSetAgentActivityFilter(activity) {
|
|
192
|
+
setFilter((value) => ({
|
|
193
|
+
...value,
|
|
194
|
+
attentionOnly: activity === "attention",
|
|
195
|
+
workingOnly: activity === "working",
|
|
196
|
+
}));
|
|
197
|
+
},
|
|
178
198
|
onToggleAgentAttention() {
|
|
179
|
-
setFilter((value) => ({ ...value, attentionOnly: !value.attentionOnly }));
|
|
199
|
+
setFilter((value) => ({ ...value, attentionOnly: !value.attentionOnly, workingOnly: false }));
|
|
180
200
|
},
|
|
181
201
|
onRetryAgents: () => options.agents.retry?.(),
|
|
182
202
|
onLoadMoreAgents: () => options.agents.loadMore?.(),
|
|
@@ -44,6 +44,8 @@ export interface ApplicationHomeSurfaceProps {
|
|
|
44
44
|
readonly agentQuery?: string;
|
|
45
45
|
readonly onAgentQueryChange?: (query: string) => void;
|
|
46
46
|
readonly agentFilterLabel?: string;
|
|
47
|
+
readonly agentActivityFilter?: "all" | "working" | "attention";
|
|
48
|
+
readonly onSetAgentActivityFilter?: (value: "all" | "working" | "attention") => void;
|
|
47
49
|
readonly onCycleAgentMachine?: () => void;
|
|
48
50
|
readonly onToggleAgentAttention?: () => void;
|
|
49
51
|
readonly agentRoster?: HomeAgentSnapshot;
|
|
@@ -74,6 +76,7 @@ export function ApplicationHomeSurface(props: ApplicationHomeSurfaceProps): JSX.
|
|
|
74
76
|
props.agentRoster?.phase === "live" &&
|
|
75
77
|
props.agentRoster.rows.length === 0 &&
|
|
76
78
|
!props.agentQuery &&
|
|
79
|
+
(!props.agentActivityFilter || props.agentActivityFilter === "all") &&
|
|
77
80
|
applicationHomeBrandVariant(bodyWidth(), height()) === "ascii";
|
|
78
81
|
const brandRows = () => (showAscii() ? APPLICATION_HOME_WORDMARK.length : 1);
|
|
79
82
|
const spacious = () => height() >= 14;
|
|
@@ -241,6 +244,8 @@ export function ApplicationHomeSurface(props: ApplicationHomeSurfaceProps): JSX.
|
|
|
241
244
|
query={props.agentQuery}
|
|
242
245
|
onQueryChange={props.onAgentQueryChange}
|
|
243
246
|
filterLabel={props.agentFilterLabel}
|
|
247
|
+
activityFilter={props.agentActivityFilter}
|
|
248
|
+
onSetActivityFilter={props.onSetAgentActivityFilter}
|
|
244
249
|
onCycleMachine={props.onCycleAgentMachine}
|
|
245
250
|
onToggleAttention={props.onToggleAgentAttention}
|
|
246
251
|
theme={props.theme}
|
|
@@ -23,14 +23,18 @@ export function KeyHint(props: KeyHintProps) {
|
|
|
23
23
|
const palette = () => componentPalette(props.theme, props, "neutral");
|
|
24
24
|
const background = () =>
|
|
25
25
|
props.button
|
|
26
|
-
? props.
|
|
26
|
+
? props.selected
|
|
27
|
+
? props.theme.roles.selection.selection
|
|
28
|
+
: props.theme.roles.surfaces.panelRaised
|
|
27
29
|
: props.quiet && !props.focused && !props.hovered
|
|
28
30
|
? props.theme.roles.surfaces.panel
|
|
29
31
|
: palette().background;
|
|
30
32
|
const foreground = () =>
|
|
31
|
-
props.
|
|
32
|
-
? props.theme.roles.
|
|
33
|
-
:
|
|
33
|
+
props.selected
|
|
34
|
+
? props.theme.roles.selection.selectionText
|
|
35
|
+
: props.quiet && !props.focused && !props.hovered
|
|
36
|
+
? props.theme.roles.text.muted
|
|
37
|
+
: palette().foreground;
|
|
34
38
|
const text = () => props.presentation ?? `${props.keys}${props.label ? ` ${props.label}` : ""}`;
|
|
35
39
|
const width = () => Math.max(1, Math.floor(props.width ?? terminalDisplayWidth(text()) + 2));
|
|
36
40
|
const content = () => clipTerminal(props.presentation ?? ` ${text()} `, width());
|