@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.
Files changed (130) hide show
  1. package/OVERVIEW.md +17 -0
  2. package/client/host-ui.tsx +184 -0
  3. package/client/multi-host-view.tsx +57 -43
  4. package/client/multi-host.ts +36 -13
  5. package/client/pill-labels.ts +124 -7
  6. package/client/pill.tsx +724 -669
  7. package/client/resources-query.ts +1 -1
  8. package/client/settings-events.ts +33 -0
  9. package/client/settings-ui.tsx +86 -9
  10. package/client/surface.tsx +310 -274
  11. package/client/telemetry-copy.ts +8 -2
  12. package/client/telemetry.tsx +372 -321
  13. package/client/turn-panel.tsx +126 -104
  14. package/client/vendor/paseo-plugin-helper/{host.ts → core/host.ts} +21 -61
  15. package/client/vendor/paseo-plugin-helper/core/index.ts +76 -0
  16. package/client/vendor/paseo-plugin-helper/core/plugin-load-count.ts +89 -0
  17. package/client/vendor/paseo-plugin-helper/{query-refresh.ts → core/query-refresh.ts} +2 -2
  18. package/client/vendor/paseo-plugin-helper/{query.ts → core/query.ts} +2 -2
  19. package/client/vendor/paseo-plugin-helper/{settings.ts → core/settings.ts} +1 -1
  20. package/client/vendor/paseo-plugin-helper/{shared-settings.ts → core/shared-settings.ts} +2 -2
  21. package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/clipboard.ts +2 -2
  22. package/client/vendor/paseo-plugin-helper/lifecycle/flair.ts +26 -0
  23. package/client/vendor/paseo-plugin-helper/{theme/color-utils.ts → lifecycle/host-color.ts} +95 -38
  24. package/client/vendor/paseo-plugin-helper/lifecycle/host-theme.tsx +109 -0
  25. package/client/vendor/paseo-plugin-helper/lifecycle/index.ts +23 -0
  26. package/client/vendor/paseo-plugin-helper/lifecycle/multi-host.ts +66 -0
  27. package/client/vendor/paseo-plugin-helper/{panel.tsx → lifecycle/panel.tsx} +7 -7
  28. package/client/vendor/paseo-plugin-helper/{pill.tsx → lifecycle/pill.tsx} +118 -338
  29. package/client/vendor/paseo-plugin-helper/lifecycle/providers.tsx +46 -0
  30. package/client/vendor/paseo-plugin-helper/lifecycle/scroll-owner.ts +26 -0
  31. package/client/vendor/paseo-plugin-helper/lifecycle/surface.tsx +140 -0
  32. package/client/vendor/paseo-plugin-helper/ui/color.ts +1 -0
  33. package/client/vendor/paseo-plugin-helper/ui/content.tsx +1326 -0
  34. package/client/vendor/paseo-plugin-helper/ui/controls.tsx +1018 -0
  35. package/client/vendor/paseo-plugin-helper/{components/DataTable.tsx → ui/data.tsx} +55 -47
  36. package/client/vendor/paseo-plugin-helper/ui/index.ts +44 -0
  37. package/client/vendor/paseo-plugin-helper/ui/layout.tsx +263 -0
  38. package/client/vendor/paseo-plugin-helper/ui/modal.tsx +111 -0
  39. package/client/vendor/paseo-plugin-helper/ui/plugin-reload-banner.tsx +119 -0
  40. package/client/vendor/paseo-plugin-helper/{settings-screen.tsx → ui/settings-screen.tsx} +3 -3
  41. package/client/vendor/paseo-plugin-helper/ui/settings.ts +36 -0
  42. package/client/vendor/paseo-plugin-helper/ui/text.tsx +821 -0
  43. package/client/vendor/paseo-plugin-helper/ui/theme.tsx +1 -0
  44. package/client/vendor/permission-audit/client.tsx +706 -0
  45. package/client/vendor/permission-audit/index.tsx +1 -0
  46. package/index.client.tsx +58 -37
  47. package/index.server.ts +256 -227
  48. package/package.json +14 -2
  49. package/paseo-plugin.json +2 -0
  50. package/screenshots/1-paseo-top-modal-system.jpg +0 -0
  51. package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
  52. package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
  53. package/screenshots/4-paseo-top-modal-about.jpg +0 -0
  54. package/screenshots/paseo-top-dashboard.jpg +0 -0
  55. package/screenshots/paseo-top-general-view.jpg +0 -0
  56. package/server/resources.ts +98 -0
  57. package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
  58. package/server/vendor/paseo-plugin-helper/index.ts +4 -0
  59. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  60. package/server/vendor/paseo-plugin-helper/mcp-injection.ts +20 -2
  61. package/server/vendor/paseo-plugin-helper/plugins.ts +184 -19
  62. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  63. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  64. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  65. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  66. package/server/vendor/paseo-plugin-helper/tickets.ts +1232 -0
  67. package/server/vendor/permission-audit/index.ts +1 -0
  68. package/server/vendor/permission-audit/server.ts +839 -0
  69. package/shared/helper-version.ts +20 -0
  70. package/shared/multi-host.ts +156 -155
  71. package/shared/resources.ts +146 -1
  72. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  73. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  74. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  75. package/shared/vendor/paseo-plugin-helper/index.ts +2 -0
  76. package/shared/vendor/paseo-plugin-helper/tickets.ts +1104 -0
  77. package/shared/vendor/permission-audit/filter.ts +61 -0
  78. package/shared/vendor/permission-audit/index.ts +2 -0
  79. package/shared/vendor/permission-audit/shared.ts +119 -0
  80. package/shared/version.ts +1 -1
  81. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  82. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  83. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -156
  84. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -184
  85. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -225
  86. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  87. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  88. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  89. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  90. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  91. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  92. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -75
  93. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
  94. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
  95. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
  96. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
  97. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -118
  98. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -80
  99. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -215
  100. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
  101. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
  102. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
  103. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
  104. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
  105. package/client/vendor/paseo-plugin-helper/components/index.ts +0 -25
  106. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -224
  107. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
  108. package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
  109. package/client/vendor/paseo-plugin-helper/index.ts +0 -29
  110. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
  111. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
  112. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
  113. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -378
  114. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
  115. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
  116. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
  117. package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
  118. package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
  119. package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
  120. package/client/vendor/paseo-plugin-helper/surface.tsx +0 -80
  121. package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
  122. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  123. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  124. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  125. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  126. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  127. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  128. /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
  129. /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
  130. /package/client/vendor/paseo-plugin-helper/{utils → lifecycle}/haptics.ts +0 -0
@@ -0,0 +1 @@
1
+ export * from "../lifecycle/host-theme";
@@ -0,0 +1,706 @@
1
+ import React, {
2
+ createContext,
3
+ useContext,
4
+ useMemo,
5
+ useState,
6
+ type ReactNode,
7
+ } from "react";
8
+ import {
9
+ ActivityIndicator,
10
+ Pressable,
11
+ ScrollView,
12
+ Text,
13
+ TextInput,
14
+ View,
15
+ type StyleProp,
16
+ type TextStyle,
17
+ type ViewStyle,
18
+ } from "react-native";
19
+ import { useRpcQuery } from "../paseo-plugin-helper/core/index";
20
+ import type {
21
+ PluginTheme,
22
+ ResponsiveLayout,
23
+ StatusVariant,
24
+ ThemeColors,
25
+ } from "../../../shared/vendor/paseo-plugin-helper/index";
26
+ import {
27
+ isToolCallEntry,
28
+ permissionAuditQuery,
29
+ type AuditRecord,
30
+ type PermissionAuditEntry,
31
+ type PermissionDecision,
32
+ type PermissionQueryFilter,
33
+ type ToolCallAuditEntry,
34
+ type ToolCallOutcome,
35
+ } from "../../../shared/vendor/permission-audit/shared.js";
36
+ import {
37
+ filterAuditEntries,
38
+ formatAuditTime,
39
+ summarizeAuditInput,
40
+ type AuditTypeFilter,
41
+ type DecisionFilter,
42
+ } from "../../../shared/vendor/permission-audit/filter.js";
43
+
44
+ export type {
45
+ AuditRecord,
46
+ AuditTypeFilter,
47
+ DecisionFilter,
48
+ PermissionAuditEntry,
49
+ PermissionDecision,
50
+ PermissionQueryFilter,
51
+ ToolCallAuditEntry,
52
+ ToolCallOutcome,
53
+ };
54
+ export { permissionAuditQuery, filterAuditEntries, formatAuditTime, summarizeAuditInput };
55
+ export type PermissionAuditVariant = "page" | "compact";
56
+
57
+ export interface UsePermissionAuditOptions {
58
+ limit?: number;
59
+ refreshIntervalMs?: number;
60
+ enabled?: boolean;
61
+ }
62
+
63
+ export function usePermissionAudit(
64
+ filter: Omit<PermissionQueryFilter, "limit"> & { limit?: number } = {},
65
+ options: UsePermissionAuditOptions = {},
66
+ ) {
67
+ const { limit = 100, refreshIntervalMs = 5000, enabled = true } = options;
68
+ const query = useRpcQuery(
69
+ permissionAuditQuery,
70
+ { ...filter, limit },
71
+ { refetchInterval: refreshIntervalMs, enabled },
72
+ );
73
+ return query;
74
+ }
75
+
76
+ export interface PermissionAuditViewProps {
77
+ agentId?: string;
78
+ variant?: PermissionAuditVariant;
79
+ showFilters?: boolean;
80
+ limit?: number;
81
+ refreshIntervalMs?: number;
82
+ /**
83
+ * Host theme for this surface. The host passes it through the surface
84
+ * registration props; when present the view provides it to the local
85
+ * composition below. When absent (tests, standalone rendering) the local
86
+ * components fall back to a neutral palette.
87
+ */
88
+ theme?: PluginTheme;
89
+ /** Host layout descriptor for this surface. */
90
+ layout?: ResponsiveLayout;
91
+ }
92
+
93
+ const DECISION_FILTERS: DecisionFilter[] = ["all", "pending", "allow", "deny"];
94
+ const TYPE_FILTERS: AuditTypeFilter[] = ["all", "permission", "tool_call"];
95
+
96
+ /**
97
+ * Neutral fallback palette so the view still renders outside a host surface
98
+ * (tests, previews). Static literal — never scraped from the DOM.
99
+ */
100
+ const FALLBACK_COLORS: ThemeColors = {
101
+ surface0: "#18181b",
102
+ surface1: "#27272a",
103
+ surface2: "#3f3f46",
104
+ border: "#3f3f46",
105
+ foreground: "#fafafa",
106
+ foregroundMuted: "#a1a1aa",
107
+ accent: "#3b82f6",
108
+ accentForeground: "#ffffff",
109
+ statusSuccess: "#22c55e",
110
+ statusWarning: "#eab308",
111
+ statusDanger: "#ef4444",
112
+ };
113
+
114
+ const ColorsContext = createContext<ThemeColors>(FALLBACK_COLORS);
115
+
116
+ /** Provides the host theme colors to the local composition below. */
117
+ export function PermissionAuditThemeProvider({
118
+ theme,
119
+ children,
120
+ }: {
121
+ theme: PluginTheme;
122
+ children: ReactNode;
123
+ }) {
124
+ return <ColorsContext.Provider value={theme.colors}>{children}</ColorsContext.Provider>;
125
+ }
126
+
127
+ /** Reads the host theme colors for this surface. */
128
+ export function usePermissionAuditColors(): ThemeColors {
129
+ return useContext(ColorsContext);
130
+ }
131
+
132
+ function statusColor(colors: ThemeColors, variant: StatusVariant): string {
133
+ switch (variant) {
134
+ case "success":
135
+ return colors.statusSuccess;
136
+ case "warning":
137
+ return colors.statusWarning;
138
+ case "danger":
139
+ return colors.statusDanger;
140
+ case "accent":
141
+ case "info":
142
+ return colors.accent;
143
+ default:
144
+ return colors.foregroundMuted;
145
+ }
146
+ }
147
+
148
+ function AuditBadge({ label, variant }: { label: string; variant: StatusVariant }) {
149
+ const colors = usePermissionAuditColors();
150
+ const color = statusColor(colors, variant);
151
+ return (
152
+ <View style={[styles.badge, { borderColor: color }]}>
153
+ <View style={[styles.badgeDot, { backgroundColor: color }]} />
154
+ <Text numberOfLines={1} style={[styles.badgeText, { color }]}>
155
+ {label}
156
+ </Text>
157
+ </View>
158
+ );
159
+ }
160
+
161
+ function AuditButton({
162
+ label,
163
+ variant = "secondary",
164
+ onPress,
165
+ testID,
166
+ }: {
167
+ label: string;
168
+ variant?: "primary" | "secondary";
169
+ onPress: () => void;
170
+ testID?: string;
171
+ }) {
172
+ const colors = usePermissionAuditColors();
173
+ const primary = variant === "primary";
174
+ return (
175
+ <Pressable
176
+ accessibilityRole="button"
177
+ testID={testID}
178
+ onPress={onPress}
179
+ style={[
180
+ styles.button,
181
+ {
182
+ backgroundColor: primary ? colors.accent : colors.surface1,
183
+ borderColor: primary ? colors.accent : colors.border,
184
+ },
185
+ ]}
186
+ >
187
+ <Text
188
+ numberOfLines={1}
189
+ style={[
190
+ styles.buttonText,
191
+ { color: primary ? colors.accentForeground : colors.foreground },
192
+ ]}
193
+ >
194
+ {label}
195
+ </Text>
196
+ </Pressable>
197
+ );
198
+ }
199
+
200
+ function AuditSearchInput({
201
+ value,
202
+ onChangeText,
203
+ placeholder,
204
+ testID,
205
+ }: {
206
+ value: string;
207
+ onChangeText: (text: string) => void;
208
+ placeholder?: string;
209
+ testID?: string;
210
+ }) {
211
+ const colors = usePermissionAuditColors();
212
+ return (
213
+ <TextInput
214
+ testID={testID}
215
+ value={value}
216
+ onChangeText={onChangeText}
217
+ placeholder={placeholder}
218
+ placeholderTextColor={colors.foregroundMuted}
219
+ style={[
220
+ styles.searchInput,
221
+ {
222
+ color: colors.foreground,
223
+ borderColor: colors.border,
224
+ backgroundColor: colors.surface1,
225
+ },
226
+ ]}
227
+ />
228
+ );
229
+ }
230
+
231
+ function AuditEmptyState({ title, description }: { title: string; description?: string }) {
232
+ const colors = usePermissionAuditColors();
233
+ return (
234
+ <View style={styles.empty}>
235
+ <Text style={[styles.emptyTitle, { color: colors.foreground }]}>{title}</Text>
236
+ {description ? (
237
+ <Text style={[styles.emptyDescription, { color: colors.foregroundMuted }]}>
238
+ {description}
239
+ </Text>
240
+ ) : null}
241
+ </View>
242
+ );
243
+ }
244
+
245
+ interface AuditColumn<T> {
246
+ key: string;
247
+ header: string;
248
+ flex?: number;
249
+ width?: number;
250
+ align?: "left" | "center" | "right";
251
+ render: (item: T) => ReactNode;
252
+ }
253
+
254
+ function AuditTable<T>({
255
+ data,
256
+ columns,
257
+ keyExtractor,
258
+ emptyState,
259
+ layout,
260
+ }: {
261
+ data: T[];
262
+ columns: AuditColumn<T>[];
263
+ keyExtractor: (item: T, index: number) => string;
264
+ emptyState?: ReactNode;
265
+ layout: ResponsiveLayout;
266
+ }) {
267
+ const colors = usePermissionAuditColors();
268
+ const isCompact =
269
+ layout.compact || layout.platform === "ios" || layout.platform === "android";
270
+
271
+ if (data.length === 0) {
272
+ return emptyState ? <View>{emptyState}</View> : null;
273
+ }
274
+
275
+ if (isCompact) {
276
+ return (
277
+ <View style={{ gap: 8 }}>
278
+ {data.map((item, idx) => (
279
+ <View
280
+ key={keyExtractor(item, idx)}
281
+ style={[styles.compactCard, { backgroundColor: colors.surface1, borderColor: colors.border }]}
282
+ >
283
+ {columns.map((col) => (
284
+ <View key={col.key} style={styles.compactRow}>
285
+ <Text style={[styles.compactHeader, { color: colors.foregroundMuted }]}>
286
+ {col.header}
287
+ </Text>
288
+ <View style={styles.compactValue}>{col.render(item)}</View>
289
+ </View>
290
+ ))}
291
+ </View>
292
+ ))}
293
+ </View>
294
+ );
295
+ }
296
+
297
+ return (
298
+ <View
299
+ style={[styles.table, { borderColor: colors.border, backgroundColor: colors.surface0 }]}
300
+ >
301
+ <View
302
+ style={[
303
+ styles.headerRow,
304
+ { backgroundColor: colors.surface1, borderBottomColor: colors.border },
305
+ ]}
306
+ >
307
+ {columns.map((col) => (
308
+ <View key={col.key} style={[styles.cell, cellStyle(col)]}>
309
+ <Text
310
+ numberOfLines={1}
311
+ ellipsizeMode="tail"
312
+ style={[styles.headerText, { color: colors.foregroundMuted }]}
313
+ >
314
+ {col.header}
315
+ </Text>
316
+ </View>
317
+ ))}
318
+ </View>
319
+ {data.map((item, idx) => (
320
+ <View
321
+ key={keyExtractor(item, idx)}
322
+ style={[
323
+ styles.row,
324
+ idx < data.length - 1 && { borderBottomColor: colors.border, borderBottomWidth: 1 },
325
+ ]}
326
+ >
327
+ {columns.map((col) => (
328
+ <View key={col.key} style={[styles.cell, cellStyle(col)]}>
329
+ {col.render(item)}
330
+ </View>
331
+ ))}
332
+ </View>
333
+ ))}
334
+ </View>
335
+ );
336
+ }
337
+
338
+ function cellStyle<T>(col: AuditColumn<T>): StyleProp<ViewStyle> {
339
+ return [
340
+ col.flex !== undefined ? { flex: col.flex } : { flex: 1 },
341
+ col.width !== undefined ? { width: col.width } : undefined,
342
+ col.align === "right"
343
+ ? styles.alignRight
344
+ : col.align === "center"
345
+ ? styles.alignCenter
346
+ : styles.alignLeft,
347
+ ];
348
+ }
349
+
350
+ function decisionLabel(value: DecisionFilter): string {
351
+ switch (value) {
352
+ case "pending":
353
+ return "Pending";
354
+ case "allow":
355
+ return "Allowed";
356
+ case "deny":
357
+ return "Denied";
358
+ default:
359
+ return "All";
360
+ }
361
+ }
362
+
363
+ function typeLabel(value: AuditTypeFilter): string {
364
+ switch (value) {
365
+ case "permission":
366
+ return "Permissions";
367
+ case "tool_call":
368
+ return "Tool calls";
369
+ default:
370
+ return "All";
371
+ }
372
+ }
373
+
374
+ function outcomePresentation(record: AuditRecord): { label: string; variant: StatusVariant } {
375
+ if (isToolCallEntry(record)) {
376
+ switch (record.outcome) {
377
+ case "success":
378
+ return { label: "Success", variant: "success" };
379
+ case "failure":
380
+ return { label: "Failed", variant: "danger" };
381
+ default:
382
+ return { label: "Canceled", variant: "warning" };
383
+ }
384
+ }
385
+ switch (record.decision) {
386
+ case "pending":
387
+ return { label: "Pending", variant: "warning" };
388
+ case "allow":
389
+ return { label: "Allowed", variant: "success" };
390
+ default:
391
+ return { label: "Denied", variant: "danger" };
392
+ }
393
+ }
394
+
395
+ /**
396
+ * Permission audit view migrated off the deprecated `client/` + `ui/` layers
397
+ * (paseo#847): the audit table, badges, buttons, search field, and empty state
398
+ * are local composition over `react-native`, so the view owns no helper UI
399
+ * dependency. Scroll ownership stays explicit: the page variant renders a
400
+ * single host `ScrollView`; the compact variant renders no scroller and leaves
401
+ * scrolling to the surrounding host surface.
402
+ */
403
+ export function PermissionAuditView({
404
+ agentId,
405
+ variant = "page",
406
+ showFilters = true,
407
+ limit = 100,
408
+ refreshIntervalMs = 5000,
409
+ theme,
410
+ layout,
411
+ }: PermissionAuditViewProps) {
412
+ const resolvedLayout = layout ?? { compact: false, platform: "web" as const };
413
+ const content = (
414
+ <PermissionAuditViewContent
415
+ agentId={agentId}
416
+ variant={variant}
417
+ showFilters={showFilters}
418
+ limit={limit}
419
+ refreshIntervalMs={refreshIntervalMs}
420
+ layout={resolvedLayout}
421
+ />
422
+ );
423
+ if (!theme) {
424
+ return content;
425
+ }
426
+ return <PermissionAuditThemeProvider theme={theme}>{content}</PermissionAuditThemeProvider>;
427
+ }
428
+
429
+ function PermissionAuditViewContent({
430
+ agentId,
431
+ variant = "page",
432
+ showFilters = true,
433
+ limit = 100,
434
+ refreshIntervalMs = 5000,
435
+ layout,
436
+ }: Omit<PermissionAuditViewProps, "theme"> & { layout: ResponsiveLayout }) {
437
+ const colors = usePermissionAuditColors();
438
+ const [search, setSearch] = useState("");
439
+ const [decision, setDecision] = useState<DecisionFilter>("all");
440
+ const [type, setType] = useState<AuditTypeFilter>("all");
441
+
442
+ const serverFilter = useMemo(
443
+ () => (agentId ? { agentId } : {}),
444
+ [agentId],
445
+ );
446
+ const { data, isLoading, isError, refetch } = usePermissionAudit(serverFilter, {
447
+ limit,
448
+ refreshIntervalMs,
449
+ });
450
+
451
+ const entries = useMemo(() => {
452
+ const all = data?.entries ?? [];
453
+ const scoped = agentId ? all.filter((entry) => entry.agentId === agentId) : all;
454
+ return filterAuditEntries(scoped, decision, search, type);
455
+ }, [data, agentId, decision, search, type]);
456
+
457
+ if (isLoading) {
458
+ return (
459
+ <View style={{ padding: 24, alignItems: "center" }}>
460
+ <ActivityIndicator />
461
+ <Text style={{ color: colors.foregroundMuted, marginTop: 8 }}>Loading permission audit log…</Text>
462
+ </View>
463
+ );
464
+ }
465
+
466
+ if (isError) {
467
+ return (
468
+ <AuditEmptyState
469
+ title="Audit log unavailable"
470
+ description="Could not load recent permission decisions."
471
+ />
472
+ );
473
+ }
474
+
475
+ const filters = showFilters ? (
476
+ <View style={{ gap: 12 }}>
477
+ <AuditSearchInput
478
+ value={search}
479
+ onChangeText={setSearch}
480
+ placeholder="Search tool, agent, outcome, or arguments…"
481
+ testID="permission-audit-search"
482
+ />
483
+ <View style={styles.filterRow}>
484
+ {TYPE_FILTERS.map((value) => (
485
+ <AuditButton
486
+ key={value}
487
+ label={typeLabel(value)}
488
+ testID={`permission-audit-type-${value}`}
489
+ variant={type === value ? "primary" : "secondary"}
490
+ onPress={() => setType(value)}
491
+ />
492
+ ))}
493
+ </View>
494
+ <View style={styles.filterRow}>
495
+ {DECISION_FILTERS.map((value) => (
496
+ <AuditButton
497
+ key={value}
498
+ label={decisionLabel(value)}
499
+ testID={`permission-audit-decision-${value}`}
500
+ variant={decision === value ? "primary" : "secondary"}
501
+ onPress={() => setDecision(value)}
502
+ />
503
+ ))}
504
+ </View>
505
+ </View>
506
+ ) : null;
507
+
508
+ const table = (
509
+ <AuditTable<AuditRecord>
510
+ data={entries}
511
+ layout={layout}
512
+ keyExtractor={(item) => `${isToolCallEntry(item) ? "tool_call" : "permission"}:${item.id}`}
513
+ columns={[
514
+ {
515
+ key: "time",
516
+ header: "Time",
517
+ flex: 2,
518
+ render: (item) => (
519
+ <Text numberOfLines={1} ellipsizeMode="middle" style={{ color: colors.foregroundMuted }}>
520
+ {formatAuditTime(item.timestamp)}
521
+ </Text>
522
+ ),
523
+ },
524
+ {
525
+ key: "entry",
526
+ header: "Entry",
527
+ flex: 3,
528
+ render: (item) => (
529
+ <View style={{ gap: 2 }}>
530
+ <Text
531
+ numberOfLines={1}
532
+ ellipsizeMode="middle"
533
+ style={{ color: colors.foreground, fontWeight: "600" }}
534
+ >
535
+ {item.name}
536
+ </Text>
537
+ <Text style={{ color: colors.foregroundMuted }} numberOfLines={1}>
538
+ {isToolCallEntry(item) ? "tool_call" : item.kind} · {item.agentId}
539
+ {item.agentModel ? ` · ${item.agentModel}` : ""}
540
+ {isToolCallEntry(item) && item.turnId ? ` · turn ${item.turnId}` : ""}
541
+ </Text>
542
+ <Text style={{ color: colors.foregroundMuted }} numberOfLines={1} ellipsizeMode="middle">
543
+ {summarizeAuditInput(item.input)}
544
+ </Text>
545
+ </View>
546
+ ),
547
+ },
548
+ {
549
+ key: "outcome",
550
+ header: "Outcome",
551
+ width: 110,
552
+ align: "right",
553
+ render: (item) => {
554
+ const { label, variant } = outcomePresentation(item);
555
+ return <AuditBadge label={label} variant={variant} />;
556
+ },
557
+ },
558
+ ]}
559
+ emptyState={
560
+ <AuditEmptyState
561
+ title="No audit entries yet"
562
+ description="Permission decisions and tool calls will appear here as agents run."
563
+ />
564
+ }
565
+ />
566
+ );
567
+
568
+ if (variant === "compact") {
569
+ return (
570
+ <View style={{ gap: 12 }}>
571
+ {filters}
572
+ {table}
573
+ </View>
574
+ );
575
+ }
576
+
577
+ return (
578
+ <ScrollView contentContainerStyle={{ paddingHorizontal: 16, paddingVertical: 14, gap: 12 }}>
579
+ {filters}
580
+ {table}
581
+ </ScrollView>
582
+ );
583
+ }
584
+
585
+ const styles = createStyleSheet();
586
+
587
+ function createStyleSheet() {
588
+ return {
589
+ badge: {
590
+ flexDirection: "row" as const,
591
+ alignItems: "center" as const,
592
+ alignSelf: "flex-start" as const,
593
+ maxWidth: "100%" as const,
594
+ flexShrink: 1,
595
+ borderWidth: 1,
596
+ borderRadius: 9999,
597
+ paddingHorizontal: 8,
598
+ paddingVertical: 2,
599
+ gap: 4,
600
+ },
601
+ badgeDot: {
602
+ width: 6,
603
+ height: 6,
604
+ borderRadius: 3,
605
+ },
606
+ badgeText: {
607
+ fontSize: 11,
608
+ fontWeight: "600" as const,
609
+ },
610
+ button: {
611
+ borderWidth: 1,
612
+ borderRadius: 8,
613
+ paddingHorizontal: 12,
614
+ paddingVertical: 6,
615
+ minHeight: 32,
616
+ flexShrink: 1,
617
+ maxWidth: "100%" as const,
618
+ alignItems: "center" as const,
619
+ justifyContent: "center" as const,
620
+ },
621
+ buttonText: {
622
+ fontSize: 12,
623
+ fontWeight: "600" as const,
624
+ },
625
+ filterRow: {
626
+ flexDirection: "row" as const,
627
+ flexWrap: "wrap" as const,
628
+ gap: 8,
629
+ },
630
+ searchInput: {
631
+ borderWidth: 1,
632
+ borderRadius: 8,
633
+ paddingHorizontal: 10,
634
+ paddingVertical: 8,
635
+ fontSize: 13,
636
+ width: "100%" as const,
637
+ },
638
+ empty: {
639
+ alignItems: "center" as const,
640
+ justifyContent: "center" as const,
641
+ gap: 8,
642
+ padding: 24,
643
+ },
644
+ emptyTitle: {
645
+ fontSize: 14,
646
+ fontWeight: "600" as const,
647
+ textAlign: "center" as const,
648
+ },
649
+ emptyDescription: {
650
+ fontSize: 12,
651
+ textAlign: "center" as const,
652
+ },
653
+ compactCard: {
654
+ borderWidth: 1,
655
+ borderRadius: 10,
656
+ padding: 10,
657
+ gap: 6,
658
+ },
659
+ compactRow: {
660
+ flexDirection: "row" as const,
661
+ justifyContent: "space-between" as const,
662
+ gap: 8,
663
+ },
664
+ compactHeader: {
665
+ fontSize: 11,
666
+ flexShrink: 1,
667
+ },
668
+ compactValue: {
669
+ flexShrink: 1,
670
+ minWidth: 0,
671
+ },
672
+ table: {
673
+ borderWidth: 1,
674
+ borderRadius: 10,
675
+ overflow: "hidden" as const,
676
+ },
677
+ headerRow: {
678
+ flexDirection: "row" as const,
679
+ borderBottomWidth: 1,
680
+ },
681
+ row: {
682
+ flexDirection: "row" as const,
683
+ },
684
+ cell: {
685
+ paddingHorizontal: 10,
686
+ paddingVertical: 8,
687
+ minWidth: 0,
688
+ flexShrink: 1,
689
+ },
690
+ alignLeft: {
691
+ alignItems: "flex-start" as const,
692
+ },
693
+ alignCenter: {
694
+ alignItems: "center" as const,
695
+ },
696
+ alignRight: {
697
+ alignItems: "flex-end" as const,
698
+ },
699
+ headerText: {
700
+ fontSize: 11,
701
+ fontWeight: "600" as const,
702
+ letterSpacing: 0.3,
703
+ textTransform: "uppercase" as const,
704
+ },
705
+ } satisfies Record<string, ViewStyle | TextStyle>;
706
+ }
@@ -0,0 +1 @@
1
+ export * from "./client.js";