@xpufx/paseo-top 0.4.4 → 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 (129) 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 +94 -1
  6. package/client/pill.tsx +690 -614
  7. package/client/resources-query.ts +1 -1
  8. package/client/settings-events.ts +1 -1
  9. package/client/settings-ui.tsx +86 -9
  10. package/client/surface.tsx +308 -276
  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} +9 -3
  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} +51 -18
  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/{surface.tsx → lifecycle/surface.tsx} +6 -6
  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} +50 -58
  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 -234
  48. package/package.json +7 -4
  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 +2 -0
  59. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  60. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  61. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  62. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  63. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  64. package/server/vendor/paseo-plugin-helper/tickets.ts +18 -67
  65. package/server/vendor/permission-audit/index.ts +1 -0
  66. package/server/vendor/permission-audit/server.ts +839 -0
  67. package/shared/helper-version.ts +1 -1
  68. package/shared/multi-host.ts +153 -159
  69. package/shared/resources.ts +145 -0
  70. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  71. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  72. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  73. package/shared/vendor/paseo-plugin-helper/index.ts +1 -0
  74. package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
  75. package/shared/vendor/permission-audit/filter.ts +61 -0
  76. package/shared/vendor/permission-audit/index.ts +2 -0
  77. package/shared/vendor/permission-audit/shared.ts +119 -0
  78. package/shared/version.ts +1 -1
  79. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  80. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  81. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -168
  82. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
  83. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
  84. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  85. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  86. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  87. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  88. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  89. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  90. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -88
  91. package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
  92. package/client/vendor/paseo-plugin-helper/components/KeyValue.tsx +0 -446
  93. package/client/vendor/paseo-plugin-helper/components/MetricGauge.tsx +0 -247
  94. package/client/vendor/paseo-plugin-helper/components/ProgressBar.tsx +0 -117
  95. package/client/vendor/paseo-plugin-helper/components/Responsive.tsx +0 -53
  96. package/client/vendor/paseo-plugin-helper/components/SearchInput.tsx +0 -132
  97. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
  98. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
  99. package/client/vendor/paseo-plugin-helper/components/StatusDot.tsx +0 -80
  100. package/client/vendor/paseo-plugin-helper/components/Tabs.tsx +0 -319
  101. package/client/vendor/paseo-plugin-helper/components/TextInput.tsx +0 -150
  102. package/client/vendor/paseo-plugin-helper/components/Toggle.tsx +0 -163
  103. package/client/vendor/paseo-plugin-helper/components/TruncatedText.tsx +0 -157
  104. package/client/vendor/paseo-plugin-helper/components/index.ts +0 -26
  105. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
  106. package/client/vendor/paseo-plugin-helper/forge-icon.tsx +0 -79
  107. package/client/vendor/paseo-plugin-helper/icon.tsx +0 -39
  108. package/client/vendor/paseo-plugin-helper/index.ts +0 -30
  109. package/client/vendor/paseo-plugin-helper/layout/ActionBar.tsx +0 -49
  110. package/client/vendor/paseo-plugin-helper/layout/FormRow.tsx +0 -103
  111. package/client/vendor/paseo-plugin-helper/layout/Grid.tsx +0 -65
  112. package/client/vendor/paseo-plugin-helper/layout/ModalBody.tsx +0 -368
  113. package/client/vendor/paseo-plugin-helper/layout/ModalContent.tsx +0 -51
  114. package/client/vendor/paseo-plugin-helper/layout/Row.tsx +0 -39
  115. package/client/vendor/paseo-plugin-helper/layout/Stack.tsx +0 -39
  116. package/client/vendor/paseo-plugin-helper/layout/index.ts +0 -7
  117. package/client/vendor/paseo-plugin-helper/styles/index.ts +0 -1
  118. package/client/vendor/paseo-plugin-helper/styles/recipes.ts +0 -550
  119. package/client/vendor/paseo-plugin-helper/theme/flair.ts +0 -76
  120. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  121. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  122. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  123. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  124. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  125. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  126. package/client/vendor/paseo-plugin-helper/tickets.tsx +0 -766
  127. /package/client/vendor/paseo-plugin-helper/{command-center.ts → core/command-center.ts} +0 -0
  128. /package/client/vendor/paseo-plugin-helper/{snapshot.ts → core/snapshot.ts} +0 -0
  129. /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";