@omercnet/paseo-context-mode 3.1.3 → 3.1.4-next.434.1

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.
@@ -1,444 +0,0 @@
1
- import { type PluginSurfaceProps, useRpc, useSettings } from "@getpaseo/plugin/client";
2
- import { Icon } from "@getpaseo/plugin/client/react-native";
3
- import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
4
- import { useEffect, useMemo, useState } from "react";
5
- import { ActivityIndicator, Pressable, ScrollView, Text, View } from "react-native";
6
- import {
7
- type BinaryStatus,
8
- contextModeSettings,
9
- getContextModeIntegrationAudit,
10
- getContextModeStats,
11
- getContextModeStatus,
12
- type IntegrationAudit,
13
- } from "../shared";
14
- import { ActionsSection } from "./actions-section";
15
- import { AnalyticsDashboard } from "./analytics-dashboard";
16
- import { BUILTIN_KNOWLEDGE_PROVIDERS, KnowledgeSection } from "./knowledge-section";
17
- import { ScopedKnowledgeSection } from "./scoped-knowledge-section";
18
- import { parseScreenParams, type SurfaceTab } from "./screen-scope";
19
-
20
- interface ContextModeSurfaceProps extends PluginSurfaceProps {
21
- onOpenSettings(): void;
22
- params?: Record<string, string>;
23
- }
24
-
25
- function statusLabel(status: BinaryStatus): string {
26
- if (status.state === "ready") return "Connected";
27
- if (status.code === "not-installed") return "Not installed";
28
- if (status.code === "unsupported") return "Unsupported";
29
- return "Unavailable";
30
- }
31
-
32
- const SURFACE_TABS: ReadonlyArray<{ id: SurfaceTab; label: string }> = [
33
- { id: "savings", label: "Savings" },
34
- { id: "knowledge", label: "Knowledge" },
35
- { id: "setup", label: "Setup" },
36
- ];
37
-
38
- export function ContextModeSurface({
39
- theme,
40
- host,
41
- layout,
42
- onOpenSettings,
43
- params,
44
- }: ContextModeSurfaceProps) {
45
- const settings = useSettings(contextModeSettings);
46
- const loadStatus = useRpc(getContextModeStatus);
47
- const loadStats = useRpc(getContextModeStats);
48
- const loadIntegrationAudit = useRpc(getContextModeIntegrationAudit);
49
- const queryClient = useQueryClient();
50
- const screenParams = parseScreenParams(params ?? {});
51
- const [activeTab, setActiveTab] = useState<SurfaceTab>(screenParams.section);
52
- useEffect(
53
- () => setActiveTab(screenParams.section),
54
- [screenParams.section, screenParams.agentId, screenParams.workspaceId],
55
- );
56
- const readySettings = settings.status === "ready" ? settings : null;
57
- const refreshInterval = readySettings?.values.refreshIntervalMs ?? 15_000;
58
- const statusKey = ["context-mode", host.id, "status"] as const;
59
- const statsKey = ["context-mode", host.id, "stats"] as const;
60
- const integrationKey = ["context-mode", host.id, "integration"] as const;
61
- const status = useQuery({
62
- queryKey: statusKey,
63
- queryFn: () => loadStatus({ fresh: false }),
64
- refetchInterval: refreshInterval,
65
- enabled: readySettings !== null,
66
- });
67
- const stats = useQuery({
68
- queryKey: statsKey,
69
- queryFn: () => loadStats({ fresh: false }),
70
- refetchInterval: refreshInterval,
71
- enabled: readySettings !== null && status.data?.state === "ready",
72
- });
73
- const integration = useQuery({
74
- queryKey: integrationKey,
75
- queryFn: () => loadIntegrationAudit({ fresh: false }),
76
- refetchInterval: refreshInterval,
77
- enabled: readySettings !== null,
78
- });
79
- const auditedProviders = integration.data?.providers.map(({ provider }) => provider) ?? [];
80
- const knowledgeProviders = auditedProviders.length
81
- ? auditedProviders
82
- : BUILTIN_KNOWLEDGE_PROVIDERS;
83
- const refresh = useMutation({
84
- mutationFn: async () => {
85
- const nextStatus = await loadStatus({ fresh: true });
86
- const nextIntegration = await loadIntegrationAudit({ fresh: true });
87
- const nextStats =
88
- nextStatus.state === "ready" && nextStatus.supportsStats
89
- ? await loadStats({ fresh: true })
90
- : undefined;
91
- return { nextStatus, nextIntegration, nextStats };
92
- },
93
- onSuccess({ nextStatus, nextIntegration, nextStats }) {
94
- queryClient.setQueryData(statusKey, nextStatus);
95
- queryClient.setQueryData(integrationKey, nextIntegration);
96
- if (nextStats) queryClient.setQueryData(statsKey, nextStats);
97
- void queryClient.invalidateQueries({
98
- queryKey: ["context-mode", host.id, "analytics-dashboard"],
99
- });
100
- },
101
- });
102
- const styles = useMemo(
103
- () => ({
104
- screen: {
105
- flex: 1,
106
- backgroundColor: theme.colors.surface0,
107
- },
108
- content: {
109
- padding: layout.compact ? 14 : 24,
110
- gap: layout.compact ? 14 : 20,
111
- width: "100%" as const,
112
- maxWidth: 920,
113
- alignSelf: "center" as const,
114
- },
115
- header: { flexDirection: "row" as const, alignItems: "center" as const, gap: 12 },
116
- headerCopy: { flex: 1, gap: 2 },
117
- title: {
118
- color: theme.colors.foreground,
119
- fontSize: layout.compact ? 22 : 28,
120
- fontWeight: "700" as const,
121
- },
122
- subtitle: { color: theme.colors.foregroundMuted, fontSize: 13 },
123
- section: { gap: 8 },
124
- sectionTitle: {
125
- color: theme.colors.foreground,
126
- fontSize: 15,
127
- fontWeight: "700" as const,
128
- },
129
- card: {
130
- gap: 10,
131
- borderWidth: 1,
132
- borderColor: theme.colors.border,
133
- borderRadius: 12,
134
- padding: layout.compact ? 12 : 16,
135
- backgroundColor: theme.colors.surface1,
136
- },
137
- row: { flexDirection: "row" as const, alignItems: "center" as const, gap: 8 },
138
- grow: { flex: 1 },
139
- label: { color: theme.colors.foregroundMuted, fontSize: 12 },
140
- value: { color: theme.colors.foreground, fontSize: 14 },
141
- success: { color: theme.colors.statusSuccess, fontWeight: "600" as const },
142
- warning: { color: theme.colors.statusWarning, fontWeight: "600" as const },
143
- error: { color: theme.colors.statusDanger },
144
- output: {
145
- color: theme.colors.foreground,
146
- fontFamily: "monospace",
147
- fontSize: layout.compact ? 11 : 12,
148
- lineHeight: layout.compact ? 16 : 18,
149
- },
150
- button: {
151
- minHeight: 36,
152
- paddingHorizontal: 12,
153
- paddingVertical: 8,
154
- borderRadius: 8,
155
- backgroundColor: theme.colors.surface2,
156
- alignItems: "center" as const,
157
- justifyContent: "center" as const,
158
- },
159
- primaryButton: { backgroundColor: theme.colors.accent },
160
- buttonText: { color: theme.colors.foreground, fontWeight: "600" as const },
161
- primaryButtonText: { color: theme.colors.accentForeground, fontWeight: "600" as const },
162
- actions: { flexDirection: "row" as const, flexWrap: "wrap" as const, gap: 8 },
163
- healthLine: {
164
- flexDirection: "row" as const,
165
- alignItems: "center" as const,
166
- flexWrap: "wrap" as const,
167
- gap: 8,
168
- },
169
- chip: {
170
- borderRadius: 999,
171
- paddingHorizontal: 9,
172
- paddingVertical: 5,
173
- backgroundColor: theme.colors.surface2,
174
- },
175
- chipText: { color: theme.colors.foreground, fontSize: 11, fontWeight: "600" as const },
176
- tabRow: {
177
- flexDirection: "row" as const,
178
- gap: 6,
179
- padding: 4,
180
- borderRadius: 10,
181
- backgroundColor: theme.colors.surface1,
182
- },
183
- tab: {
184
- flex: 1,
185
- minHeight: 38,
186
- alignItems: "center" as const,
187
- justifyContent: "center" as const,
188
- borderRadius: 8,
189
- paddingHorizontal: 10,
190
- },
191
- tabActive: { backgroundColor: theme.colors.surface2 },
192
- tabText: { color: theme.colors.foregroundMuted, fontWeight: "600" as const },
193
- tabTextActive: { color: theme.colors.foreground },
194
- tabPanel: { width: "100%" as const },
195
- tabPanelHidden: { display: "none" as const },
196
- }),
197
- [layout.compact, theme],
198
- );
199
-
200
- if (settings.status === "loading") {
201
- return (
202
- <View style={[styles.screen, { alignItems: "center", justifyContent: "center" }]}>
203
- <ActivityIndicator color={theme.colors.accent} />
204
- <Text style={styles.subtitle}>Loading Context Mode settings…</Text>
205
- </View>
206
- );
207
- }
208
-
209
- if (settings.status !== "ready") {
210
- return (
211
- <View style={[styles.screen, styles.content]}>
212
- <Text style={styles.title}>Context Mode</Text>
213
- <Text accessibilityRole="alert" style={styles.error}>
214
- {settings.error}
215
- </Text>
216
- <Pressable accessibilityRole="button" onPress={settings.reload} style={styles.button}>
217
- <Text style={styles.buttonText}>Reload settings</Text>
218
- </Pressable>
219
- </View>
220
- );
221
- }
222
-
223
- const statusData = status.data;
224
-
225
- return (
226
- <ScrollView style={styles.screen} contentContainerStyle={styles.content}>
227
- <View style={styles.header}>
228
- <Icon name="Gauge" size={layout.compact ? 24 : 30} color={theme.colors.accent} />
229
- <View style={styles.headerCopy}>
230
- <Text style={styles.title}>Context Mode</Text>
231
- <Text style={styles.subtitle}>Context saved across your agents on {host.label}</Text>
232
- </View>
233
- </View>
234
-
235
- <AnalyticsDashboard
236
- display="hero"
237
- fallback={stats.data?.state === "ready" ? stats.data.output : null}
238
- health={
239
- <HealthStrip
240
- audit={integration.data}
241
- auditError={integration.error}
242
- auditPending={integration.isPending}
243
- onOpenSettings={onOpenSettings}
244
- onRefresh={() => refresh.mutate()}
245
- refreshError={refresh.error}
246
- refreshPending={refresh.isPending}
247
- status={statusData}
248
- statusError={status.error}
249
- statusPending={status.isPending}
250
- styles={styles}
251
- />
252
- }
253
- host={host}
254
- layout={layout}
255
- theme={theme}
256
- />
257
-
258
- <View accessibilityRole="tablist" style={styles.tabRow}>
259
- {SURFACE_TABS.map((tab) => {
260
- const selected = activeTab === tab.id;
261
- return (
262
- <Pressable
263
- key={tab.id}
264
- accessibilityRole="tab"
265
- accessibilityState={{ selected }}
266
- onPress={() => setActiveTab(tab.id)}
267
- style={[styles.tab, selected ? styles.tabActive : null]}
268
- >
269
- <Text style={[styles.tabText, selected ? styles.tabTextActive : null]}>
270
- {tab.label}
271
- </Text>
272
- </Pressable>
273
- );
274
- })}
275
- </View>
276
-
277
- <View style={[styles.tabPanel, activeTab === "savings" ? null : styles.tabPanelHidden]}>
278
- <AnalyticsDashboard
279
- display="details"
280
- fallback={stats.data?.state === "ready" ? stats.data.output : null}
281
- host={host}
282
- layout={layout}
283
- theme={theme}
284
- />
285
- </View>
286
- <View style={[styles.tabPanel, activeTab === "knowledge" ? null : styles.tabPanelHidden]}>
287
- {activeTab !== "knowledge" ? null : integration.isLoading ? (
288
- <Text style={{ color: theme.colors.foregroundMuted }}>Loading providers…</Text>
289
- ) : screenParams.agentId || screenParams.workspaceId ? (
290
- <ScopedKnowledgeSection
291
- key={JSON.stringify([host.id, screenParams.agentId, screenParams.workspaceId])}
292
- params={screenParams}
293
- host={host}
294
- layout={layout}
295
- theme={theme}
296
- providers={knowledgeProviders}
297
- />
298
- ) : (
299
- <KnowledgeSection
300
- key={host.id}
301
- host={host}
302
- layout={layout}
303
- theme={theme}
304
- providers={knowledgeProviders}
305
- />
306
- )}
307
- </View>
308
- <View style={[styles.tabPanel, activeTab === "setup" ? null : styles.tabPanelHidden]}>
309
- <ActionsSection key={host.id} layout={layout} theme={theme} />
310
- </View>
311
- </ScrollView>
312
- );
313
- }
314
-
315
- function HealthStrip({
316
- audit,
317
- auditError,
318
- auditPending,
319
- onOpenSettings,
320
- onRefresh,
321
- refreshError,
322
- refreshPending,
323
- status,
324
- statusError,
325
- statusPending,
326
- styles,
327
- }: {
328
- audit: IntegrationAudit | undefined;
329
- auditError: unknown;
330
- auditPending: boolean;
331
- onOpenSettings(): void;
332
- onRefresh(): void;
333
- refreshError: unknown;
334
- refreshPending: boolean;
335
- status: BinaryStatus | undefined;
336
- statusError: unknown;
337
- statusPending: boolean;
338
- styles: {
339
- actions: object;
340
- button: object;
341
- buttonText: object;
342
- card: object;
343
- chip: object;
344
- chipText: object;
345
- error: object;
346
- grow: object;
347
- healthLine: object;
348
- label: object;
349
- success: object;
350
- subtitle: object;
351
- value: object;
352
- warning: object;
353
- };
354
- }) {
355
- const [expanded, setExpanded] = useState(false);
356
- const covered =
357
- audit?.providers.filter((provider) => provider.activation !== "disabled").length ?? 0;
358
- const total = audit?.providers.length ?? 0;
359
- const nativeCount =
360
- audit?.providers.filter((provider) => provider.activation === "native").length ?? 0;
361
- const mcpCount = audit?.providers.filter((provider) => provider.activation === "mcp").length ?? 0;
362
- const statusReady = status?.state === "ready";
363
- const error = statusError ?? auditError ?? refreshError;
364
-
365
- return (
366
- <View style={styles.card}>
367
- <View style={styles.healthLine}>
368
- {statusPending || auditPending ? <ActivityIndicator size="small" /> : null}
369
- <Text style={statusReady ? styles.success : styles.warning}>
370
- {status ? statusLabel(status) : "Checking"}
371
- </Text>
372
- {statusReady && status.version ? <Text style={styles.value}>v{status.version}</Text> : null}
373
- <View style={styles.grow} />
374
- <Text style={styles.value}>
375
- {total > 0 ? `${covered} of ${total} providers covered` : "Coverage unavailable"}
376
- </Text>
377
- </View>
378
-
379
- {status?.state === "unavailable" ? (
380
- <Text style={styles.subtitle}>{status.message}</Text>
381
- ) : null}
382
- {error ? (
383
- <Text accessibilityRole="alert" style={styles.error}>
384
- {error instanceof Error ? error.message : String(error)}
385
- </Text>
386
- ) : null}
387
- {statusReady && (!status.supportsDoctor || !status.supportsStats) ? (
388
- <Text style={styles.warning}>
389
- Some Context Mode health or savings tools are unavailable.
390
- </Text>
391
- ) : null}
392
-
393
- <View style={styles.actions}>
394
- <Pressable
395
- accessibilityRole="button"
396
- accessibilityState={{ expanded }}
397
- onPress={() => setExpanded((value) => !value)}
398
- style={styles.button}
399
- >
400
- <Text style={styles.buttonText}>{expanded ? "Hide coverage" : "Coverage details"}</Text>
401
- </Pressable>
402
- <Pressable
403
- accessibilityRole="button"
404
- disabled={refreshPending}
405
- onPress={onRefresh}
406
- style={styles.button}
407
- >
408
- <Text style={styles.buttonText}>{refreshPending ? "Refreshing…" : "Refresh"}</Text>
409
- </Pressable>
410
- <Pressable accessibilityRole="button" onPress={onOpenSettings} style={styles.button}>
411
- <Text style={styles.buttonText}>Settings</Text>
412
- </Pressable>
413
- </View>
414
-
415
- {expanded && audit ? (
416
- <View style={{ gap: 8 }}>
417
- {mcpCount > 0 ? (
418
- <Text style={styles.subtitle}>
419
- MCP coverage applies to new agents. Existing agents may need recreation or manual
420
- setup.
421
- </Text>
422
- ) : null}
423
- <View style={styles.actions}>
424
- {audit.providers.map((provider) => (
425
- <View key={provider.provider} style={styles.chip}>
426
- <Text style={styles.chipText}>
427
- {provider.provider} ·{" "}
428
- {provider.activation === "native"
429
- ? "Native"
430
- : provider.activation === "mcp"
431
- ? "MCP · new agents"
432
- : "Off"}
433
- </Text>
434
- </View>
435
- ))}
436
- </View>
437
- <Text style={styles.label}>
438
- {nativeCount} native · {mcpCount} MCP · {total - covered} off
439
- </Text>
440
- </View>
441
- ) : null}
442
- </View>
443
- );
444
- }