@xpufx/paseo-top 0.4.4 → 0.4.7

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 +18 -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 +692 -616
  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/npm-shrinkwrap.json +44 -0
  49. package/package.json +8 -4
  50. package/paseo-plugin.json +2 -0
  51. package/screenshots/1-paseo-top-modal-system.jpg +0 -0
  52. package/screenshots/2-paseo-top-modal-workspace.jpg +0 -0
  53. package/screenshots/3-paseo-top-modal-settings.jpg +0 -0
  54. package/screenshots/4-paseo-top-modal-about.jpg +0 -0
  55. package/screenshots/paseo-top-dashboard.jpg +0 -0
  56. package/screenshots/paseo-top-general-view.jpg +0 -0
  57. package/server/resources.ts +98 -0
  58. package/server/vendor/paseo-plugin-helper/custom-pills.ts +70 -0
  59. package/server/vendor/paseo-plugin-helper/index.ts +2 -0
  60. package/server/vendor/paseo-plugin-helper/logger.ts +5 -16
  61. package/server/vendor/paseo-plugin-helper/rpc-guard.ts +2 -2
  62. package/server/vendor/paseo-plugin-helper/storage-guard.ts +294 -0
  63. package/server/vendor/paseo-plugin-helper/storage.ts +36 -2
  64. package/server/vendor/paseo-plugin-helper/telemetry.ts +486 -0
  65. package/server/vendor/paseo-plugin-helper/tickets.ts +18 -67
  66. package/server/vendor/permission-audit/index.ts +1 -0
  67. package/server/vendor/permission-audit/server.ts +839 -0
  68. package/shared/helper-version.ts +1 -1
  69. package/shared/multi-host.ts +153 -159
  70. package/shared/resources.ts +145 -0
  71. package/shared/vendor/paseo-plugin-helper/async.ts +14 -7
  72. package/shared/vendor/paseo-plugin-helper/features.ts +244 -0
  73. package/shared/vendor/paseo-plugin-helper/forge.ts +27 -0
  74. package/shared/vendor/paseo-plugin-helper/index.ts +1 -0
  75. package/shared/vendor/paseo-plugin-helper/tickets.ts +122 -28
  76. package/shared/vendor/permission-audit/filter.ts +61 -0
  77. package/shared/vendor/permission-audit/index.ts +2 -0
  78. package/shared/vendor/permission-audit/shared.ts +119 -0
  79. package/shared/version.ts +1 -1
  80. package/client/vendor/paseo-plugin-helper/components/AboutSection.tsx +0 -493
  81. package/client/vendor/paseo-plugin-helper/components/AttentionBeacon.tsx +0 -250
  82. package/client/vendor/paseo-plugin-helper/components/Badge.tsx +0 -168
  83. package/client/vendor/paseo-plugin-helper/components/Button.tsx +0 -204
  84. package/client/vendor/paseo-plugin-helper/components/Card.tsx +0 -257
  85. package/client/vendor/paseo-plugin-helper/components/CodeBlock.tsx +0 -196
  86. package/client/vendor/paseo-plugin-helper/components/Collapsible.tsx +0 -277
  87. package/client/vendor/paseo-plugin-helper/components/CommandBox.tsx +0 -172
  88. package/client/vendor/paseo-plugin-helper/components/CopyButton.tsx +0 -180
  89. package/client/vendor/paseo-plugin-helper/components/EmptyState.tsx +0 -97
  90. package/client/vendor/paseo-plugin-helper/components/HighlightedText.tsx +0 -70
  91. package/client/vendor/paseo-plugin-helper/components/InlineButton.tsx +0 -88
  92. package/client/vendor/paseo-plugin-helper/components/InteractiveRow.tsx +0 -142
  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 -132
  98. package/client/vendor/paseo-plugin-helper/components/SectionHeader.tsx +0 -87
  99. package/client/vendor/paseo-plugin-helper/components/Select.tsx +0 -283
  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 -26
  106. package/client/vendor/paseo-plugin-helper/custom-pills.tsx +0 -225
  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 -30
  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 -368
  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/theme/flair.ts +0 -76
  121. package/client/vendor/paseo-plugin-helper/theme/host-variables.ts +0 -121
  122. package/client/vendor/paseo-plugin-helper/theme/index.ts +0 -7
  123. package/client/vendor/paseo-plugin-helper/theme/provider.tsx +0 -178
  124. package/client/vendor/paseo-plugin-helper/theme/responsive.ts +0 -213
  125. package/client/vendor/paseo-plugin-helper/theme/tokens.ts +0 -200
  126. package/client/vendor/paseo-plugin-helper/theme/useResponsive.ts +0 -57
  127. package/client/vendor/paseo-plugin-helper/tickets.tsx +0 -766
  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";