@xpufx/paseo-top 0.4.3 → 0.4.5
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/OVERVIEW.md +17 -0
- package/client/host-ui.tsx +184 -0
- package/client/multi-host-view.tsx +57 -43
- package/client/multi-host.ts +36 -13
- package/client/pill-labels.ts +124 -7
- package/client/pill.tsx +724 -669
- package/client/resources-query.ts +1 -1
- package/client/settings-events.ts +33 -0
- package/client/settings-ui.tsx +86 -9
- package/client/surface.tsx +310 -274
- package/client/telemetry-copy.ts +8 -2
- package/client/telemetry.tsx +372 -321
- package/client/turn-panel.tsx +126 -104
- package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +21 -61
- package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
- package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
- package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
- package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
- package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
- package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
- package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
- package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
- package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
- package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +118 -338
- package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
- package/client/vendor/paseo-plugin-helper/lifecycle/surface.tsx +140 -0
- package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
- package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
- package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
- package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +55 -47
- package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
- package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
- package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
- package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
- package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
- package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
- package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
- package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
- package/client/vendor/permission-audit/client.tsx +706 -0
- package/client/vendor/permission-audit/index.tsx +1 -0
- package/index.client.tsx +58 -37
- package/index.server.ts +256 -227
- package/package.json +14 -2
- package/paseo-plugin.json +2 -0
- package/screenshots/1-paseo-top-modal-system.jpg +0 -0
- package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
- package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
- package/screenshots/4-paseo-top-modal-about.jpg +0 -0
- package/screenshots/paseo-top-dashboard.jpg +0 -0
- package/screenshots/paseo-top-general-view.jpg +0 -0
- package/server/resources.ts +98 -0
- package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
- package/server/vendor/paseo-plugin-helper/index.ts +4 -0
- package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
- package/server/vendor/paseo-plugin-helper/mcp-injection.ts +20 -2
- package/server/vendor/paseo-plugin-helper/plugins.ts +184 -19
- package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
- package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
- package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
- package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
- package/server/vendor/paseo-plugin-helper/tickets.ts +1232 -0
- package/server/vendor/permission-audit/index.ts +1 -0
- package/server/vendor/permission-audit/server.ts +839 -0
- package/shared/helper-version.ts +20 -0
- package/shared/multi-host.ts +156 -155
- package/shared/resources.ts +146 -1
- package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
- package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
- package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
- package/shared/vendor/paseo-plugin-helper/index.ts +2 -0
- package/shared/vendor/paseo-plugin-helper/tickets.ts +1104 -0
- package/shared/vendor/permission-audit/filter.ts +61 -0
- package/shared/vendor/permission-audit/index.ts +2 -0
- package/shared/vendor/permission-audit/shared.ts +119 -0
- package/shared/version.ts +1 -1
- package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
- package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
- package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -156
- package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -184
- package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -225
- package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
- package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
- package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
- package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
- package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
- package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
- package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -75
- package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
- package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
- package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
- package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
- package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -118
- package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -215
- package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
- package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
- package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
- package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
- package/client/vendor/paseo-plugin-helper/components/index.ts +0 -25
- package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -224
- package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
- package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/index.ts +0 -29
- package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
- package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
- package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
- package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -378
- package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
- package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
- package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
- package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
- package/client/vendor/paseo-plugin-helper/surface.tsx +0 -80
- package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
- package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
- package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
- package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
- package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
- package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
- package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
- /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
- /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
package/OVERVIEW.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
Top adds real-time host resource telemetry and session performance metrics directly to the Paseo composer trackbar, conversation timeline, and an interactive dashboard modal.
|
|
2
|
+
|
|
3
|
+
The plugin displays live CPU and memory utilization, system load averages (1m, 5m, 15m), active agent turns, token consumption, and cost telemetry. An interactive dashboard modal provides visual resource gauges, multi-host fleet telemetry, workspace context, and configurable metric routing.
|
|
4
|
+
|
|
5
|
+
## Capabilities and Data Access
|
|
6
|
+
|
|
7
|
+
- **Host Resource Telemetry**: Samples CPU usage, memory utilization (including Linux `/proc/meminfo` available memory), load averages, host uptime, and core counts via local OS interfaces.
|
|
8
|
+
- **Agent and Turn Telemetry**: Monitors active agent session metadata (model, provider, turn counts, input/output/cached tokens, execution error counts) and can stamp a summary card into the session timeline upon turn completion.
|
|
9
|
+
- **Display Modes**: Supports Cycle Mode (rotates through metrics on a configurable interval), All-in-One Mode (unified ticker), and Multiple Mode (side-by-side pills).
|
|
10
|
+
- **Multi-Host Telemetry**: When configured with multiple daemon hosts, queries registered hosts bounded to one probe per host on a 15-second cadence with a 4-second timeout to report online/offline status and host latency.
|
|
11
|
+
- **Custom Metrics**: Supports user-defined metrics via declarative `.jsonc` definitions in scoped plugin storage (`~/.paseo/plugin-data/xpufx/top/pills/`) with optional periodic command execution and threshold status mapping.
|
|
12
|
+
|
|
13
|
+
## Setup and Requirements
|
|
14
|
+
|
|
15
|
+
- Runs on the Paseo daemon and requires Paseo 0.9.0 or later.
|
|
16
|
+
- Telemetry sampling runs locally on the daemon host (Linux, macOS, Windows).
|
|
17
|
+
- All plugin persistent configuration is stored within scoped plugin storage.
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Re-export the shared UI composition package for `plugins/top`
|
|
3
|
+
* (xpufx-org/paseo#976, #1218, #1258).
|
|
4
|
+
*
|
|
5
|
+
* All host-delegating cards, buttons, badges, inputs, form rows, action bars,
|
|
6
|
+
* tabs, collapsibles, code blocks, gauges, about sections, and theme providers
|
|
7
|
+
* come directly from `@xpufx/paseo-plugin-ui`.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from "react";
|
|
11
|
+
import {
|
|
12
|
+
HostModalSection as BaseHostModalSection,
|
|
13
|
+
ModalBodyScrollOwnerContext,
|
|
14
|
+
type HostModalSectionProps,
|
|
15
|
+
} from "@xpufx/paseo-plugin-ui";
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Top pill modal sections operate under the host single-scroller model
|
|
19
|
+
* (`hostScroll: true`, xpufx-org/paseo#975, #1054, #1258).
|
|
20
|
+
* Ensures fluid, unconstrained height when rendered with or without an outer
|
|
21
|
+
* host provider in testing harnesses.
|
|
22
|
+
*/
|
|
23
|
+
export function HostModalSection({ children, style, ...props }: HostModalSectionProps) {
|
|
24
|
+
return (
|
|
25
|
+
<ModalBodyScrollOwnerContext.Provider value="host">
|
|
26
|
+
<BaseHostModalSection style={style} {...props}>
|
|
27
|
+
{children}
|
|
28
|
+
</BaseHostModalSection>
|
|
29
|
+
</ModalBodyScrollOwnerContext.Provider>
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export {
|
|
34
|
+
HostAboutSection,
|
|
35
|
+
HostActionBar,
|
|
36
|
+
HostActionBar as ActionBar,
|
|
37
|
+
HostAttentionBeacon,
|
|
38
|
+
HostAttentionBeacon as AttentionBeacon,
|
|
39
|
+
HostBadge,
|
|
40
|
+
HostBadge as Badge,
|
|
41
|
+
HostButton,
|
|
42
|
+
HostButton as Button,
|
|
43
|
+
HostCard,
|
|
44
|
+
HostCard as Card,
|
|
45
|
+
HostCardHeader,
|
|
46
|
+
HostCardHeader as CardHeader,
|
|
47
|
+
HostCodeBlock,
|
|
48
|
+
HostCodeBlock as CodeBlock,
|
|
49
|
+
HostCollapsible,
|
|
50
|
+
HostCollapsible as Collapsible,
|
|
51
|
+
HostCommandBox,
|
|
52
|
+
HostCommandBox as CommandBox,
|
|
53
|
+
HostCopyButton,
|
|
54
|
+
HostEmptyState,
|
|
55
|
+
HostEmptyState as EmptyState,
|
|
56
|
+
HostFormRow,
|
|
57
|
+
HostFormRow as FormRow,
|
|
58
|
+
HostGrid,
|
|
59
|
+
HostHighlightedText,
|
|
60
|
+
HostHighlightedText as HighlightedText,
|
|
61
|
+
HostInlineButton,
|
|
62
|
+
HostInlineButton as InlineButton,
|
|
63
|
+
HostKeyValue,
|
|
64
|
+
HostKeyValue as KeyValue,
|
|
65
|
+
HostKeyValueGroup,
|
|
66
|
+
HostKeyValueGroup as KeyValueGroup,
|
|
67
|
+
HostLayoutProvider,
|
|
68
|
+
HostMetricGauge,
|
|
69
|
+
HostModalBody,
|
|
70
|
+
HostModalBody as ModalBody,
|
|
71
|
+
HostProgressBar,
|
|
72
|
+
HostRow,
|
|
73
|
+
HostScroll,
|
|
74
|
+
HostSearchInput,
|
|
75
|
+
HostSectionHeader,
|
|
76
|
+
HostSelect,
|
|
77
|
+
HostSelect as Select,
|
|
78
|
+
HostStack,
|
|
79
|
+
HostStatusDot,
|
|
80
|
+
HostStatusDot as StatusDot,
|
|
81
|
+
HostTabs,
|
|
82
|
+
HostTabs as Tabs,
|
|
83
|
+
HostTextInput,
|
|
84
|
+
HostTextInput as TextInput,
|
|
85
|
+
HostThemeProvider,
|
|
86
|
+
HostToggle,
|
|
87
|
+
HostToggle as Toggle,
|
|
88
|
+
HostVital,
|
|
89
|
+
ModalBodyScrollOwnerContext,
|
|
90
|
+
alpha,
|
|
91
|
+
getStatusColor,
|
|
92
|
+
sanitizeFluidStyle,
|
|
93
|
+
useHostLayout,
|
|
94
|
+
useHostTheme,
|
|
95
|
+
useModalBodyScrollOwner,
|
|
96
|
+
} from "@xpufx/paseo-plugin-ui";
|
|
97
|
+
|
|
98
|
+
export type {
|
|
99
|
+
HighlightOptions,
|
|
100
|
+
HighlightPart,
|
|
101
|
+
HostAboutLink,
|
|
102
|
+
HostAboutSectionProps,
|
|
103
|
+
HostActionBarProps,
|
|
104
|
+
HostActionBarProps as ActionBarProps,
|
|
105
|
+
HostAttentionBeaconMode,
|
|
106
|
+
HostAttentionBeaconMode as AttentionBeaconMode,
|
|
107
|
+
HostAttentionBeaconProps,
|
|
108
|
+
HostAttentionBeaconProps as AttentionBeaconProps,
|
|
109
|
+
HostAttentionBeaconTone,
|
|
110
|
+
HostAttentionBeaconTone as AttentionBeaconTone,
|
|
111
|
+
HostBadgeProps,
|
|
112
|
+
HostBadgeProps as BadgeProps,
|
|
113
|
+
HostBadgeSize,
|
|
114
|
+
HostBadgeSize as BadgeSize,
|
|
115
|
+
HostBadgeStyle,
|
|
116
|
+
HostBadgeStyle as BadgeStyle,
|
|
117
|
+
HostButtonProps,
|
|
118
|
+
HostButtonProps as ButtonProps,
|
|
119
|
+
HostButtonSize,
|
|
120
|
+
HostButtonSize as ButtonSize,
|
|
121
|
+
HostButtonVariant,
|
|
122
|
+
HostButtonVariant as ButtonVariant,
|
|
123
|
+
HostCardHeaderProps,
|
|
124
|
+
HostCardHeaderProps as CardHeaderProps,
|
|
125
|
+
HostCardProps,
|
|
126
|
+
HostCardProps as CardProps,
|
|
127
|
+
HostCodeBlockProps,
|
|
128
|
+
HostCodeBlockProps as CodeBlockProps,
|
|
129
|
+
HostCollapsibleProps,
|
|
130
|
+
HostCollapsibleProps as CollapsibleProps,
|
|
131
|
+
HostCommandBoxProps,
|
|
132
|
+
HostCommandBoxProps as CommandBoxProps,
|
|
133
|
+
HostCopyButtonProps,
|
|
134
|
+
HostCopyButtonSize,
|
|
135
|
+
HostCopyButtonVariant,
|
|
136
|
+
HostEmptyStateProps,
|
|
137
|
+
HostEmptyStateProps as EmptyStateProps,
|
|
138
|
+
HostFormRowProps,
|
|
139
|
+
HostFormRowProps as FormRowProps,
|
|
140
|
+
HostGridProps,
|
|
141
|
+
HostHighlightedTextProps,
|
|
142
|
+
HostInlineButtonProps,
|
|
143
|
+
HostInlineButtonProps as InlineButtonProps,
|
|
144
|
+
HostKeyValueGroupProps,
|
|
145
|
+
HostKeyValueGroupProps as KeyValueGroupProps,
|
|
146
|
+
HostKeyValueProps,
|
|
147
|
+
HostKeyValueProps as KeyValueProps,
|
|
148
|
+
HostKeyValueTruncateMode,
|
|
149
|
+
HostKeyValueTruncateMode as KeyValueTruncateMode,
|
|
150
|
+
HostLayoutProviderProps,
|
|
151
|
+
HostMetricGaugeProps,
|
|
152
|
+
HostModalBodyProps,
|
|
153
|
+
HostModalBodyProps as ModalBodyProps,
|
|
154
|
+
HostModalSectionProps,
|
|
155
|
+
HostProgressBarProps,
|
|
156
|
+
HostRowProps,
|
|
157
|
+
HostScrollProps,
|
|
158
|
+
HostSearchInputProps,
|
|
159
|
+
HostSectionHeaderProps,
|
|
160
|
+
HostSelectOption,
|
|
161
|
+
HostSelectProps,
|
|
162
|
+
HostSelectProps as SelectProps,
|
|
163
|
+
HostStackProps,
|
|
164
|
+
HostStatusDotProps,
|
|
165
|
+
HostStatusDotProps as StatusDotProps,
|
|
166
|
+
HostSurfaceVariant,
|
|
167
|
+
HostTabItem,
|
|
168
|
+
HostTabsProps,
|
|
169
|
+
HostTabsProps as TabsProps,
|
|
170
|
+
HostTextInputProps,
|
|
171
|
+
HostTextInputProps as TextInputProps,
|
|
172
|
+
HostThemeProviderProps,
|
|
173
|
+
HostToggleProps,
|
|
174
|
+
HostToggleProps as ToggleProps,
|
|
175
|
+
HostVitalProps,
|
|
176
|
+
ModalBodyScrollOwner,
|
|
177
|
+
PluginTheme,
|
|
178
|
+
PluginUiTheme,
|
|
179
|
+
PluginUiTheme as TopTheme,
|
|
180
|
+
ResponsiveLayout,
|
|
181
|
+
ScrollViewInstance,
|
|
182
|
+
StatusVariant,
|
|
183
|
+
ThemeColors,
|
|
184
|
+
} from "@xpufx/paseo-plugin-ui";
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
import React, { useMemo } from "react";
|
|
2
2
|
import { Text } from "react-native";
|
|
3
3
|
import {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
4
|
+
HostBadge,
|
|
5
|
+
HostCard,
|
|
6
|
+
HostCardHeader,
|
|
7
|
+
HostEmptyState,
|
|
8
|
+
HostGrid,
|
|
9
|
+
HostKeyValue,
|
|
10
|
+
HostRow,
|
|
11
|
+
HostSectionHeader,
|
|
12
|
+
HostStack,
|
|
13
|
+
HostStatusDot,
|
|
14
|
+
useHostLayout,
|
|
15
|
+
useHostTheme,
|
|
16
|
+
} from "./host-ui";
|
|
16
17
|
import {
|
|
17
18
|
aggregateFleet,
|
|
18
19
|
type FleetHostSnapshot,
|
|
19
20
|
type FleetHostStatus,
|
|
20
21
|
} from "../shared/multi-host";
|
|
21
|
-
import { useFleetPolling } from "./multi-host";
|
|
22
|
+
import { multiHostSupported, useFleetPolling } from "./multi-host";
|
|
22
23
|
|
|
23
24
|
const STATUS_VARIANT: Record<
|
|
24
25
|
FleetHostStatus,
|
|
@@ -59,7 +60,8 @@ function FleetHostCard({
|
|
|
59
60
|
snapshot: FleetHostSnapshot;
|
|
60
61
|
now: number;
|
|
61
62
|
}) {
|
|
62
|
-
const { colors } =
|
|
63
|
+
const { colors } = useHostTheme();
|
|
64
|
+
const { compact } = useHostLayout();
|
|
63
65
|
const variant = STATUS_VARIANT[snapshot.status];
|
|
64
66
|
const staleAge = formatStaleAge(snapshot.staleAt, now);
|
|
65
67
|
const subtitle =
|
|
@@ -68,47 +70,47 @@ function FleetHostCard({
|
|
|
68
70
|
: (staleAge ?? snapshot.error ?? "Not connected");
|
|
69
71
|
|
|
70
72
|
return (
|
|
71
|
-
<
|
|
72
|
-
<
|
|
73
|
+
<HostCard variant="elevated">
|
|
74
|
+
<HostCardHeader
|
|
73
75
|
icon="Server"
|
|
74
76
|
title={snapshot.label}
|
|
75
77
|
subtitle={subtitle}
|
|
76
78
|
badge={
|
|
77
|
-
<
|
|
78
|
-
<
|
|
79
|
+
<HostRow gap={6} align="center">
|
|
80
|
+
<HostStatusDot
|
|
79
81
|
variant={snapshot.status === "online" ? "success" : variant}
|
|
80
82
|
pulse={snapshot.status === "online"}
|
|
81
83
|
/>
|
|
82
|
-
<
|
|
84
|
+
<HostBadge
|
|
83
85
|
label={STATUS_LABEL[snapshot.status]}
|
|
84
86
|
variant={variant}
|
|
85
87
|
size="sm"
|
|
86
88
|
/>
|
|
87
|
-
</
|
|
89
|
+
</HostRow>
|
|
88
90
|
}
|
|
89
91
|
/>
|
|
90
|
-
<
|
|
91
|
-
<
|
|
92
|
-
<
|
|
93
|
-
<
|
|
92
|
+
<HostGrid columns={compact ? 1 : 2} gap={12}>
|
|
93
|
+
<HostKeyValue label="Host" value={snapshot.serverId} truncate="middle" copyable />
|
|
94
|
+
<HostKeyValue label="Latency" value={formatLatency(snapshot.latencyMs)} />
|
|
95
|
+
<HostKeyValue
|
|
94
96
|
label="Agents"
|
|
95
97
|
value={snapshot.counts ? String(snapshot.counts.agents) : "--"}
|
|
96
98
|
/>
|
|
97
|
-
<
|
|
99
|
+
<HostKeyValue
|
|
98
100
|
label="Active agents"
|
|
99
101
|
value={snapshot.counts ? String(snapshot.counts.activeAgents) : "--"}
|
|
100
102
|
/>
|
|
101
|
-
<
|
|
103
|
+
<HostKeyValue
|
|
102
104
|
label="Workspaces"
|
|
103
105
|
value={snapshot.counts ? String(snapshot.counts.workspaces) : "--"}
|
|
104
106
|
/>
|
|
105
|
-
</
|
|
107
|
+
</HostGrid>
|
|
106
108
|
{snapshot.status === "error" && snapshot.error ? (
|
|
107
109
|
<Text style={{ marginTop: 8, fontSize: 11, color: colors.statusDanger }}>
|
|
108
110
|
{snapshot.error}
|
|
109
111
|
</Text>
|
|
110
112
|
) : null}
|
|
111
|
-
</
|
|
113
|
+
</HostCard>
|
|
112
114
|
);
|
|
113
115
|
}
|
|
114
116
|
|
|
@@ -122,14 +124,26 @@ function FleetHostCard({
|
|
|
122
124
|
* Top behavior is unaffected whenever the tab is not selected.
|
|
123
125
|
*/
|
|
124
126
|
export function FleetView() {
|
|
125
|
-
const { colors } =
|
|
127
|
+
const { colors } = useHostTheme();
|
|
126
128
|
const snapshots = useFleetPolling();
|
|
127
129
|
const now = Date.now();
|
|
128
130
|
const aggregate = useMemo(() => aggregateFleet(snapshots), [snapshots]);
|
|
129
131
|
|
|
132
|
+
// A host that does not supply `useHosts`/`getPaseoClient` (the mobile bundle)
|
|
133
|
+
// cannot borrow remote clients. Say so instead of throwing.
|
|
134
|
+
if (!multiHostSupported()) {
|
|
135
|
+
return (
|
|
136
|
+
<HostEmptyState
|
|
137
|
+
icon="Server"
|
|
138
|
+
title="Multi-host fleet unavailable"
|
|
139
|
+
description="This host build does not provide multi-host access. Open Paseo on desktop to see other hosts."
|
|
140
|
+
/>
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
|
|
130
144
|
if (snapshots.length === 0) {
|
|
131
145
|
return (
|
|
132
|
-
<
|
|
146
|
+
<HostEmptyState
|
|
133
147
|
icon="Server"
|
|
134
148
|
title="No other hosts"
|
|
135
149
|
description="Configure additional hosts in Paseo to see their fleet status here."
|
|
@@ -138,33 +152,33 @@ export function FleetView() {
|
|
|
138
152
|
}
|
|
139
153
|
|
|
140
154
|
return (
|
|
141
|
-
<
|
|
155
|
+
<HostStack gap={12}>
|
|
142
156
|
<Text style={{ fontSize: 16, fontWeight: "700", color: colors.foreground }}>
|
|
143
157
|
Multi-Host Fleet
|
|
144
158
|
</Text>
|
|
145
159
|
|
|
146
|
-
<
|
|
147
|
-
<
|
|
160
|
+
<HostCard variant="elevated">
|
|
161
|
+
<HostCardHeader
|
|
148
162
|
title="Fleet Totals"
|
|
149
163
|
icon="Activity"
|
|
150
164
|
subtitle="Counts only. CPU/RAM/load stay per-host."
|
|
151
165
|
/>
|
|
152
|
-
<
|
|
153
|
-
<
|
|
154
|
-
<
|
|
166
|
+
<HostGrid columns={2} gap={12}>
|
|
167
|
+
<HostKeyValue label="Hosts" value={`${aggregate.responsive}/${aggregate.hosts} responsive`} />
|
|
168
|
+
<HostKeyValue
|
|
155
169
|
label="Online / Stale"
|
|
156
170
|
value={`${aggregate.online} / ${aggregate.stale}`}
|
|
157
171
|
/>
|
|
158
|
-
<
|
|
159
|
-
<
|
|
172
|
+
<HostKeyValue label="Agents" value={String(aggregate.counts.agents)} />
|
|
173
|
+
<HostKeyValue
|
|
160
174
|
label="Active agents"
|
|
161
175
|
value={String(aggregate.counts.activeAgents)}
|
|
162
176
|
/>
|
|
163
|
-
<
|
|
164
|
-
</
|
|
165
|
-
</
|
|
177
|
+
<HostKeyValue label="Workspaces" value={String(aggregate.counts.workspaces)} />
|
|
178
|
+
</HostGrid>
|
|
179
|
+
</HostCard>
|
|
166
180
|
|
|
167
|
-
<
|
|
181
|
+
<HostSectionHeader title="Hosts" count={snapshots.length} />
|
|
168
182
|
{snapshots.map((snapshot) => (
|
|
169
183
|
<FleetHostCard
|
|
170
184
|
key={snapshot.serverId}
|
|
@@ -172,6 +186,6 @@ export function FleetView() {
|
|
|
172
186
|
now={now}
|
|
173
187
|
/>
|
|
174
188
|
))}
|
|
175
|
-
</
|
|
189
|
+
</HostStack>
|
|
176
190
|
);
|
|
177
191
|
}
|
package/client/multi-host.ts
CHANGED
|
@@ -1,24 +1,43 @@
|
|
|
1
|
-
import { getPaseoClient, useHosts } from "@getpaseo/plugin/client";
|
|
2
1
|
import { useEffect, useMemo, useRef, useState } from "react";
|
|
3
2
|
import {
|
|
4
|
-
|
|
3
|
+
getOptionalPaseoClient,
|
|
4
|
+
isMultiHostSupported,
|
|
5
|
+
useOptionalHosts,
|
|
6
|
+
} from "./vendor/paseo-plugin-helper/lifecycle/index";
|
|
7
|
+
import {
|
|
8
|
+
createMultiHostPoller,
|
|
9
|
+
type MultiHostPoller,
|
|
5
10
|
type FleetHostClient,
|
|
6
11
|
type FleetHostCounts,
|
|
7
12
|
type FleetHostSnapshot,
|
|
8
13
|
} from "../shared/multi-host";
|
|
9
14
|
|
|
10
15
|
/**
|
|
11
|
-
*
|
|
16
|
+
* True when the running host supplies the multi-host primitives.
|
|
17
|
+
*
|
|
18
|
+
* Feature detection lives in `paseo-plugin-helper/lifecycle` so top and
|
|
19
|
+
* x-comms share one seam; this wrapper keeps the Fleet tab's local name.
|
|
20
|
+
*/
|
|
21
|
+
export function multiHostSupported(): boolean {
|
|
22
|
+
return isMultiHostSupported();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Wrap a borrowed `getOptionalPaseoClient(serverId)` as a fleet client.
|
|
12
27
|
*
|
|
13
28
|
* Only counts are read. The borrowed API exposes agents/workspaces/projects/
|
|
14
29
|
* terminals/providers/config and no host-metrics RPC, so the fleet view cannot
|
|
15
30
|
* show remote CPU/RAM/load/uptime; fabricating them would be mock data.
|
|
16
31
|
*/
|
|
17
32
|
export function createFleetClient(serverId: string): FleetHostClient {
|
|
18
|
-
//
|
|
19
|
-
// acquires for online hosts, and the throw is caught per host so one bad
|
|
20
|
-
// host cannot break the others.
|
|
21
|
-
|
|
33
|
+
// getOptionalPaseoClient throws for a non-online/unknown host; the poller
|
|
34
|
+
// only acquires for online hosts, and the throw is caught per host so one bad
|
|
35
|
+
// host cannot break the others. It returns undefined only when the host omits
|
|
36
|
+
// the seam entirely, which the poller never reaches.
|
|
37
|
+
const client = getOptionalPaseoClient(serverId);
|
|
38
|
+
if (!client) {
|
|
39
|
+
throw new Error("multi-host fleet is not available on this host");
|
|
40
|
+
}
|
|
22
41
|
return {
|
|
23
42
|
async readCounts(): Promise<FleetHostCounts> {
|
|
24
43
|
const [agents, workspaces] = await Promise.all([
|
|
@@ -47,15 +66,19 @@ export interface UseFleetPollingOptions {
|
|
|
47
66
|
/**
|
|
48
67
|
* Bounded multi-host fleet polling hook.
|
|
49
68
|
*
|
|
50
|
-
* Enumerates online hosts with `
|
|
51
|
-
* `
|
|
69
|
+
* Enumerates online hosts with `useOptionalHosts()`, acquires a borrowed
|
|
70
|
+
* `getOptionalPaseoClient(serverId)` per online host, and drives the pure
|
|
52
71
|
* {@link MultiHostPoller} state machine: max one in-flight probe per host,
|
|
53
72
|
* 15s cadence, 4s per-host timeout, stale marking, and dispose/reacquire on
|
|
54
73
|
* host status transitions. Local Top behavior is untouched because nothing
|
|
55
74
|
* starts unless the fleet view is mounted and enabled.
|
|
56
75
|
*/
|
|
57
76
|
export function useFleetPolling({ enabled = true }: UseFleetPollingOptions = {}) {
|
|
58
|
-
|
|
77
|
+
// The mobile host omits the primitives; nothing to poll, so no hooks run and
|
|
78
|
+
// the caller renders an unavailable state. Both checks are module-constant,
|
|
79
|
+
// so the hook order is stable for a given host.
|
|
80
|
+
const supported = isMultiHostSupported();
|
|
81
|
+
const hosts = useOptionalHosts();
|
|
59
82
|
const [snapshots, setSnapshots] = useState<FleetHostSnapshot[]>([]);
|
|
60
83
|
const pollerRef = useRef<MultiHostPoller | null>(null);
|
|
61
84
|
|
|
@@ -75,8 +98,8 @@ export function useFleetPolling({ enabled = true }: UseFleetPollingOptions = {})
|
|
|
75
98
|
hostInputsRef.current = hostInputs;
|
|
76
99
|
|
|
77
100
|
useEffect(() => {
|
|
78
|
-
if (!enabled) return;
|
|
79
|
-
const poller =
|
|
101
|
+
if (!enabled || !supported) return;
|
|
102
|
+
const poller = createMultiHostPoller({
|
|
80
103
|
acquire: createFleetClient,
|
|
81
104
|
onUpdate: setSnapshots,
|
|
82
105
|
});
|
|
@@ -88,7 +111,7 @@ export function useFleetPolling({ enabled = true }: UseFleetPollingOptions = {})
|
|
|
88
111
|
pollerRef.current = null;
|
|
89
112
|
setSnapshots([]);
|
|
90
113
|
};
|
|
91
|
-
}, [enabled]);
|
|
114
|
+
}, [enabled, supported]);
|
|
92
115
|
|
|
93
116
|
useEffect(() => {
|
|
94
117
|
pollerRef.current?.setHosts(hostInputs);
|
package/client/pill-labels.ts
CHANGED
|
@@ -40,7 +40,8 @@ export type PillItemType =
|
|
|
40
40
|
| "changes"
|
|
41
41
|
| "tokens"
|
|
42
42
|
| "tools"
|
|
43
|
-
| "turns"
|
|
43
|
+
| "turns"
|
|
44
|
+
| "velocity";
|
|
44
45
|
|
|
45
46
|
export interface SegmentSnapshot {
|
|
46
47
|
data?: SystemResources;
|
|
@@ -56,6 +57,20 @@ export interface SegmentSnapshot {
|
|
|
56
57
|
worktreeLocationText?: string;
|
|
57
58
|
}
|
|
58
59
|
|
|
60
|
+
/**
|
|
61
|
+
* First non-blank string wins, else the fallback. `??` only guards null and
|
|
62
|
+
* undefined, so a present-but-empty host field (blank `title`, `provider`,
|
|
63
|
+
* `branch`, `agentId`) slipped through as an empty label. On a button-shaped
|
|
64
|
+
* host an empty label renders icon-only, which reads as the pill collapsing to
|
|
65
|
+
* a skinny/empty line (xpufx-org/paseo#507).
|
|
66
|
+
*/
|
|
67
|
+
function firstNonBlank(fallback: string, ...values: Array<string | null | undefined>): string {
|
|
68
|
+
for (const value of values) {
|
|
69
|
+
if (typeof value === "string" && value.trim().length > 0) return value;
|
|
70
|
+
}
|
|
71
|
+
return fallback;
|
|
72
|
+
}
|
|
73
|
+
|
|
59
74
|
export interface TokenMetrics {
|
|
60
75
|
inputTokens?: number;
|
|
61
76
|
outputTokens?: number;
|
|
@@ -66,6 +81,9 @@ export interface TokenMetrics {
|
|
|
66
81
|
costUsd?: number;
|
|
67
82
|
}
|
|
68
83
|
|
|
84
|
+
/** Context pressure level classification. */
|
|
85
|
+
export type ContextPressureLevel = "normal" | "warning" | "high";
|
|
86
|
+
|
|
69
87
|
export function formatCompactTokens(n: number): string {
|
|
70
88
|
if (!Number.isFinite(n) || n <= 0) return "0";
|
|
71
89
|
if (n >= 1_000_000) {
|
|
@@ -79,6 +97,28 @@ export function formatCompactTokens(n: number): string {
|
|
|
79
97
|
return `${Math.round(n)}`;
|
|
80
98
|
}
|
|
81
99
|
|
|
100
|
+
export function formatTurnTokensPerSecond(
|
|
101
|
+
outputTokens: number | undefined,
|
|
102
|
+
durationMs: number | undefined,
|
|
103
|
+
): string | null {
|
|
104
|
+
if (
|
|
105
|
+
outputTokens == null ||
|
|
106
|
+
durationMs == null ||
|
|
107
|
+
!Number.isFinite(outputTokens) ||
|
|
108
|
+
!Number.isFinite(durationMs) ||
|
|
109
|
+
outputTokens <= 0 ||
|
|
110
|
+
durationMs <= 0
|
|
111
|
+
) {
|
|
112
|
+
return null;
|
|
113
|
+
}
|
|
114
|
+
const rate = outputTokens / (durationMs / 1000);
|
|
115
|
+
if (!Number.isFinite(rate) || rate <= 0) return null;
|
|
116
|
+
if (rate < 10) {
|
|
117
|
+
return `${parseFloat(rate.toFixed(1))} tok/s`;
|
|
118
|
+
}
|
|
119
|
+
return `${Math.round(rate)} tok/s`;
|
|
120
|
+
}
|
|
121
|
+
|
|
82
122
|
export function extractTokenMetrics(snap: SegmentSnapshot): TokenMetrics | null {
|
|
83
123
|
const live = snap.data?.liveUsage;
|
|
84
124
|
const last = snap.data?.lastTurn;
|
|
@@ -262,28 +302,38 @@ export function describeSegment(
|
|
|
262
302
|
const prefix = def?.shortLabel ? `${def.shortLabel} ` : "";
|
|
263
303
|
switch (item) {
|
|
264
304
|
case "branch":
|
|
265
|
-
return {
|
|
305
|
+
return {
|
|
306
|
+
icon,
|
|
307
|
+
iconTone: "accent",
|
|
308
|
+
text: firstNonBlank("--", data?.branch),
|
|
309
|
+
tone: "foreground",
|
|
310
|
+
};
|
|
266
311
|
case "worktree":
|
|
267
312
|
return {
|
|
268
313
|
icon,
|
|
269
314
|
iconTone: "accent",
|
|
270
|
-
text:
|
|
315
|
+
text: firstNonBlank("--", worktreeLocationText),
|
|
271
316
|
tone: "foreground",
|
|
272
317
|
};
|
|
273
318
|
case "agent_title":
|
|
274
|
-
return {
|
|
319
|
+
return {
|
|
320
|
+
icon,
|
|
321
|
+
iconTone: "accent",
|
|
322
|
+
text: firstNonBlank("Agent", agent?.title),
|
|
323
|
+
tone: "foreground",
|
|
324
|
+
};
|
|
275
325
|
case "agent":
|
|
276
326
|
return {
|
|
277
327
|
icon,
|
|
278
328
|
iconTone: "accent",
|
|
279
|
-
text: agent?.model
|
|
329
|
+
text: firstNonBlank("Agent", agent?.model, agent?.provider),
|
|
280
330
|
tone: "foreground",
|
|
281
331
|
};
|
|
282
332
|
case "agent_provider":
|
|
283
333
|
return {
|
|
284
334
|
icon,
|
|
285
335
|
iconTone: "accent",
|
|
286
|
-
text:
|
|
336
|
+
text: firstNonBlank("Provider", agent?.provider),
|
|
287
337
|
tone: "foreground",
|
|
288
338
|
};
|
|
289
339
|
case "agent_activity": {
|
|
@@ -299,7 +349,7 @@ export function describeSegment(
|
|
|
299
349
|
return {
|
|
300
350
|
icon,
|
|
301
351
|
iconTone: "accent",
|
|
302
|
-
text: agentId && agentId.length > 7 ? agentId.slice(0, 7) :
|
|
352
|
+
text: agentId && agentId.length > 7 ? agentId.slice(0, 7) : firstNonBlank("--", agentId),
|
|
303
353
|
tone: "foreground",
|
|
304
354
|
};
|
|
305
355
|
case "load":
|
|
@@ -395,6 +445,70 @@ export function describeSegment(
|
|
|
395
445
|
tone: "foreground",
|
|
396
446
|
};
|
|
397
447
|
}
|
|
448
|
+
case "velocity": {
|
|
449
|
+
// Pull velocity from liveUsage (real-time) or lastTurn (post-turn).
|
|
450
|
+
const tps =
|
|
451
|
+
data?.tokensPerSecond ?? data?.lastTurn?.tokensPerSecond;
|
|
452
|
+
const ctxPct =
|
|
453
|
+
data?.contextUtilizationPct ?? data?.lastTurn?.contextUtilizationPct;
|
|
454
|
+
const ctxLevel =
|
|
455
|
+
data?.contextPressureLevel ?? data?.lastTurn?.contextPressureLevel;
|
|
456
|
+
|
|
457
|
+
if (tps == null || !Number.isFinite(tps)) {
|
|
458
|
+
return {
|
|
459
|
+
icon,
|
|
460
|
+
iconTone: "none",
|
|
461
|
+
prefix,
|
|
462
|
+
prefixTone: "muted",
|
|
463
|
+
text: "--",
|
|
464
|
+
tone: "muted",
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// Cache hit rate from liveUsage.
|
|
469
|
+
const live = data?.liveUsage;
|
|
470
|
+
let cachePct: number | undefined;
|
|
471
|
+
if (
|
|
472
|
+
live &&
|
|
473
|
+
live.inputTokens != null &&
|
|
474
|
+
live.cachedInputTokens != null &&
|
|
475
|
+
live.inputTokens > 0
|
|
476
|
+
) {
|
|
477
|
+
cachePct = Math.round((live.cachedInputTokens / live.inputTokens) * 100);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Build label: ⚡ N tok/s · M% cache
|
|
481
|
+
let text = `${Math.round(tps)} tok/s`;
|
|
482
|
+
let tone: SegmentTone = "success";
|
|
483
|
+
if (cachePct != null) {
|
|
484
|
+
text += ` · ${cachePct}% cache`;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Context pressure badge as trailing.
|
|
488
|
+
let trailing: string | undefined;
|
|
489
|
+
let trailingTone: SegmentTone = "foreground";
|
|
490
|
+
if (ctxLevel === "high") {
|
|
491
|
+
trailing = "⚠️ high ctx";
|
|
492
|
+
trailingTone = "danger";
|
|
493
|
+
tone = "danger";
|
|
494
|
+
} else if (ctxLevel === "warning") {
|
|
495
|
+
trailing = "⚠️ warn ctx";
|
|
496
|
+
trailingTone = "warning";
|
|
497
|
+
tone = "warning";
|
|
498
|
+
} else if (ctxPct != null) {
|
|
499
|
+
trailing = `${ctxPct}% ctx`;
|
|
500
|
+
trailingTone = "foreground";
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return {
|
|
504
|
+
icon,
|
|
505
|
+
iconTone: "accent",
|
|
506
|
+
text,
|
|
507
|
+
tone,
|
|
508
|
+
trailing,
|
|
509
|
+
trailingTone,
|
|
510
|
+
};
|
|
511
|
+
}
|
|
398
512
|
case "cpu_ram":
|
|
399
513
|
default: {
|
|
400
514
|
const ram =
|
|
@@ -458,6 +572,9 @@ export function enabledItemsForSettings(settings: TopSettings): PillItemType[] {
|
|
|
458
572
|
if (settings.metricSurfaces && isPillEnabled(settings.metricSurfaces.turns)) {
|
|
459
573
|
items.push("turns");
|
|
460
574
|
}
|
|
575
|
+
if (settings.metricSurfaces && isPillEnabled(settings.metricSurfaces.velocity)) {
|
|
576
|
+
items.push("velocity");
|
|
577
|
+
}
|
|
461
578
|
return items;
|
|
462
579
|
}
|
|
463
580
|
|