@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/client/pill.tsx
CHANGED
|
@@ -7,48 +7,52 @@ import {
|
|
|
7
7
|
useRpc,
|
|
8
8
|
type PluginClientContext,
|
|
9
9
|
} from "@getpaseo/plugin/client";
|
|
10
|
+
import { Icon } from "@getpaseo/plugin/client/react-native";
|
|
10
11
|
import {
|
|
11
12
|
registerComposerPill,
|
|
12
13
|
registerSidebarSurface,
|
|
13
|
-
|
|
14
|
+
triggerHaptic,
|
|
14
15
|
type SidebarSurfaceRegistrar,
|
|
15
16
|
type PillLiveContext,
|
|
16
17
|
type RegisterComposerPillOptions,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
Row,
|
|
22
|
-
KeyValue,
|
|
23
|
-
KeyValueGroup,
|
|
24
|
-
ProgressBar,
|
|
25
|
-
MetricGauge,
|
|
26
|
-
Tabs,
|
|
27
|
-
Toggle,
|
|
28
|
-
Badge,
|
|
29
|
-
Icon,
|
|
30
|
-
AboutSection,
|
|
31
|
-
CustomPillBody,
|
|
32
|
-
CustomPillModalContent,
|
|
18
|
+
type RenderModalProps,
|
|
19
|
+
} from "./vendor/paseo-plugin-helper/lifecycle/index";
|
|
20
|
+
import {
|
|
21
|
+
getClientHost,
|
|
33
22
|
useRpcQuery,
|
|
34
23
|
usePluginSettings,
|
|
35
|
-
|
|
24
|
+
type ComposerPillRegistrar,
|
|
25
|
+
type HostPillProps,
|
|
26
|
+
} from "./vendor/paseo-plugin-helper/core/index";
|
|
27
|
+
import {
|
|
28
|
+
HostAboutSection,
|
|
29
|
+
HostBadge,
|
|
30
|
+
HostButton,
|
|
31
|
+
HostCard,
|
|
32
|
+
HostCardHeader,
|
|
33
|
+
HostCopyButton,
|
|
34
|
+
HostKeyValue,
|
|
35
|
+
HostLayoutProvider,
|
|
36
|
+
HostMetricGauge,
|
|
37
|
+
HostModalSection,
|
|
38
|
+
HostProgressBar,
|
|
39
|
+
HostRow,
|
|
40
|
+
HostStack,
|
|
41
|
+
HostTabs,
|
|
42
|
+
HostThemeProvider,
|
|
43
|
+
HostToggle,
|
|
36
44
|
getStatusColor,
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
type RenderPillProps,
|
|
41
|
-
type KeyValueProps,
|
|
42
|
-
type CardHeaderProps,
|
|
43
|
-
type BadgeProps,
|
|
44
|
-
} from "./vendor/paseo-plugin-helper/index";
|
|
45
|
+
useHostLayout,
|
|
46
|
+
useHostTheme,
|
|
47
|
+
} from "./host-ui";
|
|
45
48
|
import {
|
|
46
49
|
formatBytes,
|
|
47
50
|
formatUptime,
|
|
48
51
|
resolveMetricStatus,
|
|
49
52
|
type MetricThresholds,
|
|
50
|
-
type
|
|
53
|
+
type ThemeColors,
|
|
51
54
|
} from "../shared/vendor/paseo-plugin-helper/index";
|
|
55
|
+
import { PermissionAuditView } from "./vendor/permission-audit";
|
|
52
56
|
import {
|
|
53
57
|
getSystemResourcesRpc,
|
|
54
58
|
topSettingsContract,
|
|
@@ -56,14 +60,18 @@ import {
|
|
|
56
60
|
listCustomPillsRpc,
|
|
57
61
|
runCustomPillModalCommandRpc,
|
|
58
62
|
isPillEnabled,
|
|
59
|
-
|
|
63
|
+
isTimelineEnabled,
|
|
60
64
|
legacyFlagView,
|
|
61
65
|
isMcpSurfaceEnabled,
|
|
62
66
|
customPillEffectiveEnabled,
|
|
63
67
|
METRIC_DEFINITIONS,
|
|
64
68
|
DEFAULT_METRIC_SURFACES,
|
|
65
|
-
checkboxesFromTarget,
|
|
66
69
|
targetFromCheckboxes,
|
|
70
|
+
CPU_THRESHOLDS,
|
|
71
|
+
MEM_THRESHOLDS,
|
|
72
|
+
MODAL_TABS,
|
|
73
|
+
aboutExtraItems,
|
|
74
|
+
hasAnyLegacyPillFlagEnabled,
|
|
67
75
|
type SystemResources,
|
|
68
76
|
type TopSettings,
|
|
69
77
|
type CustomPillStateOutput,
|
|
@@ -72,8 +80,9 @@ import {
|
|
|
72
80
|
} from "../shared/resources";
|
|
73
81
|
import { PLUGIN_VERSION } from "../shared/version";
|
|
74
82
|
import { TopDashboardSurface } from "./surface";
|
|
83
|
+
import { addSettingsListener, removeSettingsListener, notifySettingsChanged } from "./settings-events";
|
|
75
84
|
import { useTopResourceQuery, useCustomPillsQuery } from "./resources-query";
|
|
76
|
-
import { ChoiceChips } from "./settings-ui";
|
|
85
|
+
import { ChoiceChips, MultiChoiceChips } from "./settings-ui";
|
|
77
86
|
import {
|
|
78
87
|
buildAllLabel,
|
|
79
88
|
describeSegment,
|
|
@@ -91,6 +100,11 @@ import {
|
|
|
91
100
|
|
|
92
101
|
const EMPTY_PARAMS = {};
|
|
93
102
|
|
|
103
|
+
const TIMELINE_CADENCE_OPTIONS = Array.from({ length: 11 }, (_, n) => ({
|
|
104
|
+
id: n,
|
|
105
|
+
label: n === 0 ? "Never" : n === 1 ? "Every turn" : `${n}`,
|
|
106
|
+
}));
|
|
107
|
+
|
|
94
108
|
function formatWorktreeLocation(dir: string | null | undefined): string {
|
|
95
109
|
if (!dir) return "";
|
|
96
110
|
// Check for Paseo-managed worktree: ~/.paseo/worktrees/<hash>/<slug>
|
|
@@ -134,24 +148,9 @@ function formatIdleDuration(isoString: string | null | undefined): string {
|
|
|
134
148
|
return `${days}d ${hours % 24}h`;
|
|
135
149
|
}
|
|
136
150
|
|
|
137
|
-
const CPU_THRESHOLDS: MetricThresholds = { warning: 60, danger: 85 };
|
|
138
|
-
const MEM_THRESHOLDS: MetricThresholds = { warning: 70, danger: 85 };
|
|
139
|
-
|
|
140
|
-
const TABS = [
|
|
141
|
-
{ id: "system", label: "System", shortLabel: "System", icon: "Activity" },
|
|
142
|
-
{ id: "context", label: "Workspace", shortLabel: "Workspace", icon: "GitBranch" },
|
|
143
|
-
{ id: "settings", label: "Settings", shortLabel: "Settings", icon: "Sliders" },
|
|
144
|
-
{ id: "about", label: "About", shortLabel: "About", icon: "Info" },
|
|
145
|
-
];
|
|
146
|
-
|
|
147
|
-
const TIMELINE_CADENCE_OPTIONS = Array.from({ length: 11 }, (_, n) => ({
|
|
148
|
-
id: n,
|
|
149
|
-
label: n === 0 ? "Never" : n === 1 ? "Every turn" : `${n}`,
|
|
150
|
-
}));
|
|
151
|
-
|
|
152
151
|
function getMetricColors(
|
|
153
152
|
data: SystemResources | undefined,
|
|
154
|
-
colors:
|
|
153
|
+
colors: ThemeColors,
|
|
155
154
|
) {
|
|
156
155
|
if (
|
|
157
156
|
!data ||
|
|
@@ -177,7 +176,7 @@ function getMetricColors(
|
|
|
177
176
|
// so pill call sites keep a single import path.
|
|
178
177
|
export type { PillItemType } from "./pill-labels";
|
|
179
178
|
|
|
180
|
-
export type ModalTab = "system" | "context" | "settings" | "about";
|
|
179
|
+
export type ModalTab = "system" | "context" | "permissions" | "settings" | "about";
|
|
181
180
|
|
|
182
181
|
export function getItemTab(item: PillItemType): "system" | "context" {
|
|
183
182
|
switch (item) {
|
|
@@ -189,6 +188,7 @@ export function getItemTab(item: PillItemType): "system" | "context" {
|
|
|
189
188
|
case "tokens":
|
|
190
189
|
case "tools":
|
|
191
190
|
case "turns":
|
|
191
|
+
case "velocity":
|
|
192
192
|
return "system";
|
|
193
193
|
case "branch":
|
|
194
194
|
case "worktree":
|
|
@@ -203,28 +203,15 @@ export function getItemTab(item: PillItemType): "system" | "context" {
|
|
|
203
203
|
|
|
204
204
|
const currentCycleTabByAgent = new Map<string, ModalTab>();
|
|
205
205
|
|
|
206
|
-
// The 0.8 host popover is a narrow column, so
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
/>
|
|
216
|
-
);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
function CompactCardHeader(props: CardHeaderProps) {
|
|
220
|
-
return (
|
|
221
|
-
<CardHeader titleStyle={styles.compactCardTitle} {...props} />
|
|
222
|
-
);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
function CompactBadge(props: BadgeProps) {
|
|
226
|
-
return <Badge textStyle={styles.compactBadgeText} {...props} />;
|
|
227
|
-
}
|
|
206
|
+
// The 0.8 host popover is a narrow column, so modal-path text renders a few
|
|
207
|
+
// points under the ui/ adapter defaults. These spread props apply that
|
|
208
|
+
// compact rhythm at each call site.
|
|
209
|
+
const compactKvProps = {
|
|
210
|
+
labelStyle: { fontSize: 9 },
|
|
211
|
+
valueStyle: { fontSize: 10 },
|
|
212
|
+
} as const;
|
|
213
|
+
const compactCardTitleProp = { titleStyle: { fontSize: 11 } } as const;
|
|
214
|
+
const compactBadgeProp = { textStyle: { fontSize: 9 } } as const;
|
|
228
215
|
|
|
229
216
|
interface LiveSnapshot extends SegmentSnapshot {
|
|
230
217
|
workspaceDirectory?: string | null;
|
|
@@ -352,7 +339,16 @@ async function customPillSnapshot(): Promise<CustomPillStateOutput[]> {
|
|
|
352
339
|
* per-metric pills, custom metric pills) so the presentation model cannot
|
|
353
340
|
* diverge: all are centered surfaces with a pinned popover width. Call sites
|
|
354
341
|
* only describe what is pill-specific.
|
|
342
|
+
*
|
|
343
|
+
* `onError` is wired here once for every variant: the helper reports
|
|
344
|
+
* registration and resolveLabel failures through `options.onError` in addition
|
|
345
|
+
* to its own console warning, so without this default a failing pill RPC left
|
|
346
|
+
* the pill missing with no trace outside the helper log. The toast is
|
|
347
|
+
* throttled per pill+agent so a persistently failing RPC cannot spam the host.
|
|
355
348
|
*/
|
|
349
|
+
const PILL_ERROR_TOAST_THROTTLE_MS = 60_000;
|
|
350
|
+
const lastPillErrorToastAt = new Map<string, number>();
|
|
351
|
+
|
|
356
352
|
function registerTopPill(
|
|
357
353
|
client: ComposerPillRegistrar | PluginClientContext,
|
|
358
354
|
options: Omit<RegisterComposerPillOptions<ModalTab>, "presentation" | "popoverWidth"> & {
|
|
@@ -362,8 +358,29 @@ function registerTopPill(
|
|
|
362
358
|
) {
|
|
363
359
|
return registerComposerPill<ModalTab>(client as ComposerPillRegistrar, {
|
|
364
360
|
popoverWidth: 360,
|
|
361
|
+
// The host `<Modal.Content scrollable>` is the one scroll owner (#219,
|
|
362
|
+
// #975, #1054); the plugin body renders fluid content with no nested
|
|
363
|
+
// scroller so the host owns the mobile gesture.
|
|
364
|
+
hostScroll: true,
|
|
365
365
|
...options,
|
|
366
366
|
presentation: options.presentation ?? "centered",
|
|
367
|
+
onError:
|
|
368
|
+
options.onError ??
|
|
369
|
+
(({ agentId, workspaceId, error }) => {
|
|
370
|
+
console.error(
|
|
371
|
+
`[top] pill "${options.id}" failed (agent ${agentId}, workspace ${workspaceId}): ${error.message}`,
|
|
372
|
+
);
|
|
373
|
+
const key = `${options.id}:${agentId}`;
|
|
374
|
+
const now = Date.now();
|
|
375
|
+
if (now - (lastPillErrorToastAt.get(key) ?? 0) >= PILL_ERROR_TOAST_THROTTLE_MS) {
|
|
376
|
+
lastPillErrorToastAt.set(key, now);
|
|
377
|
+
try {
|
|
378
|
+
getClientHost().useToast().error?.(`top pill failed: ${error.message}`);
|
|
379
|
+
} catch {
|
|
380
|
+
// No host toast wired; the console error above is the trace.
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}),
|
|
367
384
|
});
|
|
368
385
|
}
|
|
369
386
|
|
|
@@ -390,7 +407,7 @@ function PillItemContent({
|
|
|
390
407
|
worktreeLocationText,
|
|
391
408
|
isOpen,
|
|
392
409
|
}: PillItemContentProps) {
|
|
393
|
-
const { colors } =
|
|
410
|
+
const { colors } = useHostTheme();
|
|
394
411
|
const { cpuColor, memColor } = getMetricColors(data, colors);
|
|
395
412
|
const descriptor = describeSegment(item, { data, agent, agentId, worktreeLocationText });
|
|
396
413
|
const toneColor = (tone: SegmentTone): string => {
|
|
@@ -415,7 +432,7 @@ function PillItemContent({
|
|
|
415
432
|
};
|
|
416
433
|
|
|
417
434
|
return (
|
|
418
|
-
<
|
|
435
|
+
<HostRow gap={4} align="center" style={styles.pillContainer}>
|
|
419
436
|
{descriptor.iconTone !== "none" ? (
|
|
420
437
|
<Icon name={descriptor.icon} size={12} color={toneColor(descriptor.iconTone)} />
|
|
421
438
|
) : null}
|
|
@@ -444,45 +461,27 @@ function PillItemContent({
|
|
|
444
461
|
</Text>
|
|
445
462
|
) : null}
|
|
446
463
|
</Text>
|
|
447
|
-
</
|
|
464
|
+
</HostRow>
|
|
448
465
|
);
|
|
449
466
|
}
|
|
450
467
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
let lastNotifiedSettings: TopSettings | null = null;
|
|
454
|
-
|
|
455
|
-
export function notifySettingsChanged(settings: TopSettings) {
|
|
456
|
-
if (!shouldEmitSnapshotUpdate(
|
|
457
|
-
lastNotifiedSettings as unknown as Record<string, unknown> | null,
|
|
458
|
-
settings as unknown as Record<string, unknown>,
|
|
459
|
-
)) {
|
|
460
|
-
return;
|
|
461
|
-
}
|
|
462
|
-
lastNotifiedSettings = settings;
|
|
463
|
-
for (const listener of settingsListeners) {
|
|
464
|
-
try {
|
|
465
|
-
listener(settings);
|
|
466
|
-
} catch {
|
|
467
|
-
// Ignore listener errors
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
}
|
|
468
|
+
export { notifySettingsChanged } from "./settings-events";
|
|
469
|
+
type SettingsListener = import("./settings-events").SettingsListener;
|
|
471
470
|
|
|
472
471
|
function PillOffline() {
|
|
473
|
-
const { colors } =
|
|
472
|
+
const { colors } = useHostTheme();
|
|
474
473
|
return (
|
|
475
|
-
<
|
|
474
|
+
<HostRow gap={4} align="center" style={styles.pillContainer}>
|
|
476
475
|
<Icon name="Ghost" size={13} color={colors.statusDanger} />
|
|
477
476
|
<Text numberOfLines={1} style={[styles.pillText, { color: colors.foregroundMuted }]}>
|
|
478
477
|
Offline
|
|
479
478
|
</Text>
|
|
480
|
-
</
|
|
479
|
+
</HostRow>
|
|
481
480
|
);
|
|
482
481
|
}
|
|
483
482
|
|
|
484
483
|
function PillLoading() {
|
|
485
|
-
const { colors } =
|
|
484
|
+
const { colors } = useHostTheme();
|
|
486
485
|
return (
|
|
487
486
|
<Text numberOfLines={1} style={[styles.pillText, { color: colors.foregroundMuted }]}>
|
|
488
487
|
top...
|
|
@@ -491,19 +490,19 @@ function PillLoading() {
|
|
|
491
490
|
}
|
|
492
491
|
|
|
493
492
|
function PillEmpty() {
|
|
494
|
-
const { colors } =
|
|
493
|
+
const { colors } = useHostTheme();
|
|
495
494
|
return (
|
|
496
|
-
<
|
|
495
|
+
<HostRow gap={4} align="center" style={styles.pillContainer}>
|
|
497
496
|
<Icon name="Activity" size={12} color={colors.accent} />
|
|
498
497
|
<Text numberOfLines={1} style={[styles.pillText, { color: colors.foregroundMuted }]}>
|
|
499
498
|
top
|
|
500
499
|
</Text>
|
|
501
|
-
</
|
|
500
|
+
</HostRow>
|
|
502
501
|
);
|
|
503
502
|
}
|
|
504
503
|
|
|
505
504
|
function McpLoadingPill() {
|
|
506
|
-
const { colors } =
|
|
505
|
+
const { colors } = useHostTheme();
|
|
507
506
|
return (
|
|
508
507
|
<Text numberOfLines={1} style={[styles.pillText, { color: colors.foregroundMuted }]}>
|
|
509
508
|
MCP…
|
|
@@ -521,9 +520,8 @@ interface PillSegmentProps {
|
|
|
521
520
|
}
|
|
522
521
|
|
|
523
522
|
/**
|
|
524
|
-
* The
|
|
525
|
-
*
|
|
526
|
-
* segment must not nest a second interaction handler.
|
|
523
|
+
* The segment must not nest a second interaction handler: on the host-rendered
|
|
524
|
+
* pill it sits inside the host's own pressable.
|
|
527
525
|
*/
|
|
528
526
|
function PillSegment({
|
|
529
527
|
item,
|
|
@@ -562,9 +560,9 @@ function AllInOnePill({
|
|
|
562
560
|
worktreeLocationText,
|
|
563
561
|
isOpen,
|
|
564
562
|
}: AllInOnePillProps) {
|
|
565
|
-
const { colors } =
|
|
563
|
+
const { colors } = useHostTheme();
|
|
566
564
|
return (
|
|
567
|
-
<
|
|
565
|
+
<HostRow gap={6} align="center" style={styles.allInOneContainer}>
|
|
568
566
|
{items.map((item, idx) => (
|
|
569
567
|
<React.Fragment key={item}>
|
|
570
568
|
{idx > 0 && (
|
|
@@ -580,16 +578,41 @@ function AllInOnePill({
|
|
|
580
578
|
/>
|
|
581
579
|
</React.Fragment>
|
|
582
580
|
))}
|
|
583
|
-
</
|
|
581
|
+
</HostRow>
|
|
584
582
|
);
|
|
585
583
|
}
|
|
586
584
|
|
|
587
|
-
|
|
585
|
+
/**
|
|
586
|
+
* Props the pill body views used to receive from the helper's `renderPill`
|
|
587
|
+
* option, which the Paseo 0.9 host shape removed: the host renders the pill
|
|
588
|
+
* body itself, so nothing wires these views any more. The shape is kept
|
|
589
|
+
* because `pill-render.test.tsx` still regression-tests their mobile layout
|
|
590
|
+
* (#507) and that coverage is worth more than the dead wiring.
|
|
591
|
+
*/
|
|
592
|
+
interface PillBodyProps extends HostPillProps {
|
|
593
|
+
isOpen: boolean;
|
|
594
|
+
open(payload?: ModalTab): void;
|
|
595
|
+
close(): void;
|
|
596
|
+
toggle(payload?: ModalTab): void;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export interface SingleItemPillViewProps extends PillBodyProps {
|
|
588
600
|
item: PillItemType;
|
|
589
601
|
defaultTab?: ModalTab;
|
|
590
602
|
}
|
|
591
603
|
|
|
592
|
-
export function SingleItemPillView({
|
|
604
|
+
export function SingleItemPillView(props: SingleItemPillViewProps) {
|
|
605
|
+
// Legacy pill body view (see PillBodyProps): kept for the #507 render
|
|
606
|
+
// regression tests. The host theme prop flows through HostThemeProvider so
|
|
607
|
+
// the ui/ adapters inside resolve real host colors.
|
|
608
|
+
return (
|
|
609
|
+
<HostThemeProvider theme={props.theme}>
|
|
610
|
+
<SingleItemPillViewBody {...props} />
|
|
611
|
+
</HostThemeProvider>
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
function SingleItemPillViewBody({
|
|
593
616
|
item,
|
|
594
617
|
workspaceId,
|
|
595
618
|
agentId,
|
|
@@ -669,7 +692,18 @@ export function SingleItemPillView({
|
|
|
669
692
|
);
|
|
670
693
|
}
|
|
671
694
|
|
|
672
|
-
function PillView(
|
|
695
|
+
function PillView(props: PillBodyProps) {
|
|
696
|
+
// Legacy pill body view (see PillBodyProps): kept for the #507 render
|
|
697
|
+
// regression tests. The host theme prop flows through HostThemeProvider so
|
|
698
|
+
// the ui/ adapters inside resolve real host colors.
|
|
699
|
+
return (
|
|
700
|
+
<HostThemeProvider theme={props.theme}>
|
|
701
|
+
<PillViewBody {...props} />
|
|
702
|
+
</HostThemeProvider>
|
|
703
|
+
);
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
function PillViewBody({ isOpen, open, workspaceId, agentId }: PillBodyProps) {
|
|
673
707
|
// No background settings poll here: the hook re-verifies on mount and
|
|
674
708
|
// window focus, and mutations invalidate the shared settings cache.
|
|
675
709
|
// Polling from every mounted pill/modal/surface multiplied daemon reads
|
|
@@ -691,18 +725,7 @@ function PillView({ isOpen, open, workspaceId, agentId }: RenderPillProps<ModalT
|
|
|
691
725
|
lastUsage: a?.lastUsage,
|
|
692
726
|
}));
|
|
693
727
|
|
|
694
|
-
const hasAnyEnabled =
|
|
695
|
-
flags.showCpuRam ||
|
|
696
|
-
flags.showBranch ||
|
|
697
|
-
flags.showWorktree ||
|
|
698
|
-
flags.showAgentTitle ||
|
|
699
|
-
flags.showAgent ||
|
|
700
|
-
flags.showAgentProvider ||
|
|
701
|
-
flags.showAgentActivity ||
|
|
702
|
-
flags.showAgentId ||
|
|
703
|
-
flags.showLoad ||
|
|
704
|
-
flags.showUptime ||
|
|
705
|
-
(flags.showMcp);
|
|
728
|
+
const hasAnyEnabled = hasAnyLegacyPillFlagEnabled(flags) || flags.showMcp;
|
|
706
729
|
|
|
707
730
|
// If no items are selected, fallback to CPU & RAM without mutating saved settings
|
|
708
731
|
const effectiveShowCpuRam = flags.showCpuRam || !hasAnyEnabled;
|
|
@@ -789,6 +812,9 @@ function PillView({ isOpen, open, workspaceId, agentId }: RenderPillProps<ModalT
|
|
|
789
812
|
if (settings.metricSurfaces && isPillEnabled(settings.metricSurfaces.turns)) {
|
|
790
813
|
items.push("turns");
|
|
791
814
|
}
|
|
815
|
+
if (settings.metricSurfaces && isPillEnabled(settings.metricSurfaces.velocity)) {
|
|
816
|
+
items.push("velocity");
|
|
817
|
+
}
|
|
792
818
|
|
|
793
819
|
// Legacy render fallback only. On the supported 0.8 host the pill is
|
|
794
820
|
// host-rendered from `resolveLabel`, which advances the cycle on the shared
|
|
@@ -844,12 +870,11 @@ interface ResourceModalProps extends RenderModalProps<ModalTab> {
|
|
|
844
870
|
initialTab?: ModalTab;
|
|
845
871
|
}
|
|
846
872
|
|
|
847
|
-
function
|
|
873
|
+
function MetricPillSelector({
|
|
848
874
|
settings,
|
|
849
875
|
updateSettings,
|
|
850
876
|
notifySettingsChanged,
|
|
851
877
|
mcpInstalled,
|
|
852
|
-
mcpRunning,
|
|
853
878
|
customPills,
|
|
854
879
|
customOverrides,
|
|
855
880
|
onCustomToggle,
|
|
@@ -858,141 +883,119 @@ function MetricSurfaceMatrix({
|
|
|
858
883
|
updateSettings: (updates: Partial<TopSettings>) => void;
|
|
859
884
|
notifySettingsChanged: (s: TopSettings) => void;
|
|
860
885
|
mcpInstalled: boolean;
|
|
861
|
-
mcpRunning?: boolean | null;
|
|
862
886
|
customPills: Array<{ id: string; title: string; sourceFile?: string; enabled: boolean }>;
|
|
863
887
|
customOverrides: Record<string, boolean> | undefined;
|
|
864
888
|
onCustomToggle: (id: string, val: boolean) => void;
|
|
865
889
|
}) {
|
|
866
|
-
const { colors
|
|
890
|
+
const { colors } = useHostTheme();
|
|
867
891
|
const surfaces = settings.metricSurfaces ?? DEFAULT_METRIC_SURFACES;
|
|
868
|
-
const
|
|
869
|
-
|
|
870
|
-
const
|
|
871
|
-
|
|
872
|
-
|
|
892
|
+
const masterOn = settings.showCustomPills ?? true;
|
|
893
|
+
|
|
894
|
+
const builtInOptions = METRIC_DEFINITIONS.map((def) => ({
|
|
895
|
+
id: def.id,
|
|
896
|
+
label: def.title,
|
|
897
|
+
icon: def.icon,
|
|
898
|
+
disabled: def.id === "mcp" && !mcpInstalled,
|
|
899
|
+
}));
|
|
900
|
+
|
|
901
|
+
const selectedBuiltIns = METRIC_DEFINITIONS
|
|
902
|
+
.filter((def) => isPillEnabled(surfaces[def.id]))
|
|
903
|
+
.map((def) => def.id);
|
|
904
|
+
|
|
905
|
+
const toggleMetric = (metricId: MetricId) => {
|
|
906
|
+
triggerHaptic("light");
|
|
907
|
+
const currentPill = isPillEnabled(surfaces[metricId]);
|
|
908
|
+
const currentTimeline = isTimelineEnabled(surfaces[metricId]);
|
|
909
|
+
const next = {
|
|
910
|
+
...surfaces,
|
|
911
|
+
[metricId]: targetFromCheckboxes(!currentPill, currentTimeline),
|
|
912
|
+
};
|
|
913
|
+
const nextSettings = { ...settings, metricSurfaces: next };
|
|
873
914
|
updateSettings({ metricSurfaces: next });
|
|
874
|
-
notifySettingsChanged(
|
|
915
|
+
notifySettingsChanged(nextSettings);
|
|
875
916
|
};
|
|
917
|
+
|
|
918
|
+
const selectAll = () => {
|
|
919
|
+
triggerHaptic("medium");
|
|
920
|
+
const next = { ...surfaces };
|
|
921
|
+
for (const def of METRIC_DEFINITIONS) {
|
|
922
|
+
if (def.id === "mcp" && !mcpInstalled) continue;
|
|
923
|
+
const currentTimeline = isTimelineEnabled(surfaces[def.id]);
|
|
924
|
+
next[def.id] = targetFromCheckboxes(true, currentTimeline);
|
|
925
|
+
}
|
|
926
|
+
const nextSettings = { ...settings, metricSurfaces: next };
|
|
927
|
+
updateSettings({ metricSurfaces: next });
|
|
928
|
+
notifySettingsChanged(nextSettings);
|
|
929
|
+
};
|
|
930
|
+
|
|
931
|
+
const clearAll = () => {
|
|
932
|
+
triggerHaptic("medium");
|
|
933
|
+
const next = { ...surfaces };
|
|
934
|
+
for (const def of METRIC_DEFINITIONS) {
|
|
935
|
+
const currentTimeline = isTimelineEnabled(surfaces[def.id]);
|
|
936
|
+
next[def.id] = targetFromCheckboxes(false, currentTimeline);
|
|
937
|
+
}
|
|
938
|
+
const nextSettings = { ...settings, metricSurfaces: next };
|
|
939
|
+
updateSettings({ metricSurfaces: next });
|
|
940
|
+
notifySettingsChanged(nextSettings);
|
|
941
|
+
};
|
|
942
|
+
|
|
876
943
|
return (
|
|
877
|
-
<
|
|
878
|
-
<
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
944
|
+
<HostStack gap={10}>
|
|
945
|
+
<MultiChoiceChips
|
|
946
|
+
options={builtInOptions}
|
|
947
|
+
selected={selectedBuiltIns}
|
|
948
|
+
onToggle={toggleMetric}
|
|
949
|
+
onSelectAll={selectAll}
|
|
950
|
+
onClearAll={clearAll}
|
|
951
|
+
/>
|
|
952
|
+
{customPills.length > 0 && (
|
|
953
|
+
<HostStack gap={6}>
|
|
954
|
+
<Text style={{ fontSize: 10, fontWeight: "600", color: colors.foregroundMuted }}>
|
|
955
|
+
Custom Metric Pills
|
|
888
956
|
</Text>
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
setTarget(
|
|
911
|
-
def.id,
|
|
912
|
-
targetFromCheckboxes(nextBoxes.pill, nextBoxes.timeline),
|
|
913
|
-
);
|
|
914
|
-
};
|
|
915
|
-
return (
|
|
916
|
-
<View
|
|
917
|
-
key={def.id}
|
|
918
|
-
style={[
|
|
919
|
-
styles.metricMatrixRow,
|
|
920
|
-
isCompact && styles.metricMatrixRowCompact,
|
|
921
|
-
{ minHeight: matrixRowMinHeight },
|
|
922
|
-
index < METRIC_DEFINITIONS.length - 1
|
|
923
|
-
? {
|
|
924
|
-
borderBottomWidth: 1,
|
|
925
|
-
borderBottomColor: colors.border,
|
|
926
|
-
}
|
|
927
|
-
: undefined,
|
|
928
|
-
]}
|
|
929
|
-
>
|
|
930
|
-
<View style={styles.metricMatrixLabel}>
|
|
931
|
-
<Text style={[styles.metricMatrixTitle, { color: colors.foreground }]}>
|
|
932
|
-
{def.title}
|
|
933
|
-
</Text>
|
|
934
|
-
{note ? (
|
|
935
|
-
<Text style={[styles.metricMatrixNote, { color: colors.foregroundMuted }]}>
|
|
936
|
-
{note}
|
|
937
|
-
</Text>
|
|
938
|
-
) : null}
|
|
939
|
-
</View>
|
|
940
|
-
<View style={[styles.metricMatrixTargets, isCompact && styles.metricMatrixTargetsCompact]}>
|
|
941
|
-
<Toggle
|
|
942
|
-
value={boxes.pill}
|
|
943
|
-
disabled={disabled}
|
|
944
|
-
onValueChange={(val) => setBox("pill", val)}
|
|
945
|
-
style={styles.matrixToggle}
|
|
946
|
-
/>
|
|
947
|
-
<Toggle
|
|
948
|
-
value={boxes.timeline && !def.pillOnly}
|
|
949
|
-
disabled={disabled || timelineDisabled}
|
|
950
|
-
onValueChange={(val) => setBox("timeline", val)}
|
|
951
|
-
style={styles.matrixToggle}
|
|
952
|
-
/>
|
|
953
|
-
</View>
|
|
954
|
-
</View>
|
|
955
|
-
);
|
|
956
|
-
})}
|
|
957
|
-
{customPills.map((pill) => {
|
|
958
|
-
const masterOn = settings.showCustomPills ?? true;
|
|
959
|
-
const enabled = customPillEffectiveEnabled(masterOn, customOverrides, pill);
|
|
960
|
-
return (
|
|
961
|
-
<View
|
|
962
|
-
key={`custom-${pill.id}`}
|
|
963
|
-
style={styles.metricMatrixRow}
|
|
964
|
-
>
|
|
965
|
-
<View style={styles.metricMatrixLabel}>
|
|
966
|
-
<Text style={[styles.metricMatrixTitle, { color: colors.foreground }]}>
|
|
967
|
-
{pill.title}
|
|
968
|
-
</Text>
|
|
969
|
-
<Text style={[styles.metricMatrixNote, { color: colors.foregroundMuted }]}>
|
|
970
|
-
custom pill
|
|
971
|
-
</Text>
|
|
972
|
-
</View>
|
|
973
|
-
<View style={[styles.metricMatrixTargets, isCompact && styles.metricMatrixTargetsCompact]}>
|
|
974
|
-
<Toggle
|
|
975
|
-
value={enabled}
|
|
976
|
-
disabled={!masterOn}
|
|
977
|
-
onValueChange={(val) => onCustomToggle(pill.id, val)}
|
|
978
|
-
style={styles.matrixToggle}
|
|
979
|
-
/>
|
|
980
|
-
<Toggle
|
|
981
|
-
value={false}
|
|
982
|
-
disabled={true}
|
|
983
|
-
onValueChange={() => {}}
|
|
984
|
-
style={styles.matrixToggle}
|
|
985
|
-
/>
|
|
986
|
-
</View>
|
|
987
|
-
</View>
|
|
988
|
-
);
|
|
989
|
-
})}
|
|
990
|
-
</View>
|
|
957
|
+
<MultiChoiceChips
|
|
958
|
+
options={customPills.map((pill) => ({
|
|
959
|
+
id: pill.id,
|
|
960
|
+
label: pill.title,
|
|
961
|
+
icon: "Sliders",
|
|
962
|
+
disabled: !masterOn,
|
|
963
|
+
}))}
|
|
964
|
+
selected={customPills
|
|
965
|
+
.filter((pill) => customPillEffectiveEnabled(masterOn, customOverrides, pill))
|
|
966
|
+
.map((pill) => pill.id)}
|
|
967
|
+
onToggle={(pillId) => {
|
|
968
|
+
triggerHaptic("light");
|
|
969
|
+
const pill = customPills.find((p) => p.id === pillId);
|
|
970
|
+
if (!pill) return;
|
|
971
|
+
const current = customPillEffectiveEnabled(masterOn, customOverrides, pill);
|
|
972
|
+
onCustomToggle(pillId, !current);
|
|
973
|
+
}}
|
|
974
|
+
/>
|
|
975
|
+
</HostStack>
|
|
976
|
+
)}
|
|
977
|
+
</HostStack>
|
|
991
978
|
);
|
|
992
979
|
}
|
|
993
980
|
|
|
994
|
-
function ResourceModal(
|
|
995
|
-
|
|
981
|
+
function ResourceModal(props: ResourceModalProps) {
|
|
982
|
+
return (
|
|
983
|
+
<HostThemeProvider theme={props.theme}>
|
|
984
|
+
<HostLayoutProvider layout={props.layout}>
|
|
985
|
+
<ResourceModalBody {...props} />
|
|
986
|
+
</HostLayoutProvider>
|
|
987
|
+
</HostThemeProvider>
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
function ResourceModalBody({ workspaceId, agentId, initialTab, payload, theme, layout }: ResourceModalProps) {
|
|
992
|
+
const { colors } = useHostTheme();
|
|
993
|
+
const { compact } = useHostLayout();
|
|
994
|
+
// Host-density padding rhythm (the helper's "comfortable" scale): the host
|
|
995
|
+
// owns density now, so the modal keeps the same numeric values locally.
|
|
996
|
+
const padding = compact
|
|
997
|
+
? { horizontal: 12, vertical: 10, gap: 8 }
|
|
998
|
+
: { horizontal: 16, vertical: 14, gap: 12 };
|
|
996
999
|
// Settings re-verify on mount/focus via hook defaults; the settings tab
|
|
997
1000
|
// refetches explicitly below. No background poll: the modal only lives
|
|
998
1001
|
// while open. Custom-pill definitions are a separate data source and keep
|
|
@@ -1068,18 +1071,7 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1068
1071
|
});
|
|
1069
1072
|
}, [agentId, data, agent, workspace?.directory]);
|
|
1070
1073
|
|
|
1071
|
-
const hasAnyPillEnabled =
|
|
1072
|
-
flags.showCpuRam ||
|
|
1073
|
-
flags.showBranch ||
|
|
1074
|
-
flags.showWorktree ||
|
|
1075
|
-
flags.showAgentTitle ||
|
|
1076
|
-
flags.showAgent ||
|
|
1077
|
-
flags.showAgentProvider ||
|
|
1078
|
-
flags.showAgentActivity ||
|
|
1079
|
-
flags.showAgentId ||
|
|
1080
|
-
flags.showLoad ||
|
|
1081
|
-
flags.showUptime ||
|
|
1082
|
-
isMcpEnabled;
|
|
1074
|
+
const hasAnyPillEnabled = hasAnyLegacyPillFlagEnabled(flags) || isMcpEnabled;
|
|
1083
1075
|
|
|
1084
1076
|
const handleRefresh = () => {
|
|
1085
1077
|
triggerHaptic("light");
|
|
@@ -1093,8 +1085,8 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1093
1085
|
};
|
|
1094
1086
|
|
|
1095
1087
|
const navbar = (
|
|
1096
|
-
<
|
|
1097
|
-
tabs={
|
|
1088
|
+
<HostTabs
|
|
1089
|
+
tabs={[...MODAL_TABS]}
|
|
1098
1090
|
activeTab={activeTab}
|
|
1099
1091
|
onTabChange={handleTabChange}
|
|
1100
1092
|
/>
|
|
@@ -1104,235 +1096,251 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1104
1096
|
paddingHorizontal: padding.horizontal,
|
|
1105
1097
|
paddingTop: padding.vertical,
|
|
1106
1098
|
paddingBottom: Math.round(padding.gap / 2),
|
|
1107
|
-
|
|
1099
|
+
position: "sticky" as const,
|
|
1100
|
+
top: 0,
|
|
1101
|
+
zIndex: 10,
|
|
1102
|
+
} as any;
|
|
1108
1103
|
|
|
1109
1104
|
if (isError && !data) {
|
|
1110
1105
|
return (
|
|
1111
|
-
<
|
|
1112
|
-
style={
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
<Icon name="Ghost" size={24} color={colors.statusDanger} />
|
|
1124
|
-
<Text style={[styles.errorText, { color: colors.statusDanger }]}>
|
|
1125
|
-
{error instanceof Error ? error.message : "Failed to load metrics"}
|
|
1126
|
-
</Text>
|
|
1106
|
+
<HostModalSection>
|
|
1107
|
+
<View style={styles.modalColumn}>
|
|
1108
|
+
<View style={navbarStyle}>{navbar}</View>
|
|
1109
|
+
<View style={styles.modalContent}>
|
|
1110
|
+
<HostCard variant="elevated">
|
|
1111
|
+
<View style={styles.errorBox}>
|
|
1112
|
+
<Icon name="Ghost" size={24} color={colors.statusDanger} />
|
|
1113
|
+
<Text style={[styles.errorText, { color: colors.statusDanger }]}>
|
|
1114
|
+
{error instanceof Error ? error.message : "Failed to load metrics"}
|
|
1115
|
+
</Text>
|
|
1116
|
+
</View>
|
|
1117
|
+
</HostCard>
|
|
1127
1118
|
</View>
|
|
1128
|
-
</
|
|
1129
|
-
</
|
|
1119
|
+
</View>
|
|
1120
|
+
</HostModalSection>
|
|
1130
1121
|
);
|
|
1131
1122
|
}
|
|
1132
1123
|
|
|
1133
1124
|
const { cpuColor, memColor } = getMetricColors(data, colors);
|
|
1134
1125
|
|
|
1135
1126
|
return (
|
|
1136
|
-
<
|
|
1137
|
-
style={
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
<ProgressBar
|
|
1196
|
-
value={data?.cpuUsagePercent ?? 0}
|
|
1197
|
-
thresholds={CPU_THRESHOLDS}
|
|
1198
|
-
height={8}
|
|
1199
|
-
/>
|
|
1127
|
+
<HostModalSection>
|
|
1128
|
+
<View style={styles.modalColumn}>
|
|
1129
|
+
<View style={navbarStyle}>{navbar}</View>
|
|
1130
|
+
<View style={styles.modalContent}>
|
|
1131
|
+
{activeTab === "system" && (
|
|
1132
|
+
<>
|
|
1133
|
+
{/* Dual Metric Gauges Hero */}
|
|
1134
|
+
<HostCard variant="elevated">
|
|
1135
|
+
<HostRow justify="between" align="center" style={styles.gaugeContainer}>
|
|
1136
|
+
<HostMetricGauge
|
|
1137
|
+
value={data?.cpuUsagePercent ?? 0}
|
|
1138
|
+
thresholds={CPU_THRESHOLDS}
|
|
1139
|
+
label="CPU Load"
|
|
1140
|
+
size={72}
|
|
1141
|
+
/>
|
|
1142
|
+
<HostMetricGauge
|
|
1143
|
+
value={data?.memoryUsedPercent ?? 0}
|
|
1144
|
+
thresholds={MEM_THRESHOLDS}
|
|
1145
|
+
label="RAM Used"
|
|
1146
|
+
size={72}
|
|
1147
|
+
/>
|
|
1148
|
+
</HostRow>
|
|
1149
|
+
</HostCard>
|
|
1150
|
+
|
|
1151
|
+
{/* Host Meta Card */}
|
|
1152
|
+
<HostCard variant="elevated">
|
|
1153
|
+
<HostStack gap={12}>
|
|
1154
|
+
<HostKeyValue
|
|
1155
|
+
label="Host"
|
|
1156
|
+
value={data?.hostname ?? "Unknown"}
|
|
1157
|
+
copyable
|
|
1158
|
+
{...compactKvProps}
|
|
1159
|
+
/>
|
|
1160
|
+
<HostKeyValue
|
|
1161
|
+
label="Uptime"
|
|
1162
|
+
value={data?.uptimeSeconds ? formatUptime(data?.uptimeSeconds) : "--"}
|
|
1163
|
+
{...compactKvProps}
|
|
1164
|
+
/>
|
|
1165
|
+
</HostStack>
|
|
1166
|
+
<HostKeyValue
|
|
1167
|
+
label="Processor"
|
|
1168
|
+
value={data?.cpuModel ?? "--"}
|
|
1169
|
+
subValue={data?.cpuCores ? `(${data?.cpuCores} cores)` : undefined}
|
|
1170
|
+
{...compactKvProps}
|
|
1171
|
+
/>
|
|
1172
|
+
</HostCard>
|
|
1173
|
+
|
|
1174
|
+
{/* CPU Utilization Card */}
|
|
1175
|
+
<HostCard variant="elevated">
|
|
1176
|
+
<HostCardHeader
|
|
1177
|
+
title="CPU Details"
|
|
1178
|
+
{...compactCardTitleProp}
|
|
1179
|
+
value={
|
|
1180
|
+
<Text style={[styles.metricHighlight, { color: cpuColor }]}>
|
|
1181
|
+
{data?.cpuUsagePercent ?? 0}%
|
|
1182
|
+
</Text>
|
|
1183
|
+
}
|
|
1184
|
+
/>
|
|
1200
1185
|
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
</Card>
|
|
1186
|
+
<HostProgressBar
|
|
1187
|
+
value={data?.cpuUsagePercent ?? 0}
|
|
1188
|
+
thresholds={CPU_THRESHOLDS}
|
|
1189
|
+
height={8}
|
|
1190
|
+
/>
|
|
1207
1191
|
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1192
|
+
<HostKeyValue
|
|
1193
|
+
label="Load Average (1m, 5m, 15m)"
|
|
1194
|
+
value={data?.loadAvg ? data?.loadAvg.map((n) => n.toFixed(2)).join(" ") : "-- -- --"}
|
|
1195
|
+
mono
|
|
1196
|
+
{...compactKvProps}
|
|
1197
|
+
/>
|
|
1198
|
+
</HostCard>
|
|
1199
|
+
|
|
1200
|
+
{/* Memory Card */}
|
|
1201
|
+
<HostCard variant="elevated">
|
|
1202
|
+
<HostCardHeader
|
|
1203
|
+
title="Memory Details"
|
|
1204
|
+
{...compactCardTitleProp}
|
|
1205
|
+
value={
|
|
1206
|
+
<Text style={[styles.metricHighlight, { color: memColor }]}>
|
|
1207
|
+
{data?.memoryUsedPercent ?? 0}%
|
|
1208
|
+
</Text>
|
|
1209
|
+
}
|
|
1210
|
+
/>
|
|
1218
1211
|
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1212
|
+
<HostProgressBar
|
|
1213
|
+
value={data?.memoryUsedPercent ?? 0}
|
|
1214
|
+
thresholds={MEM_THRESHOLDS}
|
|
1215
|
+
height={8}
|
|
1216
|
+
/>
|
|
1224
1217
|
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1218
|
+
<HostKeyValue
|
|
1219
|
+
label="Used / Total"
|
|
1220
|
+
value={`${formatBytes(data?.memoryUsedBytes ?? 0)} / ${formatBytes(data?.memoryTotalBytes ?? 0)}`}
|
|
1221
|
+
{...compactKvProps}
|
|
1222
|
+
/>
|
|
1223
|
+
</HostCard>
|
|
1224
|
+
|
|
1225
|
+
{/* MCP Servers Card */}
|
|
1226
|
+
{Boolean(data?.mcpInstalled) && (
|
|
1227
|
+
<HostCard variant="elevated">
|
|
1228
|
+
<HostCardHeader
|
|
1229
|
+
title="MCP Servers"
|
|
1230
|
+
subtitle="Source: paseo-mcp-tools"
|
|
1231
|
+
icon="Server"
|
|
1232
|
+
{...compactCardTitleProp}
|
|
1233
|
+
value={
|
|
1234
|
+
data?.mcp ? (
|
|
1235
|
+
<HostBadge
|
|
1236
|
+
label={data?.mcp.isStale ? "Stale Snapshot" : "Live"}
|
|
1237
|
+
variant={data?.mcp.isStale ? "warning" : "success"}
|
|
1238
|
+
dot
|
|
1239
|
+
{...compactBadgeProp}
|
|
1240
|
+
/>
|
|
1241
|
+
) : (
|
|
1242
|
+
<HostBadge
|
|
1243
|
+
label="No Data"
|
|
1244
|
+
variant="neutral"
|
|
1245
|
+
{...compactBadgeProp}
|
|
1246
|
+
/>
|
|
1247
|
+
)
|
|
1248
|
+
}
|
|
1244
1249
|
/>
|
|
1245
|
-
) : (
|
|
1246
|
-
<CompactBadge label="No Data" variant="neutral" />
|
|
1247
|
-
)
|
|
1248
|
-
}
|
|
1249
|
-
/>
|
|
1250
1250
|
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1280
|
-
|
|
1281
|
-
|
|
1282
|
-
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1251
|
+
{data?.mcp ? (
|
|
1252
|
+
<>
|
|
1253
|
+
<HostStack gap={12}>
|
|
1254
|
+
<HostKeyValue
|
|
1255
|
+
label="Health"
|
|
1256
|
+
value={`${data?.mcp.healthy} healthy / ${data?.mcp.total} total`}
|
|
1257
|
+
{...compactKvProps}
|
|
1258
|
+
/>
|
|
1259
|
+
<HostKeyValue
|
|
1260
|
+
label="Snapshot Updated"
|
|
1261
|
+
value={formatTimeAgo(data?.mcp.updatedAt)}
|
|
1262
|
+
{...compactKvProps}
|
|
1263
|
+
/>
|
|
1264
|
+
<HostKeyValue
|
|
1265
|
+
label="Data Provider"
|
|
1266
|
+
value="paseo-mcp-tools"
|
|
1267
|
+
{...compactKvProps}
|
|
1268
|
+
/>
|
|
1269
|
+
</HostStack>
|
|
1270
|
+
|
|
1271
|
+
{data?.mcp.servers && data?.mcp.servers.length > 0 ? (
|
|
1272
|
+
<View style={styles.mcpList}>
|
|
1273
|
+
{data?.mcp.servers.map((srv) => {
|
|
1274
|
+
const badgeVariant: "success" | "warning" | "danger" | "neutral" =
|
|
1275
|
+
srv.status === "healthy"
|
|
1276
|
+
? "success"
|
|
1277
|
+
: srv.status === "degraded"
|
|
1278
|
+
? "warning"
|
|
1279
|
+
: srv.status === "down"
|
|
1280
|
+
? "danger"
|
|
1281
|
+
: "neutral";
|
|
1282
|
+
return (
|
|
1283
|
+
<HostRow
|
|
1284
|
+
key={srv.name}
|
|
1285
|
+
justify="between"
|
|
1286
|
+
align="center"
|
|
1287
|
+
style={styles.mcpRow}
|
|
1288
|
+
>
|
|
1289
|
+
<HostRow gap={8} align="center" style={styles.mcpInfo}>
|
|
1290
|
+
<Text
|
|
1291
|
+
numberOfLines={1}
|
|
1292
|
+
style={[styles.mcpName, { color: colors.foreground }]}
|
|
1293
|
+
>
|
|
1294
|
+
{srv.name}
|
|
1295
|
+
</Text>
|
|
1296
|
+
<Text style={[styles.mcpLatency, { color: colors.foregroundMuted }]}>
|
|
1297
|
+
{srv.latencyMs >= 0 ? `${srv.latencyMs}ms` : "timeout"}
|
|
1298
|
+
</Text>
|
|
1299
|
+
</HostRow>
|
|
1300
|
+
<HostBadge {...compactBadgeProp} label={srv.status} variant={badgeVariant} />
|
|
1301
|
+
</HostRow>
|
|
1302
|
+
);
|
|
1303
|
+
})}
|
|
1293
1304
|
</View>
|
|
1294
|
-
)
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
)}
|
|
1308
|
-
</Card>
|
|
1309
|
-
)}
|
|
1305
|
+
) : (
|
|
1306
|
+
<Text style={[styles.mcpEmpty, { color: colors.foregroundMuted }]}>
|
|
1307
|
+
No MCP servers configured
|
|
1308
|
+
</Text>
|
|
1309
|
+
)}
|
|
1310
|
+
</>
|
|
1311
|
+
) : (
|
|
1312
|
+
<Text style={[styles.mcpEmpty, { color: colors.foregroundMuted }]}>
|
|
1313
|
+
Waiting for status snapshot from paseo-mcp-tools...
|
|
1314
|
+
</Text>
|
|
1315
|
+
)}
|
|
1316
|
+
</HostCard>
|
|
1317
|
+
)}
|
|
1310
1318
|
|
|
1311
1319
|
{/* Custom Metric Pills Card */}
|
|
1312
1320
|
{Boolean(data?.customPills && data?.customPills.length > 0) && (
|
|
1313
|
-
<
|
|
1314
|
-
<
|
|
1321
|
+
<HostCard variant="elevated">
|
|
1322
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1315
1323
|
title="Custom Metric Pills"
|
|
1316
1324
|
subtitle="Discovered from ~/.paseo/top/pills"
|
|
1317
1325
|
icon="Sliders"
|
|
1318
1326
|
value={
|
|
1319
|
-
<
|
|
1327
|
+
<HostBadge {...compactBadgeProp}
|
|
1320
1328
|
label={`${data?.customPills?.length ?? 0} active`}
|
|
1321
1329
|
variant="accent"
|
|
1322
1330
|
/>
|
|
1323
1331
|
}
|
|
1324
1332
|
/>
|
|
1325
|
-
<
|
|
1333
|
+
<HostStack gap={12}>
|
|
1326
1334
|
{(data?.customPills ?? []).map((cp) => (
|
|
1327
|
-
<
|
|
1335
|
+
<HostKeyValue {...compactKvProps}
|
|
1328
1336
|
key={cp.id}
|
|
1329
1337
|
label={cp.title}
|
|
1330
1338
|
value={cp.displayValue}
|
|
1331
1339
|
subValue={cp.status !== "neutral" ? `(${cp.status})` : undefined}
|
|
1332
1340
|
/>
|
|
1333
1341
|
))}
|
|
1334
|
-
</
|
|
1335
|
-
</
|
|
1342
|
+
</HostStack>
|
|
1343
|
+
</HostCard>
|
|
1336
1344
|
)}
|
|
1337
1345
|
</>
|
|
1338
1346
|
)}
|
|
@@ -1340,48 +1348,48 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1340
1348
|
{activeTab === "context" && (
|
|
1341
1349
|
<>
|
|
1342
1350
|
{/* Workspace Information */}
|
|
1343
|
-
<
|
|
1344
|
-
<
|
|
1351
|
+
<HostCard variant="elevated">
|
|
1352
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1345
1353
|
title="Workspace & Git"
|
|
1346
1354
|
icon="GitBranch"
|
|
1347
1355
|
value={
|
|
1348
1356
|
workspace?.status ? (
|
|
1349
|
-
<
|
|
1357
|
+
<HostBadge {...compactBadgeProp} label={workspace.status} variant="info" />
|
|
1350
1358
|
) : undefined
|
|
1351
1359
|
}
|
|
1352
1360
|
/>
|
|
1353
|
-
<
|
|
1354
|
-
<
|
|
1355
|
-
<
|
|
1356
|
-
</
|
|
1361
|
+
<HostStack gap={12}>
|
|
1362
|
+
<HostKeyValue {...compactKvProps} label="Git Branch" value={data?.branch || "Unknown"} />
|
|
1363
|
+
<HostKeyValue {...compactKvProps} label="Kind" value={workspace?.kind || "Unknown"} />
|
|
1364
|
+
</HostStack>
|
|
1357
1365
|
{workspace?.directory ? (
|
|
1358
|
-
<
|
|
1366
|
+
<HostKeyValue {...compactKvProps} label="Worktree Location" value={workspace.directory} copyable mono />
|
|
1359
1367
|
) : null}
|
|
1360
1368
|
{workspace?.name ? (
|
|
1361
|
-
<
|
|
1369
|
+
<HostKeyValue {...compactKvProps} label="Workspace Name" value={workspace.name} />
|
|
1362
1370
|
) : null}
|
|
1363
1371
|
{workspace?.title && workspace.title !== workspace.name ? (
|
|
1364
|
-
<
|
|
1372
|
+
<HostKeyValue {...compactKvProps} label="Workspace Title" value={workspace.title} />
|
|
1365
1373
|
) : null}
|
|
1366
1374
|
{workspace?.projectDisplayName ? (
|
|
1367
|
-
<
|
|
1375
|
+
<HostKeyValue {...compactKvProps} label="Project" value={workspace.projectDisplayName} />
|
|
1368
1376
|
) : null}
|
|
1369
1377
|
{workspace?.diffStat ? (
|
|
1370
|
-
<
|
|
1378
|
+
<HostKeyValue {...compactKvProps}
|
|
1371
1379
|
label="Git Changes"
|
|
1372
1380
|
value={`+${workspace.diffStat.additions} -${workspace.diffStat.deletions}`}
|
|
1373
1381
|
/>
|
|
1374
1382
|
) : null}
|
|
1375
|
-
</
|
|
1383
|
+
</HostCard>
|
|
1376
1384
|
|
|
1377
1385
|
{/* Agent Information */}
|
|
1378
|
-
<
|
|
1379
|
-
<
|
|
1386
|
+
<HostCard variant="elevated">
|
|
1387
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1380
1388
|
title={agent?.title ? `Agent: ${agent.title}` : "Agent Session"}
|
|
1381
1389
|
icon="Bot"
|
|
1382
1390
|
value={
|
|
1383
1391
|
agent?.status ? (
|
|
1384
|
-
<
|
|
1392
|
+
<HostBadge {...compactBadgeProp}
|
|
1385
1393
|
label={agent.status}
|
|
1386
1394
|
variant={agent.status === "running" ? "success" : "info"}
|
|
1387
1395
|
/>
|
|
@@ -1389,17 +1397,17 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1389
1397
|
}
|
|
1390
1398
|
/>
|
|
1391
1399
|
{agent?.title ? (
|
|
1392
|
-
<
|
|
1400
|
+
<HostKeyValue {...compactKvProps} label="Agent Tab" value={agent.title} />
|
|
1393
1401
|
) : null}
|
|
1394
1402
|
{agentId ? (
|
|
1395
|
-
<
|
|
1403
|
+
<HostKeyValue {...compactKvProps} label="Agent ID" value={agentId} copyable mono />
|
|
1396
1404
|
) : null}
|
|
1397
|
-
<
|
|
1398
|
-
<
|
|
1399
|
-
<
|
|
1400
|
-
</
|
|
1401
|
-
<
|
|
1402
|
-
<
|
|
1405
|
+
<HostStack gap={12}>
|
|
1406
|
+
<HostKeyValue {...compactKvProps} label="Model" value={agent?.model || "Standard"} />
|
|
1407
|
+
<HostKeyValue {...compactKvProps} label="Provider" value={agent?.provider || "Default"} />
|
|
1408
|
+
</HostStack>
|
|
1409
|
+
<HostStack gap={12}>
|
|
1410
|
+
<HostKeyValue {...compactKvProps}
|
|
1403
1411
|
label="Last Worked"
|
|
1404
1412
|
value={
|
|
1405
1413
|
agent?.status === "running"
|
|
@@ -1407,7 +1415,7 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1407
1415
|
: formatTimeAgo(agent?.lastActivityAt)
|
|
1408
1416
|
}
|
|
1409
1417
|
/>
|
|
1410
|
-
<
|
|
1418
|
+
<HostKeyValue {...compactKvProps}
|
|
1411
1419
|
label="Inactivity"
|
|
1412
1420
|
value={
|
|
1413
1421
|
agent?.status === "running"
|
|
@@ -1415,25 +1423,25 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1415
1423
|
: formatIdleDuration(agent?.lastActivityAt)
|
|
1416
1424
|
}
|
|
1417
1425
|
/>
|
|
1418
|
-
</
|
|
1426
|
+
</HostStack>
|
|
1419
1427
|
{agent?.cwd ? (
|
|
1420
|
-
<
|
|
1428
|
+
<HostKeyValue {...compactKvProps} label="Working Directory" value={agent.cwd} copyable mono />
|
|
1421
1429
|
) : null}
|
|
1422
|
-
</
|
|
1430
|
+
</HostCard>
|
|
1423
1431
|
|
|
1424
1432
|
{/* Token Usage & Context Window */}
|
|
1425
|
-
<
|
|
1426
|
-
<
|
|
1433
|
+
<HostCard variant="elevated">
|
|
1434
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1427
1435
|
title="Tokens & Context Window"
|
|
1428
1436
|
icon="Coins"
|
|
1429
1437
|
value={
|
|
1430
1438
|
contextPercent != null ? (
|
|
1431
|
-
<
|
|
1439
|
+
<HostBadge {...compactBadgeProp}
|
|
1432
1440
|
label={`${contextPercent}% ctx`}
|
|
1433
1441
|
variant={contextPercent >= 85 ? "danger" : contextPercent >= 70 ? "warning" : "success"}
|
|
1434
1442
|
/>
|
|
1435
1443
|
) : tokenMetrics?.totalTokens != null ? (
|
|
1436
|
-
<
|
|
1444
|
+
<HostBadge {...compactBadgeProp}
|
|
1437
1445
|
label={`${formatCompactTokens(tokenMetrics.totalTokens)} tok`}
|
|
1438
1446
|
variant="neutral"
|
|
1439
1447
|
/>
|
|
@@ -1443,64 +1451,64 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1443
1451
|
|
|
1444
1452
|
{tokenMetrics?.contextMaxTokens != null && tokenMetrics.contextMaxTokens > 0 ? (
|
|
1445
1453
|
<View style={styles.contextUsageBlock}>
|
|
1446
|
-
<
|
|
1454
|
+
<HostRow justify="between" align="center" style={styles.contextUsageRow}>
|
|
1447
1455
|
<Text style={[styles.compactKvLabel, { color: colors.foregroundMuted }]}>Context Utilization</Text>
|
|
1448
1456
|
<Text style={[styles.compactKvValue, { color: colors.foreground, fontWeight: "600" }]}>
|
|
1449
1457
|
{formatCompactTokens(tokenMetrics.contextUsedTokens ?? 0)} / {formatCompactTokens(tokenMetrics.contextMaxTokens)} ({contextPercent}%)
|
|
1450
1458
|
</Text>
|
|
1451
|
-
</
|
|
1452
|
-
<
|
|
1459
|
+
</HostRow>
|
|
1460
|
+
<HostProgressBar
|
|
1453
1461
|
value={contextPercent ?? 0}
|
|
1454
1462
|
thresholds={{ warning: 70, danger: 85 }}
|
|
1455
1463
|
height={8}
|
|
1456
1464
|
/>
|
|
1457
1465
|
</View>
|
|
1458
1466
|
) : tokenMetrics?.contextUsedTokens != null ? (
|
|
1459
|
-
<
|
|
1467
|
+
<HostKeyValue {...compactKvProps}
|
|
1460
1468
|
label="Context Used"
|
|
1461
|
-
value={`${tokenMetrics.contextUsedTokens.toLocaleString()} (${formatCompactTokens(tokenMetrics.contextUsedTokens)})`}
|
|
1469
|
+
value={`${tokenMetrics.contextUsedTokens.toLocaleString('en-US')} (${formatCompactTokens(tokenMetrics.contextUsedTokens)})`}
|
|
1462
1470
|
/>
|
|
1463
1471
|
) : null}
|
|
1464
1472
|
|
|
1465
|
-
<
|
|
1466
|
-
<
|
|
1473
|
+
<HostStack gap={12}>
|
|
1474
|
+
<HostKeyValue {...compactKvProps}
|
|
1467
1475
|
label="Input Tokens"
|
|
1468
|
-
value={tokenMetrics?.inputTokens != null ? tokenMetrics.inputTokens.toLocaleString() : "--"}
|
|
1476
|
+
value={tokenMetrics?.inputTokens != null ? tokenMetrics.inputTokens.toLocaleString('en-US') : "--"}
|
|
1469
1477
|
subValue={tokenMetrics?.inputTokens != null ? formatCompactTokens(tokenMetrics.inputTokens) : undefined}
|
|
1470
1478
|
/>
|
|
1471
|
-
<
|
|
1479
|
+
<HostKeyValue {...compactKvProps}
|
|
1472
1480
|
label="Output Tokens"
|
|
1473
|
-
value={tokenMetrics?.outputTokens != null ? tokenMetrics.outputTokens.toLocaleString() : "--"}
|
|
1481
|
+
value={tokenMetrics?.outputTokens != null ? tokenMetrics.outputTokens.toLocaleString('en-US') : "--"}
|
|
1474
1482
|
subValue={tokenMetrics?.outputTokens != null ? formatCompactTokens(tokenMetrics.outputTokens) : undefined}
|
|
1475
1483
|
/>
|
|
1476
1484
|
{tokenMetrics?.cachedTokens != null && (
|
|
1477
|
-
<
|
|
1485
|
+
<HostKeyValue {...compactKvProps}
|
|
1478
1486
|
label="Cached Tokens"
|
|
1479
|
-
value={tokenMetrics.cachedTokens.toLocaleString()}
|
|
1487
|
+
value={tokenMetrics.cachedTokens.toLocaleString('en-US')}
|
|
1480
1488
|
subValue={formatCompactTokens(tokenMetrics.cachedTokens)}
|
|
1481
1489
|
/>
|
|
1482
1490
|
)}
|
|
1483
1491
|
{tokenMetrics?.costUsd != null && (
|
|
1484
|
-
<
|
|
1492
|
+
<HostKeyValue {...compactKvProps}
|
|
1485
1493
|
label="Session / Turn Cost"
|
|
1486
1494
|
value={`$${tokenMetrics.costUsd < 0.01 ? tokenMetrics.costUsd.toFixed(4) : tokenMetrics.costUsd.toFixed(2)}`}
|
|
1487
1495
|
/>
|
|
1488
1496
|
)}
|
|
1489
|
-
</
|
|
1497
|
+
</HostStack>
|
|
1490
1498
|
{!tokenMetrics && (
|
|
1491
1499
|
<Text style={[styles.mcpEmpty, { color: colors.foregroundMuted, marginTop: 4 }]}>
|
|
1492
1500
|
No token usage recorded for this agent session yet
|
|
1493
1501
|
</Text>
|
|
1494
1502
|
)}
|
|
1495
|
-
</
|
|
1503
|
+
</HostCard>
|
|
1496
1504
|
</>
|
|
1497
1505
|
)}
|
|
1498
1506
|
|
|
1499
1507
|
{activeTab === "settings" && (
|
|
1500
1508
|
<>
|
|
1501
1509
|
{/* Pill Display Mode */}
|
|
1502
|
-
<
|
|
1503
|
-
<
|
|
1510
|
+
<HostCard variant="elevated">
|
|
1511
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1504
1512
|
title="Pill Display Mode"
|
|
1505
1513
|
icon="LayoutGrid"
|
|
1506
1514
|
subtitle="How active items appear in the composer trackbar"
|
|
@@ -1520,7 +1528,7 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1520
1528
|
}}
|
|
1521
1529
|
/>
|
|
1522
1530
|
<View style={styles.settingsSpacer}>
|
|
1523
|
-
<
|
|
1531
|
+
<HostToggle
|
|
1524
1532
|
label="Show Composer Pill"
|
|
1525
1533
|
description="Hide the composer pill entirely; the dashboard stays available from the sidebar"
|
|
1526
1534
|
style={styles.toggleFullWidth}
|
|
@@ -1532,13 +1540,18 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1532
1540
|
}}
|
|
1533
1541
|
/>
|
|
1534
1542
|
</View>
|
|
1535
|
-
</
|
|
1543
|
+
</HostCard>
|
|
1536
1544
|
|
|
1537
1545
|
{/* Active Pill Items Selectors */}
|
|
1538
|
-
<
|
|
1539
|
-
<
|
|
1546
|
+
<HostCard variant="elevated">
|
|
1547
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1540
1548
|
title="Active Pill Items"
|
|
1541
1549
|
icon="Sliders"
|
|
1550
|
+
value={
|
|
1551
|
+
<Text style={[styles.accentValueText, { color: colors.accent }]}>
|
|
1552
|
+
{`${METRIC_DEFINITIONS.filter((d) => isPillEnabled((settings.metricSurfaces ?? DEFAULT_METRIC_SURFACES)[d.id])).length} active`}
|
|
1553
|
+
</Text>
|
|
1554
|
+
}
|
|
1542
1555
|
subtitle={
|
|
1543
1556
|
!hasAnyPillEnabled
|
|
1544
1557
|
? "None selected -- automatically showing CPU & RAM"
|
|
@@ -1550,12 +1563,11 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1550
1563
|
}
|
|
1551
1564
|
/>
|
|
1552
1565
|
<View style={styles.settingsToggles}>
|
|
1553
|
-
<
|
|
1566
|
+
<MetricPillSelector
|
|
1554
1567
|
settings={settings}
|
|
1555
1568
|
updateSettings={updateSettings}
|
|
1556
1569
|
notifySettingsChanged={notifySettingsChanged}
|
|
1557
1570
|
mcpInstalled={Boolean(data?.mcpInstalled)}
|
|
1558
|
-
mcpRunning={data?.mcpRunning ?? null}
|
|
1559
1571
|
customPills={(customPillList?.pills ?? []).map((pill) => ({
|
|
1560
1572
|
id: pill.id,
|
|
1561
1573
|
title: pill.title,
|
|
@@ -1571,17 +1583,17 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1571
1583
|
}}
|
|
1572
1584
|
/>
|
|
1573
1585
|
</View>
|
|
1574
|
-
</
|
|
1586
|
+
</HostCard>
|
|
1575
1587
|
|
|
1576
1588
|
{/* Custom Metric Pills */}
|
|
1577
|
-
<
|
|
1578
|
-
<
|
|
1589
|
+
<HostCard variant="elevated">
|
|
1590
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1579
1591
|
title="Custom Metric Pills"
|
|
1580
1592
|
icon="Sliders"
|
|
1581
1593
|
subtitle="Standalone pills discovered from ~/.paseo/top/pills"
|
|
1582
1594
|
/>
|
|
1583
1595
|
<View style={styles.settingsToggles}>
|
|
1584
|
-
<
|
|
1596
|
+
<HostToggle
|
|
1585
1597
|
label="Show Custom Metric Pills"
|
|
1586
1598
|
description="Display pills defined in ~/.paseo/top/pills as standalone composer pills"
|
|
1587
1599
|
style={styles.toggleFullWidth}
|
|
@@ -1595,12 +1607,12 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1595
1607
|
/>
|
|
1596
1608
|
|
|
1597
1609
|
</View>
|
|
1598
|
-
</
|
|
1610
|
+
</HostCard>
|
|
1599
1611
|
|
|
1600
1612
|
{/* Rotation Speed Setting (Cycle mode only) */}
|
|
1601
1613
|
{(settings.pillMode ?? "cycle") === "cycle" && (
|
|
1602
|
-
<
|
|
1603
|
-
<
|
|
1614
|
+
<HostCard variant="elevated">
|
|
1615
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1604
1616
|
title="Rotation Speed"
|
|
1605
1617
|
icon="Clock"
|
|
1606
1618
|
value={
|
|
@@ -1617,12 +1629,12 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1617
1629
|
updateSettings({ intervalSeconds });
|
|
1618
1630
|
}}
|
|
1619
1631
|
/>
|
|
1620
|
-
</
|
|
1632
|
+
</HostCard>
|
|
1621
1633
|
)}
|
|
1622
1634
|
|
|
1623
1635
|
{/* Timeline Cadence Setting */}
|
|
1624
|
-
<
|
|
1625
|
-
<
|
|
1636
|
+
<HostCard variant="elevated">
|
|
1637
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1626
1638
|
title="Timeline Cadence"
|
|
1627
1639
|
icon="Clock"
|
|
1628
1640
|
value={
|
|
@@ -1648,30 +1660,92 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1648
1660
|
<Text style={[styles.modeDesc, styles.modeDescSpaced, { color: colors.foregroundMuted }]}>
|
|
1649
1661
|
0 behaves as never; N above 1 stamps every Nth turn
|
|
1650
1662
|
</Text>
|
|
1651
|
-
</
|
|
1663
|
+
</HostCard>
|
|
1664
|
+
|
|
1665
|
+
{/* Timeline Card Metrics Setting */}
|
|
1666
|
+
{(settings.timelineCadence ?? 1) > 0 && (
|
|
1667
|
+
<HostCard variant="elevated">
|
|
1668
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1669
|
+
title="Timeline Card Metrics"
|
|
1670
|
+
icon="GitCommitHorizontal"
|
|
1671
|
+
value={
|
|
1672
|
+
<Text style={[styles.accentValueText, { color: colors.accent }]}>
|
|
1673
|
+
{`${METRIC_DEFINITIONS.filter((d) => !d.pillOnly && isTimelineEnabled((settings.metricSurfaces ?? DEFAULT_METRIC_SURFACES)[d.id])).length} active`}
|
|
1674
|
+
</Text>
|
|
1675
|
+
}
|
|
1676
|
+
subtitle="Choose which metrics appear on turn summary cards"
|
|
1677
|
+
/>
|
|
1678
|
+
<MultiChoiceChips
|
|
1679
|
+
options={METRIC_DEFINITIONS.filter((d) => !d.pillOnly).map((def) => ({
|
|
1680
|
+
id: def.id,
|
|
1681
|
+
label: def.title,
|
|
1682
|
+
icon: def.icon,
|
|
1683
|
+
disabled: def.id === "mcp" && !data?.mcpInstalled,
|
|
1684
|
+
}))}
|
|
1685
|
+
selected={METRIC_DEFINITIONS.filter((d) => !d.pillOnly && isTimelineEnabled((settings.metricSurfaces ?? DEFAULT_METRIC_SURFACES)[d.id])).map((d) => d.id)}
|
|
1686
|
+
onToggle={(metricId) => {
|
|
1687
|
+
triggerHaptic("light");
|
|
1688
|
+
const surfaces = settings.metricSurfaces ?? DEFAULT_METRIC_SURFACES;
|
|
1689
|
+
const currentPill = isPillEnabled(surfaces[metricId]);
|
|
1690
|
+
const currentTimeline = isTimelineEnabled(surfaces[metricId]);
|
|
1691
|
+
const next = {
|
|
1692
|
+
...surfaces,
|
|
1693
|
+
[metricId]: targetFromCheckboxes(currentPill, !currentTimeline),
|
|
1694
|
+
};
|
|
1695
|
+
updateSettings({ metricSurfaces: next });
|
|
1696
|
+
notifySettingsChanged({ ...settings, metricSurfaces: next });
|
|
1697
|
+
}}
|
|
1698
|
+
onSelectAll={() => {
|
|
1699
|
+
triggerHaptic("medium");
|
|
1700
|
+
const surfaces = settings.metricSurfaces ?? DEFAULT_METRIC_SURFACES;
|
|
1701
|
+
const next = { ...surfaces };
|
|
1702
|
+
for (const def of METRIC_DEFINITIONS) {
|
|
1703
|
+
if (def.pillOnly) continue;
|
|
1704
|
+
if (def.id === "mcp" && !data?.mcpInstalled) continue;
|
|
1705
|
+
const currentPill = isPillEnabled(surfaces[def.id]);
|
|
1706
|
+
next[def.id] = targetFromCheckboxes(currentPill, true);
|
|
1707
|
+
}
|
|
1708
|
+
updateSettings({ metricSurfaces: next });
|
|
1709
|
+
notifySettingsChanged({ ...settings, metricSurfaces: next });
|
|
1710
|
+
}}
|
|
1711
|
+
onClearAll={() => {
|
|
1712
|
+
triggerHaptic("medium");
|
|
1713
|
+
const surfaces = settings.metricSurfaces ?? DEFAULT_METRIC_SURFACES;
|
|
1714
|
+
const next = { ...surfaces };
|
|
1715
|
+
for (const def of METRIC_DEFINITIONS) {
|
|
1716
|
+
if (def.pillOnly) continue;
|
|
1717
|
+
const currentPill = isPillEnabled(surfaces[def.id]);
|
|
1718
|
+
next[def.id] = targetFromCheckboxes(currentPill, false);
|
|
1719
|
+
}
|
|
1720
|
+
updateSettings({ metricSurfaces: next });
|
|
1721
|
+
notifySettingsChanged({ ...settings, metricSurfaces: next });
|
|
1722
|
+
}}
|
|
1723
|
+
/>
|
|
1724
|
+
</HostCard>
|
|
1725
|
+
)}
|
|
1652
1726
|
|
|
1653
1727
|
{/* Default Modal Tab Setting */}
|
|
1654
|
-
<
|
|
1655
|
-
<
|
|
1728
|
+
<HostCard variant="elevated">
|
|
1729
|
+
<HostCardHeader {...compactCardTitleProp}
|
|
1656
1730
|
title="Default Modal Tab"
|
|
1657
1731
|
icon="Sliders"
|
|
1658
1732
|
value={
|
|
1659
1733
|
<Text style={[styles.accentValueText, { color: colors.accent }]}>
|
|
1660
|
-
{
|
|
1734
|
+
{MODAL_TABS.find((t) => t.id === settings.defaultTab)?.label || "System"}
|
|
1661
1735
|
</Text>
|
|
1662
1736
|
}
|
|
1663
1737
|
/>
|
|
1664
1738
|
<ChoiceChips
|
|
1665
|
-
options={
|
|
1739
|
+
options={MODAL_TABS.map((tab) => ({ id: tab.id as ModalTab, label: tab.label }))}
|
|
1666
1740
|
value={settings.defaultTab ?? "system"}
|
|
1667
1741
|
onChange={(defaultTab) => {
|
|
1668
1742
|
triggerHaptic("light");
|
|
1669
1743
|
updateSettings({ defaultTab });
|
|
1670
1744
|
}}
|
|
1671
1745
|
/>
|
|
1672
|
-
</
|
|
1746
|
+
</HostCard>
|
|
1673
1747
|
|
|
1674
|
-
<
|
|
1748
|
+
<HostButton
|
|
1675
1749
|
label="Reset to Defaults"
|
|
1676
1750
|
variant="secondary"
|
|
1677
1751
|
size="sm"
|
|
@@ -1685,8 +1759,12 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1685
1759
|
</>
|
|
1686
1760
|
)}
|
|
1687
1761
|
|
|
1762
|
+
{activeTab === "permissions" && (
|
|
1763
|
+
<PermissionAuditView variant="compact" theme={theme} layout={layout} />
|
|
1764
|
+
)}
|
|
1765
|
+
|
|
1688
1766
|
{activeTab === "about" && (
|
|
1689
|
-
<
|
|
1767
|
+
<HostAboutSection
|
|
1690
1768
|
name="paseo-top"
|
|
1691
1769
|
description="Live host system and workspace monitor for Paseo composer trackbar."
|
|
1692
1770
|
version={data?.version ?? PLUGIN_VERSION}
|
|
@@ -1694,28 +1772,7 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1694
1772
|
repository="https://github.com/xpufx/paseo-top"
|
|
1695
1773
|
issues="https://github.com/xpufx/paseo-top/issues"
|
|
1696
1774
|
license="MIT"
|
|
1697
|
-
|
|
1698
|
-
extraItems={[
|
|
1699
|
-
{
|
|
1700
|
-
label: "Host Platform",
|
|
1701
|
-
value: data?.platform ? `${data?.platform} (${data?.arch ?? "unknown"})` : "Linux",
|
|
1702
|
-
copyable: true,
|
|
1703
|
-
},
|
|
1704
|
-
{ label: "Host Name", value: data?.hostname ?? "localhost", copyable: true },
|
|
1705
|
-
{ label: "CPU Model", value: data?.cpuModel ?? "unknown", copyable: true },
|
|
1706
|
-
{
|
|
1707
|
-
label: "CPU Cores",
|
|
1708
|
-
value: `${data?.cpuCores ?? 0} cores`,
|
|
1709
|
-
},
|
|
1710
|
-
{
|
|
1711
|
-
label: "Total Memory",
|
|
1712
|
-
value: data?.memoryTotalBytes ? formatBytes(data?.memoryTotalBytes) : "unknown",
|
|
1713
|
-
},
|
|
1714
|
-
{
|
|
1715
|
-
label: "Host Uptime",
|
|
1716
|
-
value: data?.uptimeSeconds ? formatUptime(data?.uptimeSeconds) : "unknown",
|
|
1717
|
-
},
|
|
1718
|
-
]}
|
|
1775
|
+
extraItems={aboutExtraItems(data)}
|
|
1719
1776
|
/>
|
|
1720
1777
|
)}
|
|
1721
1778
|
|
|
@@ -1727,28 +1784,80 @@ function ResourceModal({ theme, workspaceId, agentId, initialTab, payload }: Res
|
|
|
1727
1784
|
</Text>
|
|
1728
1785
|
</View>
|
|
1729
1786
|
)}
|
|
1730
|
-
|
|
1787
|
+
</View>
|
|
1788
|
+
</View>
|
|
1789
|
+
</HostModalSection>
|
|
1731
1790
|
);
|
|
1732
1791
|
}
|
|
1733
1792
|
|
|
1734
|
-
interface
|
|
1793
|
+
interface LiveCustomPillModalProps {
|
|
1735
1794
|
pillId: string;
|
|
1736
|
-
initial:
|
|
1795
|
+
initial: CustomPillStateOutput;
|
|
1737
1796
|
}
|
|
1738
1797
|
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1798
|
+
/**
|
|
1799
|
+
* Drilldown modal body for a custom metric pill, built from `ui/` primitives
|
|
1800
|
+
* (the deprecated `client/` `CustomPillModalContent` was dropped in the #847
|
|
1801
|
+
* migration). The host owns the modal frame; this fills it fluidly.
|
|
1802
|
+
*/
|
|
1803
|
+
function CustomPillModalContent({
|
|
1804
|
+
state,
|
|
1805
|
+
onRefresh,
|
|
1806
|
+
isRefreshing = false,
|
|
1807
|
+
}: {
|
|
1808
|
+
state: CustomPillStateOutput;
|
|
1809
|
+
onRefresh?: () => Promise<void> | void;
|
|
1810
|
+
isRefreshing?: boolean;
|
|
1811
|
+
}) {
|
|
1812
|
+
const { colors } = useHostTheme();
|
|
1813
|
+
const displayText =
|
|
1814
|
+
state.modalOutput || state.rawValue || (state.error ? `Error: ${state.error}` : "No output");
|
|
1744
1815
|
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1816
|
+
return (
|
|
1817
|
+
<View style={styles.customPillModal}>
|
|
1818
|
+
<HostCard>
|
|
1819
|
+
<HostCardHeader
|
|
1820
|
+
title={state.modalTitle ?? state.title}
|
|
1821
|
+
subtitle={state.modalDescription}
|
|
1822
|
+
icon={state.icon}
|
|
1823
|
+
badge={<HostBadge label={state.displayValue} variant={state.status} />}
|
|
1824
|
+
action={
|
|
1825
|
+
onRefresh ? (
|
|
1826
|
+
<HostButton
|
|
1827
|
+
variant="secondary"
|
|
1828
|
+
size="sm"
|
|
1829
|
+
icon="RefreshCw"
|
|
1830
|
+
loading={isRefreshing}
|
|
1831
|
+
label="Refresh"
|
|
1832
|
+
onPress={() => onRefresh()}
|
|
1833
|
+
/>
|
|
1834
|
+
) : undefined
|
|
1835
|
+
}
|
|
1836
|
+
/>
|
|
1837
|
+
<View style={styles.codeBlock}>
|
|
1838
|
+
<Text
|
|
1839
|
+
selectable
|
|
1840
|
+
numberOfLines={8}
|
|
1841
|
+
style={[styles.codeText, { color: colors.foreground }]}
|
|
1842
|
+
>
|
|
1843
|
+
{displayText}
|
|
1844
|
+
</Text>
|
|
1845
|
+
<HostCopyButton
|
|
1846
|
+
text={displayText}
|
|
1847
|
+
accessibilityLabel="Copy pill output"
|
|
1848
|
+
toastMessage="pill output"
|
|
1849
|
+
/>
|
|
1850
|
+
</View>
|
|
1851
|
+
<Text style={[styles.timestampText, { color: colors.foregroundMuted }]}>
|
|
1852
|
+
Last updated: {new Date(state.lastUpdated).toLocaleTimeString()}
|
|
1853
|
+
</Text>
|
|
1854
|
+
</HostCard>
|
|
1855
|
+
</View>
|
|
1856
|
+
);
|
|
1748
1857
|
}
|
|
1749
1858
|
|
|
1750
1859
|
function LiveCustomPillModal({ pillId, initial }: LiveCustomPillModalProps) {
|
|
1751
|
-
const { colors } =
|
|
1860
|
+
const { colors } = useHostTheme();
|
|
1752
1861
|
const [refreshing, setRefreshing] = useState(false);
|
|
1753
1862
|
const [outputOverride, setOutputOverride] = useState<string | undefined>(undefined);
|
|
1754
1863
|
const { data, refetch } = useCustomPillsQuery();
|
|
@@ -1856,12 +1965,6 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
1856
1965
|
modalTitle: "Host System Resources",
|
|
1857
1966
|
modalIcon: "Activity",
|
|
1858
1967
|
icon: "Cpu",
|
|
1859
|
-
resolveDefaultPayload: ({ agentId }) => {
|
|
1860
|
-
if (latestSettings.pillMode === "all") {
|
|
1861
|
-
return latestSettings.defaultTab;
|
|
1862
|
-
}
|
|
1863
|
-
return currentCycleTabByAgent.get(agentId) ?? latestSettings.defaultTab;
|
|
1864
|
-
},
|
|
1865
1968
|
resolveLabel: async (ctx) => {
|
|
1866
1969
|
const snap = await liveSnapshotFor(ctx);
|
|
1867
1970
|
const items = enabledItemsForSettings(latestSettings);
|
|
@@ -1878,7 +1981,6 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
1878
1981
|
};
|
|
1879
1982
|
},
|
|
1880
1983
|
refreshIntervalMs: 3000,
|
|
1881
|
-
renderPill: (props) => <PillView {...props} />,
|
|
1882
1984
|
renderModal: (props) => <ResourceModal {...props} />,
|
|
1883
1985
|
});
|
|
1884
1986
|
activePills.set("paseo-top", cleanup);
|
|
@@ -1890,17 +1992,7 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
1890
1992
|
activePills.delete("paseo-top");
|
|
1891
1993
|
}
|
|
1892
1994
|
|
|
1893
|
-
const hasAny =
|
|
1894
|
-
flags.showCpuRam ||
|
|
1895
|
-
flags.showBranch ||
|
|
1896
|
-
flags.showWorktree ||
|
|
1897
|
-
flags.showAgentTitle ||
|
|
1898
|
-
flags.showAgent ||
|
|
1899
|
-
flags.showAgentProvider ||
|
|
1900
|
-
flags.showAgentActivity ||
|
|
1901
|
-
flags.showAgentId ||
|
|
1902
|
-
flags.showLoad ||
|
|
1903
|
-
flags.showUptime;
|
|
1995
|
+
const hasAny = hasAnyLegacyPillFlagEnabled(flags);
|
|
1904
1996
|
|
|
1905
1997
|
const effectiveCpu = flags.showCpuRam || !hasAny;
|
|
1906
1998
|
|
|
@@ -2063,6 +2155,16 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
2063
2155
|
defaultTab: "system",
|
|
2064
2156
|
});
|
|
2065
2157
|
}
|
|
2158
|
+
if (settings.metricSurfaces && isPillEnabled(settings.metricSurfaces.velocity)) {
|
|
2159
|
+
desiredPills.push({
|
|
2160
|
+
id: "paseo-top-velocity",
|
|
2161
|
+
item: "velocity",
|
|
2162
|
+
icon: "Zap",
|
|
2163
|
+
title: "Generation Velocity",
|
|
2164
|
+
modalTitle: "Host System Resources",
|
|
2165
|
+
defaultTab: "system",
|
|
2166
|
+
});
|
|
2167
|
+
}
|
|
2066
2168
|
|
|
2067
2169
|
const desiredIds = new Set(desiredPills.map((p) => p.id));
|
|
2068
2170
|
|
|
@@ -2083,16 +2185,8 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
2083
2185
|
modalTitle: pillDef.modalTitle,
|
|
2084
2186
|
icon: pillDef.icon,
|
|
2085
2187
|
modalIcon: pillDef.icon,
|
|
2086
|
-
resolveDefaultPayload: () => pillDef.defaultTab,
|
|
2087
2188
|
resolveLabel: singleItemLabelResolver(pillDef.item),
|
|
2088
2189
|
refreshIntervalMs: 5000,
|
|
2089
|
-
renderPill: (props) => (
|
|
2090
|
-
<SingleItemPillView
|
|
2091
|
-
item={pillDef.item}
|
|
2092
|
-
defaultTab={pillDef.defaultTab}
|
|
2093
|
-
{...props}
|
|
2094
|
-
/>
|
|
2095
|
-
),
|
|
2096
2190
|
renderModal: (props) => (
|
|
2097
2191
|
<ResourceModal initialTab={pillDef.defaultTab} {...props} />
|
|
2098
2192
|
),
|
|
@@ -2104,7 +2198,7 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
2104
2198
|
}
|
|
2105
2199
|
|
|
2106
2200
|
// Register settings listener
|
|
2107
|
-
|
|
2201
|
+
addSettingsListener(syncPills);
|
|
2108
2202
|
|
|
2109
2203
|
// Initial sync synchronously
|
|
2110
2204
|
syncPills(topSettingsContract.defaultSettings);
|
|
@@ -2185,9 +2279,12 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
2185
2279
|
);
|
|
2186
2280
|
},
|
|
2187
2281
|
refreshIntervalMs: 5000,
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2282
|
+
renderModal: (props) => (
|
|
2283
|
+
<HostThemeProvider theme={props.theme}>
|
|
2284
|
+
<HostLayoutProvider layout={props.layout}>
|
|
2285
|
+
<LiveCustomPillModal pillId={pill.id} initial={pill} />
|
|
2286
|
+
</HostLayoutProvider>
|
|
2287
|
+
</HostThemeProvider>
|
|
2191
2288
|
),
|
|
2192
2289
|
});
|
|
2193
2290
|
activeCustomPills.set(pill.id, cleanup);
|
|
@@ -2206,7 +2303,7 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
2206
2303
|
const onSettingsChanged = () => {
|
|
2207
2304
|
void syncCustomPills();
|
|
2208
2305
|
};
|
|
2209
|
-
|
|
2306
|
+
addSettingsListener(onSettingsChanged);
|
|
2210
2307
|
|
|
2211
2308
|
let disposed = false;
|
|
2212
2309
|
const cleanup = () => {
|
|
@@ -2218,8 +2315,8 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
2218
2315
|
cleanupSidebar = null;
|
|
2219
2316
|
}
|
|
2220
2317
|
clearInterval(customPillInterval);
|
|
2221
|
-
|
|
2222
|
-
|
|
2318
|
+
removeSettingsListener(syncPills);
|
|
2319
|
+
removeSettingsListener(onSettingsChanged);
|
|
2223
2320
|
for (const cleanup of activePills.values()) {
|
|
2224
2321
|
cleanup();
|
|
2225
2322
|
}
|
|
@@ -2241,8 +2338,6 @@ export function contributeClient(client: ComposerPillRegistrar | PluginClientCon
|
|
|
2241
2338
|
// primitive covers.
|
|
2242
2339
|
const styles = {
|
|
2243
2340
|
modalRoot: {
|
|
2244
|
-
flex: 1,
|
|
2245
|
-
minHeight: 0,
|
|
2246
2341
|
width: "100%",
|
|
2247
2342
|
},
|
|
2248
2343
|
pillContainer: {
|
|
@@ -2252,6 +2347,11 @@ const styles = {
|
|
|
2252
2347
|
overflow: "hidden",
|
|
2253
2348
|
flexShrink: 1,
|
|
2254
2349
|
minWidth: 0,
|
|
2350
|
+
// The pill label is one short line; without a height floor a body that
|
|
2351
|
+
// momentarily renders no text (loading, or a blank host field) collapses to
|
|
2352
|
+
// an unreadable skinny line on mobile (xpufx-org/paseo#507). The host
|
|
2353
|
+
// composer pill is 32pt; keep the inner body at least a line tall.
|
|
2354
|
+
minHeight: 20,
|
|
2255
2355
|
},
|
|
2256
2356
|
pillText: {
|
|
2257
2357
|
fontSize: 11,
|
|
@@ -2336,80 +2436,6 @@ const styles = {
|
|
|
2336
2436
|
accentValueText: {
|
|
2337
2437
|
fontWeight: "600",
|
|
2338
2438
|
},
|
|
2339
|
-
metricMatrix: {
|
|
2340
|
-
gap: 0,
|
|
2341
|
-
width: "100%",
|
|
2342
|
-
},
|
|
2343
|
-
metricMatrixCompact: {
|
|
2344
|
-
minWidth: 0,
|
|
2345
|
-
},
|
|
2346
|
-
metricMatrixHeader: {
|
|
2347
|
-
flexDirection: "row",
|
|
2348
|
-
alignItems: "center",
|
|
2349
|
-
justifyContent: "space-between",
|
|
2350
|
-
paddingLeft: 4,
|
|
2351
|
-
paddingRight: 4,
|
|
2352
|
-
paddingBottom: 2,
|
|
2353
|
-
},
|
|
2354
|
-
metricMatrixHeaderCompact: {
|
|
2355
|
-
paddingLeft: 2,
|
|
2356
|
-
paddingRight: 2,
|
|
2357
|
-
},
|
|
2358
|
-
metricMatrixHeaderText: {
|
|
2359
|
-
fontSize: 9,
|
|
2360
|
-
fontWeight: "700",
|
|
2361
|
-
textTransform: "uppercase",
|
|
2362
|
-
letterSpacing: 0.5,
|
|
2363
|
-
},
|
|
2364
|
-
metricMatrixTargetHeader: {
|
|
2365
|
-
flexDirection: "row",
|
|
2366
|
-
alignItems: "center",
|
|
2367
|
-
gap: 28,
|
|
2368
|
-
paddingRight: 10,
|
|
2369
|
-
},
|
|
2370
|
-
metricMatrixTargetHeaderCompact: {
|
|
2371
|
-
gap: 16,
|
|
2372
|
-
paddingRight: 8,
|
|
2373
|
-
},
|
|
2374
|
-
metricMatrixRow: {
|
|
2375
|
-
flexDirection: "row",
|
|
2376
|
-
alignItems: "center",
|
|
2377
|
-
justifyContent: "space-between",
|
|
2378
|
-
paddingLeft: 4,
|
|
2379
|
-
paddingRight: 4,
|
|
2380
|
-
},
|
|
2381
|
-
metricMatrixRowCompact: {
|
|
2382
|
-
paddingLeft: 2,
|
|
2383
|
-
paddingRight: 2,
|
|
2384
|
-
},
|
|
2385
|
-
metricMatrixLabel: {
|
|
2386
|
-
flex: 1,
|
|
2387
|
-
minWidth: 0,
|
|
2388
|
-
paddingVertical: 2,
|
|
2389
|
-
},
|
|
2390
|
-
metricMatrixTitle: {
|
|
2391
|
-
fontSize: 11,
|
|
2392
|
-
fontWeight: "600",
|
|
2393
|
-
},
|
|
2394
|
-
metricMatrixNote: {
|
|
2395
|
-
fontSize: 9,
|
|
2396
|
-
lineHeight: 12,
|
|
2397
|
-
marginTop: 1,
|
|
2398
|
-
},
|
|
2399
|
-
metricMatrixTargets: {
|
|
2400
|
-
flexDirection: "row",
|
|
2401
|
-
alignItems: "center",
|
|
2402
|
-
gap: 16,
|
|
2403
|
-
paddingLeft: 8,
|
|
2404
|
-
},
|
|
2405
|
-
metricMatrixTargetsCompact: {
|
|
2406
|
-
gap: 8,
|
|
2407
|
-
paddingLeft: 4,
|
|
2408
|
-
},
|
|
2409
|
-
matrixToggle: {
|
|
2410
|
-
width: 38,
|
|
2411
|
-
minHeight: 44,
|
|
2412
|
-
},
|
|
2413
2439
|
allInOneContainer: {
|
|
2414
2440
|
flexDirection: "row",
|
|
2415
2441
|
alignItems: "center",
|
|
@@ -2417,6 +2443,8 @@ const styles = {
|
|
|
2417
2443
|
overflow: "hidden",
|
|
2418
2444
|
flexShrink: 1,
|
|
2419
2445
|
minWidth: 0,
|
|
2446
|
+
// See pillContainer: the combined pill must keep a line-height floor too.
|
|
2447
|
+
minHeight: 20,
|
|
2420
2448
|
},
|
|
2421
2449
|
dividerText: {
|
|
2422
2450
|
fontSize: 10,
|
|
@@ -2460,4 +2488,31 @@ const styles = {
|
|
|
2460
2488
|
fontStyle: "italic",
|
|
2461
2489
|
paddingVertical: 4,
|
|
2462
2490
|
},
|
|
2491
|
+
modalColumn: {
|
|
2492
|
+
width: "100%",
|
|
2493
|
+
},
|
|
2494
|
+
modalContent: {
|
|
2495
|
+
width: "100%",
|
|
2496
|
+
paddingTop: 6,
|
|
2497
|
+
},
|
|
2498
|
+
customPillModal: {
|
|
2499
|
+
width: "100%",
|
|
2500
|
+
padding: 12,
|
|
2501
|
+
},
|
|
2502
|
+
codeBlock: {
|
|
2503
|
+
flexDirection: "row",
|
|
2504
|
+
alignItems: "flex-start",
|
|
2505
|
+
gap: 8,
|
|
2506
|
+
marginTop: 8,
|
|
2507
|
+
},
|
|
2508
|
+
codeText: {
|
|
2509
|
+
flex: 1,
|
|
2510
|
+
minWidth: 0,
|
|
2511
|
+
fontSize: 11,
|
|
2512
|
+
fontFamily: "monospace",
|
|
2513
|
+
},
|
|
2514
|
+
timestampText: {
|
|
2515
|
+
fontSize: 11,
|
|
2516
|
+
marginTop: 8,
|
|
2517
|
+
},
|
|
2463
2518
|
} as const;
|