@ateam-ai-mobile/plugin-runtime 0.1.5 → 0.1.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.
- package/dist/chunk-PKDQLNBL.mjs +2873 -0
- package/dist/chunk-PKDQLNBL.mjs.map +1 -0
- package/dist/index.d.mts +1759 -8
- package/dist/index.d.ts +1759 -8
- package/dist/index.js +2467 -1321
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +335 -2139
- package/dist/index.mjs.map +1 -1
- package/dist/loader-5LUVXUY2.mjs +9 -0
- package/dist/loader-5LUVXUY2.mjs.map +1 -0
- package/package.json +2 -2
- package/src/bundles/bundleManager.ts +368 -27
- package/src/bundles/integrity.ts +29 -0
- package/src/bundles/storage.ts +40 -0
- package/src/deviceStateHost.ts +197 -0
- package/src/hooks/usePlugin.ts +1 -1
- package/src/host.ts +80 -8
- package/src/index.ts +90 -1
- package/src/pluginRuntime.ts +97 -0
- package/src/sdk/CameraPreview.tsx +94 -0
- package/src/sdk/device-state.ts +1225 -0
- package/src/sdk/index.ts +35 -3
- package/src/sdk/registration.ts +39 -0
- package/src/sdk/types.ts +345 -8
- package/src/sdk/voice.ts +148 -0
- package/src/tools/loader.ts +167 -0
- package/src/tools/platformTools.ts +41 -0
- package/src/tools/registry.ts +183 -0
- package/src/tools/serverCalls.ts +78 -0
- package/src/tools/sessionSync.ts +107 -0
- package/src/voiceHost.ts +126 -0
|
@@ -0,0 +1,2873 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __export = (target, all) => {
|
|
3
|
+
for (var name in all)
|
|
4
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
// src/api/plugins.ts
|
|
8
|
+
import { apiFetch } from "@ateam-ai-mobile/core";
|
|
9
|
+
import { getCoreApiUrl } from "@ateam-ai-mobile/core";
|
|
10
|
+
function supportsNative(manifest) {
|
|
11
|
+
const mode = manifest.render?.mode;
|
|
12
|
+
return mode === "react-native" || mode === "adaptive";
|
|
13
|
+
}
|
|
14
|
+
function getNativeComponentId(manifest) {
|
|
15
|
+
if (manifest.render?.mode === "react-native") return manifest.render.component;
|
|
16
|
+
if (manifest.render?.mode === "adaptive") return manifest.render.reactNative?.component;
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
function getNativeBundleUrl(manifest) {
|
|
20
|
+
const direct = manifest.bundleUrl;
|
|
21
|
+
if (direct) return direct;
|
|
22
|
+
const render = manifest.render;
|
|
23
|
+
if (render.mode === "react-native") return render.bundleUrl || null;
|
|
24
|
+
if (render.mode === "adaptive") return render.reactNative?.bundleUrl || null;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function getNativeBundleHash(manifest) {
|
|
28
|
+
const direct = manifest.bundleHash;
|
|
29
|
+
if (direct) return direct;
|
|
30
|
+
const render = manifest.render;
|
|
31
|
+
if (render.mode === "react-native") return render.bundleHash || null;
|
|
32
|
+
if (render.mode === "adaptive") return render.reactNative?.bundleHash || null;
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
function supportsWebView(manifest) {
|
|
36
|
+
return manifest.render?.mode === "iframe" || manifest.render?.mode === "adaptive";
|
|
37
|
+
}
|
|
38
|
+
async function listPlugins() {
|
|
39
|
+
const res = await apiFetch("/mcp", {
|
|
40
|
+
method: "POST",
|
|
41
|
+
body: JSON.stringify({
|
|
42
|
+
jsonrpc: "2.0",
|
|
43
|
+
id: Date.now(),
|
|
44
|
+
method: "tools/call",
|
|
45
|
+
params: {
|
|
46
|
+
name: "cp.listContextPlugins",
|
|
47
|
+
arguments: {}
|
|
48
|
+
}
|
|
49
|
+
})
|
|
50
|
+
});
|
|
51
|
+
const structured = res.result?.structuredContent?.plugins;
|
|
52
|
+
if (structured) return structured;
|
|
53
|
+
const text = res.result?.content?.[0]?.text;
|
|
54
|
+
if (!text) return [];
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(text);
|
|
57
|
+
return parsed.plugins || parsed || [];
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
async function getPlugin(pluginId) {
|
|
63
|
+
const res = await apiFetch("/mcp", {
|
|
64
|
+
method: "POST",
|
|
65
|
+
body: JSON.stringify({
|
|
66
|
+
jsonrpc: "2.0",
|
|
67
|
+
id: Date.now(),
|
|
68
|
+
method: "tools/call",
|
|
69
|
+
params: {
|
|
70
|
+
name: "cp.getContextPlugin",
|
|
71
|
+
arguments: { id: pluginId }
|
|
72
|
+
// Backend expects { id }, NOT { pluginId }
|
|
73
|
+
}
|
|
74
|
+
})
|
|
75
|
+
});
|
|
76
|
+
if (res.result?.structuredContent) return res.result.structuredContent;
|
|
77
|
+
const text = res.result?.content?.[0]?.text;
|
|
78
|
+
if (!text) return null;
|
|
79
|
+
try {
|
|
80
|
+
const parsed = JSON.parse(text);
|
|
81
|
+
if (parsed.error) {
|
|
82
|
+
console.warn("[getPlugin] Backend error:", parsed.error);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
return parsed;
|
|
86
|
+
} catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
function resolvePluginUrl(manifest, tenant) {
|
|
91
|
+
const render = manifest.render;
|
|
92
|
+
let url;
|
|
93
|
+
if (render?.mode === "iframe") {
|
|
94
|
+
url = render.iframeUrl;
|
|
95
|
+
} else if (render?.mode === "adaptive") {
|
|
96
|
+
url = render.iframeUrl || render.iframe?.iframeUrl;
|
|
97
|
+
}
|
|
98
|
+
if (!url) return "";
|
|
99
|
+
if (url.startsWith("http")) {
|
|
100
|
+
const sep = url.includes("?") ? "&" : "?";
|
|
101
|
+
return tenant ? `${url}${sep}tenant=${encodeURIComponent(tenant)}` : url;
|
|
102
|
+
}
|
|
103
|
+
if (tenant && url.startsWith("/mcp-ui/")) {
|
|
104
|
+
const tenantUrl = url.replace("/mcp-ui/", `/mcp-ui/${encodeURIComponent(tenant)}/`);
|
|
105
|
+
return `${getCoreApiUrl()}${tenantUrl}`;
|
|
106
|
+
}
|
|
107
|
+
return `${getCoreApiUrl()}${url}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// src/bundles/bundleManager.ts
|
|
111
|
+
import React2 from "react";
|
|
112
|
+
import {
|
|
113
|
+
useState,
|
|
114
|
+
useEffect as useEffect6,
|
|
115
|
+
useCallback as useCallback2,
|
|
116
|
+
useMemo as useMemo4,
|
|
117
|
+
useRef as useRef4,
|
|
118
|
+
useContext,
|
|
119
|
+
useReducer,
|
|
120
|
+
useLayoutEffect,
|
|
121
|
+
useImperativeHandle,
|
|
122
|
+
useDebugValue
|
|
123
|
+
} from "react";
|
|
124
|
+
import * as ReactNative from "react-native";
|
|
125
|
+
import { apiFetchRaw, getTenant as getTenant2 } from "@ateam-ai-mobile/core";
|
|
126
|
+
|
|
127
|
+
// src/sdk/index.ts
|
|
128
|
+
var sdk_exports = {};
|
|
129
|
+
__export(sdk_exports, {
|
|
130
|
+
ActionButton: () => ActionButton,
|
|
131
|
+
ActivityIndicator: () => ActivityIndicator,
|
|
132
|
+
Animated: () => Animated,
|
|
133
|
+
Badge: () => Badge,
|
|
134
|
+
CameraPreview: () => CameraPreview,
|
|
135
|
+
Card: () => Card,
|
|
136
|
+
EmptyState: () => EmptyState,
|
|
137
|
+
ErrorState: () => ErrorState,
|
|
138
|
+
FlatList: () => FlatList,
|
|
139
|
+
Image: () => Image,
|
|
140
|
+
ListItem: () => ListItem,
|
|
141
|
+
LoadingState: () => LoadingState,
|
|
142
|
+
Modal: () => Modal,
|
|
143
|
+
PLATFORM_VOICE_CALLER: () => PLATFORM_VOICE_CALLER,
|
|
144
|
+
Platform: () => Platform,
|
|
145
|
+
PluginSDK: () => PluginSDK,
|
|
146
|
+
Pressable: () => Pressable,
|
|
147
|
+
RefreshControl: () => RefreshControl,
|
|
148
|
+
ScrollView: () => ScrollView,
|
|
149
|
+
SearchBar: () => SearchBar,
|
|
150
|
+
SectionList: () => SectionList,
|
|
151
|
+
StyleSheet: () => StyleSheet,
|
|
152
|
+
Switch: () => Switch,
|
|
153
|
+
TabBar: () => TabBar,
|
|
154
|
+
Text: () => Text,
|
|
155
|
+
TextInput: () => TextInput,
|
|
156
|
+
TouchableOpacity: () => TouchableOpacity,
|
|
157
|
+
View: () => View,
|
|
158
|
+
register: () => register,
|
|
159
|
+
registerService: () => registerService,
|
|
160
|
+
registerTools: () => registerTools,
|
|
161
|
+
setPluginThemeColors: () => setPluginThemeColors,
|
|
162
|
+
useApi: () => useApi,
|
|
163
|
+
usePluginTheme: () => usePluginTheme
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// src/sdk/registration.ts
|
|
167
|
+
function register(id, config) {
|
|
168
|
+
return {
|
|
169
|
+
id,
|
|
170
|
+
type: config.type || "ui",
|
|
171
|
+
capabilities: config.capabilities,
|
|
172
|
+
stateDomains: config.stateDomains,
|
|
173
|
+
Component: config.Component,
|
|
174
|
+
tools: config.tools
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
function registerTools(id, config) {
|
|
178
|
+
return {
|
|
179
|
+
id,
|
|
180
|
+
type: "tools",
|
|
181
|
+
capabilities: config.capabilities,
|
|
182
|
+
stateDomains: config.stateDomains,
|
|
183
|
+
tools: config.tools
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function registerService(id, config) {
|
|
187
|
+
return {
|
|
188
|
+
id,
|
|
189
|
+
type: "service",
|
|
190
|
+
capabilities: config.capabilities,
|
|
191
|
+
hooks: config.hooks,
|
|
192
|
+
onStart: config.onStart,
|
|
193
|
+
onStop: config.onStop
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/sdk/voice.ts
|
|
198
|
+
var PLATFORM_VOICE_CALLER = /* @__PURE__ */ Symbol("adas.voice.platform");
|
|
199
|
+
|
|
200
|
+
// src/sdk/primitives.ts
|
|
201
|
+
import {
|
|
202
|
+
View,
|
|
203
|
+
Text,
|
|
204
|
+
ScrollView,
|
|
205
|
+
FlatList,
|
|
206
|
+
SectionList,
|
|
207
|
+
Pressable,
|
|
208
|
+
TouchableOpacity,
|
|
209
|
+
TextInput,
|
|
210
|
+
Image,
|
|
211
|
+
ActivityIndicator,
|
|
212
|
+
Modal,
|
|
213
|
+
Switch,
|
|
214
|
+
StyleSheet,
|
|
215
|
+
Animated,
|
|
216
|
+
Platform,
|
|
217
|
+
RefreshControl
|
|
218
|
+
} from "react-native";
|
|
219
|
+
|
|
220
|
+
// src/sdk/components.ts
|
|
221
|
+
import React from "react";
|
|
222
|
+
import {
|
|
223
|
+
View as View2,
|
|
224
|
+
Text as Text2,
|
|
225
|
+
Pressable as Pressable2,
|
|
226
|
+
TextInput as TextInput2,
|
|
227
|
+
ActivityIndicator as ActivityIndicator2,
|
|
228
|
+
StyleSheet as StyleSheet2
|
|
229
|
+
} from "react-native";
|
|
230
|
+
function Card({ theme, children, style, onPress }) {
|
|
231
|
+
const cardStyle = [
|
|
232
|
+
styles.card,
|
|
233
|
+
{
|
|
234
|
+
backgroundColor: theme.colors.surface,
|
|
235
|
+
borderColor: theme.colors.border
|
|
236
|
+
},
|
|
237
|
+
style
|
|
238
|
+
];
|
|
239
|
+
if (onPress) {
|
|
240
|
+
return React.createElement(Pressable2, { style: cardStyle, onPress }, children);
|
|
241
|
+
}
|
|
242
|
+
return React.createElement(View2, { style: cardStyle }, children);
|
|
243
|
+
}
|
|
244
|
+
var BADGE_COLORS = {
|
|
245
|
+
accent: { bg: "accentSoft", text: "accent" },
|
|
246
|
+
success: { bg: "successSoft", text: "success" },
|
|
247
|
+
warning: { bg: "warningSoft", text: "warning" },
|
|
248
|
+
error: { bg: "errorSoft", text: "error" },
|
|
249
|
+
purple: { bg: "purpleSoft", text: "purple" }
|
|
250
|
+
};
|
|
251
|
+
function Badge({ theme, label, color = "accent", style }) {
|
|
252
|
+
const mapping = BADGE_COLORS[color];
|
|
253
|
+
return React.createElement(
|
|
254
|
+
View2,
|
|
255
|
+
{
|
|
256
|
+
style: [
|
|
257
|
+
styles.badge,
|
|
258
|
+
{
|
|
259
|
+
backgroundColor: theme.colors[mapping.bg],
|
|
260
|
+
borderRadius: theme.borderRadius.sm
|
|
261
|
+
},
|
|
262
|
+
style
|
|
263
|
+
]
|
|
264
|
+
},
|
|
265
|
+
React.createElement(Text2, {
|
|
266
|
+
style: [styles.badgeText, { color: theme.colors[mapping.text] }]
|
|
267
|
+
}, label)
|
|
268
|
+
);
|
|
269
|
+
}
|
|
270
|
+
function TabBar({ theme, tabs, activeTab, onTabChange, style }) {
|
|
271
|
+
return React.createElement(
|
|
272
|
+
View2,
|
|
273
|
+
{
|
|
274
|
+
style: [
|
|
275
|
+
styles.tabBar,
|
|
276
|
+
{ borderBottomColor: theme.colors.border },
|
|
277
|
+
style
|
|
278
|
+
]
|
|
279
|
+
},
|
|
280
|
+
tabs.map(
|
|
281
|
+
(tab) => React.createElement(
|
|
282
|
+
Pressable2,
|
|
283
|
+
{
|
|
284
|
+
key: tab,
|
|
285
|
+
style: [
|
|
286
|
+
styles.tab,
|
|
287
|
+
{
|
|
288
|
+
borderBottomColor: tab === activeTab ? theme.colors.accent : "transparent"
|
|
289
|
+
}
|
|
290
|
+
],
|
|
291
|
+
onPress: () => onTabChange(tab)
|
|
292
|
+
},
|
|
293
|
+
React.createElement(Text2, {
|
|
294
|
+
style: {
|
|
295
|
+
color: tab === activeTab ? theme.colors.accent : theme.colors.textSecondary,
|
|
296
|
+
fontSize: theme.fontSize.sm,
|
|
297
|
+
fontWeight: tab === activeTab ? "600" : "400"
|
|
298
|
+
}
|
|
299
|
+
}, tab)
|
|
300
|
+
)
|
|
301
|
+
)
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
function ListItem({ theme, title, subtitle, right, onPress, style }) {
|
|
305
|
+
const content = [
|
|
306
|
+
React.createElement(
|
|
307
|
+
View2,
|
|
308
|
+
{ key: "content", style: styles.listItemContent },
|
|
309
|
+
React.createElement(Text2, {
|
|
310
|
+
style: { color: theme.colors.text, fontSize: theme.fontSize.md },
|
|
311
|
+
numberOfLines: 1
|
|
312
|
+
}, title),
|
|
313
|
+
subtitle ? React.createElement(Text2, {
|
|
314
|
+
style: { color: theme.colors.textSecondary, fontSize: theme.fontSize.xs, marginTop: 2 },
|
|
315
|
+
numberOfLines: 1
|
|
316
|
+
}, subtitle) : null
|
|
317
|
+
),
|
|
318
|
+
right ? React.createElement(View2, { key: "right" }, right) : null
|
|
319
|
+
];
|
|
320
|
+
if (onPress) {
|
|
321
|
+
return React.createElement(
|
|
322
|
+
Pressable2,
|
|
323
|
+
{ style: [styles.listItem, { borderBottomColor: theme.colors.border }, style], onPress },
|
|
324
|
+
...content
|
|
325
|
+
);
|
|
326
|
+
}
|
|
327
|
+
return React.createElement(
|
|
328
|
+
View2,
|
|
329
|
+
{ style: [styles.listItem, { borderBottomColor: theme.colors.border }, style] },
|
|
330
|
+
...content
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
function SearchBar({ theme, value, onChangeText, placeholder = "Search...", style }) {
|
|
334
|
+
return React.createElement(TextInput2, {
|
|
335
|
+
style: [
|
|
336
|
+
styles.searchBar,
|
|
337
|
+
{
|
|
338
|
+
backgroundColor: theme.colors.input,
|
|
339
|
+
color: theme.colors.text,
|
|
340
|
+
borderColor: theme.colors.border,
|
|
341
|
+
borderRadius: theme.borderRadius.sm,
|
|
342
|
+
fontSize: theme.fontSize.sm
|
|
343
|
+
},
|
|
344
|
+
style
|
|
345
|
+
],
|
|
346
|
+
value,
|
|
347
|
+
onChangeText,
|
|
348
|
+
placeholder,
|
|
349
|
+
placeholderTextColor: theme.colors.textMuted,
|
|
350
|
+
autoCapitalize: "none",
|
|
351
|
+
autoCorrect: false
|
|
352
|
+
});
|
|
353
|
+
}
|
|
354
|
+
function EmptyState({ theme, title, subtitle, style }) {
|
|
355
|
+
return React.createElement(
|
|
356
|
+
View2,
|
|
357
|
+
{ style: [styles.emptyState, style] },
|
|
358
|
+
React.createElement(Text2, {
|
|
359
|
+
style: { color: theme.colors.textSecondary, fontSize: theme.fontSize.md, textAlign: "center" }
|
|
360
|
+
}, title),
|
|
361
|
+
subtitle ? React.createElement(Text2, {
|
|
362
|
+
style: { color: theme.colors.textMuted, fontSize: theme.fontSize.sm, textAlign: "center", marginTop: 4 }
|
|
363
|
+
}, subtitle) : null
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
function ErrorState({ theme, message, onRetry, style }) {
|
|
367
|
+
return React.createElement(
|
|
368
|
+
View2,
|
|
369
|
+
{ style: [styles.emptyState, style] },
|
|
370
|
+
React.createElement(Text2, {
|
|
371
|
+
style: { color: theme.colors.error, fontSize: theme.fontSize.md, textAlign: "center" }
|
|
372
|
+
}, message),
|
|
373
|
+
onRetry ? React.createElement(
|
|
374
|
+
Pressable2,
|
|
375
|
+
{
|
|
376
|
+
style: [styles.retryButton, { backgroundColor: theme.colors.accentSoft, borderRadius: theme.borderRadius.sm }],
|
|
377
|
+
onPress: onRetry
|
|
378
|
+
},
|
|
379
|
+
React.createElement(Text2, {
|
|
380
|
+
style: { color: theme.colors.accent, fontSize: theme.fontSize.sm, fontWeight: "600" }
|
|
381
|
+
}, "Retry")
|
|
382
|
+
) : null
|
|
383
|
+
);
|
|
384
|
+
}
|
|
385
|
+
function LoadingState({ theme, message = "Loading...", style }) {
|
|
386
|
+
return React.createElement(
|
|
387
|
+
View2,
|
|
388
|
+
{ style: [styles.emptyState, style] },
|
|
389
|
+
React.createElement(ActivityIndicator2, { size: "small", color: theme.colors.accent }),
|
|
390
|
+
React.createElement(Text2, {
|
|
391
|
+
style: { color: theme.colors.textSecondary, fontSize: theme.fontSize.sm, marginTop: 8 }
|
|
392
|
+
}, message)
|
|
393
|
+
);
|
|
394
|
+
}
|
|
395
|
+
function ActionButton({ theme, label, icon, onPress, style }) {
|
|
396
|
+
return React.createElement(
|
|
397
|
+
Pressable2,
|
|
398
|
+
{
|
|
399
|
+
style: [
|
|
400
|
+
styles.fab,
|
|
401
|
+
{ backgroundColor: theme.colors.accent, borderRadius: theme.borderRadius.full },
|
|
402
|
+
style
|
|
403
|
+
],
|
|
404
|
+
onPress
|
|
405
|
+
},
|
|
406
|
+
icon || React.createElement(Text2, { style: styles.fabText }, label || "+")
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
var styles = StyleSheet2.create({
|
|
410
|
+
card: {
|
|
411
|
+
padding: 12,
|
|
412
|
+
borderRadius: 12,
|
|
413
|
+
borderWidth: 1,
|
|
414
|
+
marginBottom: 8
|
|
415
|
+
},
|
|
416
|
+
badge: {
|
|
417
|
+
paddingHorizontal: 8,
|
|
418
|
+
paddingVertical: 3
|
|
419
|
+
},
|
|
420
|
+
badgeText: {
|
|
421
|
+
fontSize: 11,
|
|
422
|
+
fontWeight: "600"
|
|
423
|
+
},
|
|
424
|
+
tabBar: {
|
|
425
|
+
flexDirection: "row",
|
|
426
|
+
borderBottomWidth: 1
|
|
427
|
+
},
|
|
428
|
+
tab: {
|
|
429
|
+
flex: 1,
|
|
430
|
+
padding: 12,
|
|
431
|
+
alignItems: "center",
|
|
432
|
+
borderBottomWidth: 2
|
|
433
|
+
},
|
|
434
|
+
listItem: {
|
|
435
|
+
flexDirection: "row",
|
|
436
|
+
alignItems: "center",
|
|
437
|
+
paddingVertical: 12,
|
|
438
|
+
paddingHorizontal: 16,
|
|
439
|
+
borderBottomWidth: StyleSheet2.hairlineWidth
|
|
440
|
+
},
|
|
441
|
+
listItemContent: {
|
|
442
|
+
flex: 1
|
|
443
|
+
},
|
|
444
|
+
searchBar: {
|
|
445
|
+
height: 40,
|
|
446
|
+
paddingHorizontal: 12,
|
|
447
|
+
borderWidth: 1
|
|
448
|
+
},
|
|
449
|
+
emptyState: {
|
|
450
|
+
flex: 1,
|
|
451
|
+
justifyContent: "center",
|
|
452
|
+
alignItems: "center",
|
|
453
|
+
padding: 32
|
|
454
|
+
},
|
|
455
|
+
retryButton: {
|
|
456
|
+
marginTop: 12,
|
|
457
|
+
paddingHorizontal: 16,
|
|
458
|
+
paddingVertical: 8
|
|
459
|
+
},
|
|
460
|
+
fab: {
|
|
461
|
+
position: "absolute",
|
|
462
|
+
bottom: 24,
|
|
463
|
+
right: 24,
|
|
464
|
+
width: 56,
|
|
465
|
+
height: 56,
|
|
466
|
+
justifyContent: "center",
|
|
467
|
+
alignItems: "center",
|
|
468
|
+
shadowColor: "#000",
|
|
469
|
+
shadowOffset: { width: 0, height: 2 },
|
|
470
|
+
shadowOpacity: 0.25,
|
|
471
|
+
shadowRadius: 4,
|
|
472
|
+
elevation: 5
|
|
473
|
+
},
|
|
474
|
+
fabText: {
|
|
475
|
+
color: "#fff",
|
|
476
|
+
fontSize: 24,
|
|
477
|
+
fontWeight: "600"
|
|
478
|
+
}
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
// src/host.ts
|
|
482
|
+
var _haptics = null;
|
|
483
|
+
function setHapticsProvider(haptics) {
|
|
484
|
+
_haptics = haptics;
|
|
485
|
+
}
|
|
486
|
+
function getHaptics() {
|
|
487
|
+
return _haptics;
|
|
488
|
+
}
|
|
489
|
+
function unavailable(api) {
|
|
490
|
+
return new Proxy({}, {
|
|
491
|
+
get: (_t, method) => () => Promise.reject(new Error(`[adas] native.${api}.${String(method)} unavailable \u2014 host did not call setPluginNativeProvider()`))
|
|
492
|
+
});
|
|
493
|
+
}
|
|
494
|
+
var noopNative = {
|
|
495
|
+
camera: unavailable("camera"),
|
|
496
|
+
location: unavailable("location"),
|
|
497
|
+
storage: unavailable("storage"),
|
|
498
|
+
biometrics: unavailable("biometrics"),
|
|
499
|
+
biometricsV2: unavailable("biometricsV2"),
|
|
500
|
+
notifications: unavailable("notifications"),
|
|
501
|
+
haptics: { impact: () => {
|
|
502
|
+
}, notification: () => {
|
|
503
|
+
}, selection: () => {
|
|
504
|
+
}, error: () => {
|
|
505
|
+
} },
|
|
506
|
+
connectivity: { isOnline: () => true, onChange: () => () => {
|
|
507
|
+
} },
|
|
508
|
+
fileSystem: unavailable("fileSystem"),
|
|
509
|
+
cookies: unavailable("cookies"),
|
|
510
|
+
sharing: unavailable("sharing"),
|
|
511
|
+
scanner: unavailable("scanner"),
|
|
512
|
+
nfc: unavailable("nfc"),
|
|
513
|
+
bluetooth: unavailable("bluetooth"),
|
|
514
|
+
vision: unavailable("vision")
|
|
515
|
+
};
|
|
516
|
+
var _pluginNative = noopNative;
|
|
517
|
+
function setPluginNativeProvider(native) {
|
|
518
|
+
_pluginNative = native;
|
|
519
|
+
}
|
|
520
|
+
function getPluginNative() {
|
|
521
|
+
return _pluginNative;
|
|
522
|
+
}
|
|
523
|
+
function blocked(api, reason) {
|
|
524
|
+
return new Proxy({}, {
|
|
525
|
+
get: (_t, method) => {
|
|
526
|
+
if (typeof method !== "string") return void 0;
|
|
527
|
+
return () => {
|
|
528
|
+
console.warn(`[adas] native.${api}.${method} blocked \u2014 ${reason ?? `capability '${api}' not declared in the plugin manifest`}`);
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
function gatePluginNative(native, declared, pluginId) {
|
|
534
|
+
const caps = declared || {};
|
|
535
|
+
const scopedFileSystem = native.fileSystemFor && pluginId ? native.fileSystemFor(pluginId) : null;
|
|
536
|
+
const scopedStorage = native.storageFor && pluginId ? native.storageFor(pluginId) : null;
|
|
537
|
+
const gated = {
|
|
538
|
+
camera: caps.camera ? native.camera : blocked("camera"),
|
|
539
|
+
location: caps.location ? native.location : blocked("location"),
|
|
540
|
+
storage: caps.offline ? scopedStorage ?? blocked("storage", "no trusted pluginId to scope storage \u2014 refusing shared access (fail closed)") : blocked("storage"),
|
|
541
|
+
biometrics: caps.biometrics ? native.biometrics : blocked("biometrics"),
|
|
542
|
+
biometricsV2: caps.biometricsV2 ? native.biometricsV2 : blocked("biometricsV2"),
|
|
543
|
+
notifications: caps.notifications ? native.notifications : blocked("notifications"),
|
|
544
|
+
haptics: caps.haptics ? native.haptics : blocked("haptics"),
|
|
545
|
+
connectivity: native.connectivity,
|
|
546
|
+
fileSystem: caps.fileSystem ? scopedFileSystem ?? blocked("fileSystem", "no trusted pluginId to scope the filesystem \u2014 refusing shared access (fail closed)") : blocked("fileSystem"),
|
|
547
|
+
cookies: caps.cookies ? native.cookies : blocked("cookies"),
|
|
548
|
+
sharing: caps.sharing ? native.sharing : blocked("sharing"),
|
|
549
|
+
scanner: caps.scanner ? native.scanner : blocked("scanner"),
|
|
550
|
+
nfc: caps.nfc ? native.nfc : blocked("nfc"),
|
|
551
|
+
// Gated like the rest even though nothing implements it yet. An undeclared
|
|
552
|
+
// plugin gets the usual blocked proxy; a DECLARED one reaches an API whose
|
|
553
|
+
// every method refuses by name. Both are better than the `undefined` this
|
|
554
|
+
// used to be, which crashed with "cannot read properties of undefined".
|
|
555
|
+
bluetooth: caps.bluetooth ? native.bluetooth : blocked("bluetooth"),
|
|
556
|
+
vision: caps.vision ? native.vision : blocked("vision")
|
|
557
|
+
// What this plugin DECLARED, carried on the object it already receives.
|
|
558
|
+
//
|
|
559
|
+
// It is how a COMPONENT can be capability-gated: <CameraPreview> is handed
|
|
560
|
+
// `native` and reads this, so showing the camera obeys the same flag as
|
|
561
|
+
// calling it. Without this the preview would be a second door to the camera
|
|
562
|
+
// with only one lock. Non-enumerable so it cannot be mistaken for an API.
|
|
563
|
+
};
|
|
564
|
+
Object.defineProperty(gated, "__declaredCapabilities", {
|
|
565
|
+
value: Object.freeze({ ...caps }),
|
|
566
|
+
enumerable: false,
|
|
567
|
+
writable: false
|
|
568
|
+
});
|
|
569
|
+
return gated;
|
|
570
|
+
}
|
|
571
|
+
var _cameraPreview = null;
|
|
572
|
+
function setCameraPreviewProvider(Component) {
|
|
573
|
+
_cameraPreview = Component;
|
|
574
|
+
}
|
|
575
|
+
function getCameraPreviewProvider() {
|
|
576
|
+
return _cameraPreview;
|
|
577
|
+
}
|
|
578
|
+
var _nativeRegistry = /* @__PURE__ */ new Map();
|
|
579
|
+
function registerNativePlugin(id, plugin) {
|
|
580
|
+
_nativeRegistry.set(id, plugin);
|
|
581
|
+
}
|
|
582
|
+
function unregisterNativePlugin(id) {
|
|
583
|
+
_nativeRegistry.delete(id);
|
|
584
|
+
}
|
|
585
|
+
var nativePluginRegistry = {
|
|
586
|
+
has(id) {
|
|
587
|
+
return _nativeRegistry.has(id);
|
|
588
|
+
},
|
|
589
|
+
async get(id) {
|
|
590
|
+
const entry = _nativeRegistry.get(id);
|
|
591
|
+
if (!entry) return null;
|
|
592
|
+
if (typeof entry === "function") {
|
|
593
|
+
try {
|
|
594
|
+
return await entry();
|
|
595
|
+
} catch (err) {
|
|
596
|
+
console.warn(`[adas-plugins] lazy native plugin '${id}' failed to load:`, err);
|
|
597
|
+
return null;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
return entry;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
var _bundleModules = /* @__PURE__ */ new Map();
|
|
604
|
+
function registerBundleModules(modules) {
|
|
605
|
+
for (const [name, mod] of Object.entries(modules)) {
|
|
606
|
+
_bundleModules.set(name, mod);
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
function resolveBundleModule(name) {
|
|
610
|
+
return _bundleModules.get(name);
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
// src/sdk/CameraPreview.tsx
|
|
614
|
+
import { jsx } from "react/jsx-runtime";
|
|
615
|
+
function declaredCapabilities(native) {
|
|
616
|
+
return native && native.__declaredCapabilities || null;
|
|
617
|
+
}
|
|
618
|
+
function CameraPreview({ native, facing = "back", style, renderUnavailable }) {
|
|
619
|
+
const caps = declaredCapabilities(native);
|
|
620
|
+
if (caps && !caps.camera) {
|
|
621
|
+
return unavailable2(
|
|
622
|
+
"this plugin did not declare `capabilities: { camera: true }`, so it may not show the camera",
|
|
623
|
+
renderUnavailable,
|
|
624
|
+
style
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
const Provider = getCameraPreviewProvider();
|
|
628
|
+
if (!Provider) {
|
|
629
|
+
return unavailable2(
|
|
630
|
+
"this app does not provide a camera preview (the host has not called setCameraPreviewProvider)",
|
|
631
|
+
renderUnavailable,
|
|
632
|
+
style
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
return /* @__PURE__ */ jsx(Provider, { facing, style, renderUnavailable });
|
|
636
|
+
}
|
|
637
|
+
function unavailable2(reason, render, style) {
|
|
638
|
+
if (render) return render(reason);
|
|
639
|
+
return /* @__PURE__ */ jsx(View, { style: [{ flex: 1, backgroundColor: "#000", alignItems: "center", justifyContent: "center", padding: 20 }, style], children: /* @__PURE__ */ jsx(Text, { style: { color: "#39FF66", fontSize: 12, textAlign: "center" }, children: `NO CAMERA PREVIEW
|
|
640
|
+
${reason}` }) });
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// src/sdk/usePluginTheme.ts
|
|
644
|
+
import { useMemo, useSyncExternalStore } from "react";
|
|
645
|
+
import { useColorScheme } from "react-native";
|
|
646
|
+
import { ada, spacing, fontSize, borderRadius } from "@ateam-ai-mobile/core";
|
|
647
|
+
var adaColors = {
|
|
648
|
+
bg: ada.paper,
|
|
649
|
+
surface: ada.paperSoft,
|
|
650
|
+
surfaceAlt: ada.paperDeep,
|
|
651
|
+
input: ada.paper,
|
|
652
|
+
border: ada.border,
|
|
653
|
+
text: ada.ink,
|
|
654
|
+
textSecondary: ada.inkSoft,
|
|
655
|
+
textMuted: ada.inkMuted,
|
|
656
|
+
accent: ada.orange,
|
|
657
|
+
accentSoft: ada.orangeSoft,
|
|
658
|
+
success: "#5A8A5C",
|
|
659
|
+
successSoft: "rgba(90,138,92,0.14)",
|
|
660
|
+
warning: "#D49A4E",
|
|
661
|
+
warningSoft: "rgba(212,154,78,0.16)",
|
|
662
|
+
error: "#C15545",
|
|
663
|
+
errorSoft: "rgba(193,85,69,0.14)",
|
|
664
|
+
purple: ada.orangeDeep,
|
|
665
|
+
purpleSoft: ada.orangeSoft
|
|
666
|
+
};
|
|
667
|
+
var _colors = adaColors;
|
|
668
|
+
var _isDark = null;
|
|
669
|
+
var _subscribers = /* @__PURE__ */ new Set();
|
|
670
|
+
function setPluginThemeColors(colors, opts) {
|
|
671
|
+
_colors = { ...adaColors, ...colors };
|
|
672
|
+
if (opts?.isDark !== void 0) _isDark = opts.isDark;
|
|
673
|
+
_subscribers.forEach((fn) => fn());
|
|
674
|
+
}
|
|
675
|
+
function subscribe(fn) {
|
|
676
|
+
_subscribers.add(fn);
|
|
677
|
+
return () => {
|
|
678
|
+
_subscribers.delete(fn);
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
function usePluginTheme() {
|
|
682
|
+
const colors = useSyncExternalStore(subscribe, () => _colors);
|
|
683
|
+
const hostIsDark = useSyncExternalStore(subscribe, () => _isDark);
|
|
684
|
+
const scheme = useColorScheme();
|
|
685
|
+
const isDark = hostIsDark ?? scheme === "dark";
|
|
686
|
+
return useMemo(() => ({
|
|
687
|
+
colors,
|
|
688
|
+
spacing: {
|
|
689
|
+
xs: spacing.xs,
|
|
690
|
+
sm: spacing.sm,
|
|
691
|
+
md: spacing.md,
|
|
692
|
+
lg: spacing.lg,
|
|
693
|
+
xl: spacing.xl,
|
|
694
|
+
xxl: spacing.xxl
|
|
695
|
+
},
|
|
696
|
+
fontSize: {
|
|
697
|
+
xs: fontSize.xs,
|
|
698
|
+
sm: fontSize.sm,
|
|
699
|
+
md: fontSize.md,
|
|
700
|
+
lg: fontSize.lg,
|
|
701
|
+
xl: fontSize.xl
|
|
702
|
+
},
|
|
703
|
+
borderRadius: {
|
|
704
|
+
sm: borderRadius.sm,
|
|
705
|
+
md: borderRadius.md,
|
|
706
|
+
lg: borderRadius.lg,
|
|
707
|
+
full: borderRadius.full
|
|
708
|
+
},
|
|
709
|
+
isDark
|
|
710
|
+
}), [colors, isDark]);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// src/sdk/useApi.ts
|
|
714
|
+
import { useMemo as useMemo2 } from "react";
|
|
715
|
+
function useApi(bridge) {
|
|
716
|
+
return useMemo2(() => {
|
|
717
|
+
const apiFn = async (connectorId, tool, args = {}) => {
|
|
718
|
+
if (!bridge) {
|
|
719
|
+
throw new Error("useApi: bridge is required. Pass it from plugin props.");
|
|
720
|
+
}
|
|
721
|
+
return bridge.mcpCall(tool, args, connectorId);
|
|
722
|
+
};
|
|
723
|
+
const callMethod = async (first, second, third) => {
|
|
724
|
+
if (!bridge) {
|
|
725
|
+
throw new Error("useApi: bridge is required. Pass it from plugin props.");
|
|
726
|
+
}
|
|
727
|
+
if (typeof second === "string") {
|
|
728
|
+
return bridge.mcpCall(second, third || {}, first);
|
|
729
|
+
}
|
|
730
|
+
if (typeof third === "string") {
|
|
731
|
+
return bridge.mcpCall(first, second || {}, third);
|
|
732
|
+
}
|
|
733
|
+
return bridge.mcpCall(first, second || {});
|
|
734
|
+
};
|
|
735
|
+
Object.defineProperty(apiFn, "call", {
|
|
736
|
+
value: callMethod,
|
|
737
|
+
writable: false,
|
|
738
|
+
configurable: false
|
|
739
|
+
});
|
|
740
|
+
return apiFn;
|
|
741
|
+
}, [bridge]);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/sdk/index.ts
|
|
745
|
+
var PluginSDK = { register, registerService, registerTools };
|
|
746
|
+
|
|
747
|
+
// src/surfaces/SurfaceDrawer.tsx
|
|
748
|
+
import { useEffect as useEffect2, useRef } from "react";
|
|
749
|
+
import {
|
|
750
|
+
View as View4,
|
|
751
|
+
Text as Text3,
|
|
752
|
+
Pressable as Pressable3,
|
|
753
|
+
StyleSheet as StyleSheet4,
|
|
754
|
+
Animated as Animated3,
|
|
755
|
+
Dimensions,
|
|
756
|
+
Platform as Platform2
|
|
757
|
+
} from "react-native";
|
|
758
|
+
import Svg2, { Path } from "react-native-svg";
|
|
759
|
+
import { ada as ada2 } from "@ateam-ai-mobile/core";
|
|
760
|
+
|
|
761
|
+
// src/surfaces/AdaOrb.tsx
|
|
762
|
+
import { useEffect } from "react";
|
|
763
|
+
import { View as View3, StyleSheet as StyleSheet3 } from "react-native";
|
|
764
|
+
import Animated2, {
|
|
765
|
+
useSharedValue,
|
|
766
|
+
useAnimatedProps,
|
|
767
|
+
withRepeat,
|
|
768
|
+
withTiming,
|
|
769
|
+
withSequence,
|
|
770
|
+
Easing,
|
|
771
|
+
cancelAnimation
|
|
772
|
+
} from "react-native-reanimated";
|
|
773
|
+
import Svg, { Circle, Defs, RadialGradient, Stop, Text as SvgText } from "react-native-svg";
|
|
774
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
775
|
+
var AnimatedCircle = Animated2.createAnimatedComponent(Circle);
|
|
776
|
+
function AdaOrb({ size = 120, state = "idle", inscription = true }) {
|
|
777
|
+
const showInscription = inscription && size >= 60;
|
|
778
|
+
const center = size / 2;
|
|
779
|
+
const coreR = size * 0.32;
|
|
780
|
+
const innerHaloR = size * 0.42;
|
|
781
|
+
const outerHaloR = size * 0.5;
|
|
782
|
+
const coreScale = useSharedValue(1);
|
|
783
|
+
const innerScale = useSharedValue(1);
|
|
784
|
+
const outerScale = useSharedValue(1);
|
|
785
|
+
const outerOpacity = useSharedValue(0.55);
|
|
786
|
+
const rippleCycle = useSharedValue(0);
|
|
787
|
+
const orbit1 = useSharedValue(0);
|
|
788
|
+
const orbit2 = useSharedValue(0);
|
|
789
|
+
const orbit3 = useSharedValue(0);
|
|
790
|
+
const beat = useSharedValue(1);
|
|
791
|
+
useEffect(() => {
|
|
792
|
+
cancelAnimation(coreScale);
|
|
793
|
+
cancelAnimation(innerScale);
|
|
794
|
+
cancelAnimation(outerScale);
|
|
795
|
+
cancelAnimation(outerOpacity);
|
|
796
|
+
cancelAnimation(rippleCycle);
|
|
797
|
+
cancelAnimation(orbit1);
|
|
798
|
+
cancelAnimation(orbit2);
|
|
799
|
+
cancelAnimation(orbit3);
|
|
800
|
+
cancelAnimation(beat);
|
|
801
|
+
rippleCycle.value = 0;
|
|
802
|
+
orbit1.value = 0;
|
|
803
|
+
orbit2.value = 0;
|
|
804
|
+
orbit3.value = 0;
|
|
805
|
+
beat.value = 1;
|
|
806
|
+
let breathMs = 3600;
|
|
807
|
+
let breathAmount = 0.025;
|
|
808
|
+
if (state === "listening") {
|
|
809
|
+
breathMs = 2e3;
|
|
810
|
+
breathAmount = 0.05;
|
|
811
|
+
rippleCycle.value = withRepeat(
|
|
812
|
+
withTiming(1, { duration: 2e3, easing: Easing.out(Easing.quad) }),
|
|
813
|
+
-1,
|
|
814
|
+
false
|
|
815
|
+
);
|
|
816
|
+
} else if (state === "thinking") {
|
|
817
|
+
breathMs = 2400;
|
|
818
|
+
breathAmount = 0.04;
|
|
819
|
+
orbit1.value = withRepeat(
|
|
820
|
+
withTiming(1, { duration: 1100, easing: Easing.linear }),
|
|
821
|
+
-1,
|
|
822
|
+
false
|
|
823
|
+
);
|
|
824
|
+
orbit2.value = withRepeat(
|
|
825
|
+
withTiming(1, { duration: 1900, easing: Easing.linear }),
|
|
826
|
+
-1,
|
|
827
|
+
false
|
|
828
|
+
);
|
|
829
|
+
orbit3.value = withRepeat(
|
|
830
|
+
withTiming(-1, { duration: 2700, easing: Easing.linear }),
|
|
831
|
+
-1,
|
|
832
|
+
false
|
|
833
|
+
);
|
|
834
|
+
} else if (state === "speaking") {
|
|
835
|
+
breathMs = 700;
|
|
836
|
+
breathAmount = 0.13;
|
|
837
|
+
beat.value = withRepeat(
|
|
838
|
+
withSequence(
|
|
839
|
+
withTiming(1.08, { duration: 90, easing: Easing.out(Easing.quad) }),
|
|
840
|
+
withTiming(1, { duration: 130, easing: Easing.in(Easing.quad) }),
|
|
841
|
+
withTiming(1.05, { duration: 80, easing: Easing.out(Easing.quad) }),
|
|
842
|
+
withTiming(1, { duration: 200, easing: Easing.in(Easing.quad) }),
|
|
843
|
+
withTiming(1, { duration: 280 })
|
|
844
|
+
),
|
|
845
|
+
-1,
|
|
846
|
+
false
|
|
847
|
+
);
|
|
848
|
+
}
|
|
849
|
+
const breathEasing = state === "speaking" ? Easing.out(Easing.cubic) : Easing.inOut(Easing.sin);
|
|
850
|
+
coreScale.value = withRepeat(
|
|
851
|
+
withSequence(
|
|
852
|
+
withTiming(1 + breathAmount, { duration: breathMs, easing: breathEasing }),
|
|
853
|
+
withTiming(1, { duration: breathMs, easing: breathEasing })
|
|
854
|
+
),
|
|
855
|
+
-1,
|
|
856
|
+
false
|
|
857
|
+
);
|
|
858
|
+
innerScale.value = withRepeat(
|
|
859
|
+
withSequence(
|
|
860
|
+
withTiming(1 + breathAmount * 1.4, { duration: breathMs * 1.15, easing: breathEasing }),
|
|
861
|
+
withTiming(1, { duration: breathMs * 1.15, easing: breathEasing })
|
|
862
|
+
),
|
|
863
|
+
-1,
|
|
864
|
+
false
|
|
865
|
+
);
|
|
866
|
+
outerScale.value = withRepeat(
|
|
867
|
+
withSequence(
|
|
868
|
+
withTiming(1 + breathAmount * 1.8, { duration: breathMs * 1.3, easing: breathEasing }),
|
|
869
|
+
withTiming(1, { duration: breathMs * 1.3, easing: breathEasing })
|
|
870
|
+
),
|
|
871
|
+
-1,
|
|
872
|
+
false
|
|
873
|
+
);
|
|
874
|
+
outerOpacity.value = withRepeat(
|
|
875
|
+
withSequence(
|
|
876
|
+
withTiming(state === "idle" ? 0.55 : 0.8, { duration: breathMs * 1.3, easing: breathEasing }),
|
|
877
|
+
withTiming(0.35, { duration: breathMs * 1.3, easing: breathEasing })
|
|
878
|
+
),
|
|
879
|
+
-1,
|
|
880
|
+
false
|
|
881
|
+
);
|
|
882
|
+
}, [state]);
|
|
883
|
+
const coreProps = useAnimatedProps(() => ({
|
|
884
|
+
r: coreR * coreScale.value * beat.value
|
|
885
|
+
}));
|
|
886
|
+
const innerProps = useAnimatedProps(() => ({
|
|
887
|
+
r: innerHaloR * innerScale.value * beat.value
|
|
888
|
+
}));
|
|
889
|
+
const outerProps = useAnimatedProps(() => ({
|
|
890
|
+
r: outerHaloR * outerScale.value,
|
|
891
|
+
opacity: outerOpacity.value
|
|
892
|
+
}));
|
|
893
|
+
const ripple1Props = useAnimatedProps(() => {
|
|
894
|
+
const t = rippleCycle.value;
|
|
895
|
+
return {
|
|
896
|
+
r: outerHaloR * (1 + t * 0.55),
|
|
897
|
+
opacity: state === "listening" ? 0.55 * (1 - t) : 0
|
|
898
|
+
};
|
|
899
|
+
});
|
|
900
|
+
const ripple2Props = useAnimatedProps(() => {
|
|
901
|
+
const t = (rippleCycle.value + 0.34) % 1;
|
|
902
|
+
return {
|
|
903
|
+
r: outerHaloR * (1 + t * 0.55),
|
|
904
|
+
opacity: state === "listening" ? 0.45 * (1 - t) : 0
|
|
905
|
+
};
|
|
906
|
+
});
|
|
907
|
+
const ripple3Props = useAnimatedProps(() => {
|
|
908
|
+
const t = (rippleCycle.value + 0.67) % 1;
|
|
909
|
+
return {
|
|
910
|
+
r: outerHaloR * (1 + t * 0.55),
|
|
911
|
+
opacity: state === "listening" ? 0.35 * (1 - t) : 0
|
|
912
|
+
};
|
|
913
|
+
});
|
|
914
|
+
const spark1Props = useAnimatedProps(() => {
|
|
915
|
+
const angle = orbit1.value * Math.PI * 2 - Math.PI / 2;
|
|
916
|
+
const r = coreR * 0.55;
|
|
917
|
+
return {
|
|
918
|
+
cx: center + Math.cos(angle) * r,
|
|
919
|
+
cy: center + Math.sin(angle) * r,
|
|
920
|
+
opacity: state === "thinking" ? 1 : 0
|
|
921
|
+
};
|
|
922
|
+
});
|
|
923
|
+
const spark2Props = useAnimatedProps(() => {
|
|
924
|
+
const angle = orbit2.value * Math.PI * 2 + Math.PI * 0.6;
|
|
925
|
+
const r = coreR * 0.85;
|
|
926
|
+
return {
|
|
927
|
+
cx: center + Math.cos(angle) * r,
|
|
928
|
+
cy: center + Math.sin(angle) * r,
|
|
929
|
+
opacity: state === "thinking" ? 0.9 : 0
|
|
930
|
+
};
|
|
931
|
+
});
|
|
932
|
+
const spark3Props = useAnimatedProps(() => {
|
|
933
|
+
const angle = orbit3.value * Math.PI * 2 + Math.PI * 1.3;
|
|
934
|
+
const r = coreR * 1.05;
|
|
935
|
+
return {
|
|
936
|
+
cx: center + Math.cos(angle) * r,
|
|
937
|
+
cy: center + Math.sin(angle) * r,
|
|
938
|
+
opacity: state === "thinking" ? 0.7 : 0
|
|
939
|
+
};
|
|
940
|
+
});
|
|
941
|
+
return /* @__PURE__ */ jsx2(View3, { style: [styles2.wrap, { width: size, height: size }], children: /* @__PURE__ */ jsxs(Svg, { width: size, height: size, children: [
|
|
942
|
+
/* @__PURE__ */ jsxs(Defs, { children: [
|
|
943
|
+
/* @__PURE__ */ jsxs(RadialGradient, { id: "adaCore", cx: "36%", cy: "30%", r: "70%", children: [
|
|
944
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "0%", stopColor: "#FFE0B0", stopOpacity: "1" }),
|
|
945
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "28%", stopColor: "#FFA85A", stopOpacity: "1" }),
|
|
946
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "65%", stopColor: "#FF7A28", stopOpacity: "1" }),
|
|
947
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "100%", stopColor: "#B8410A", stopOpacity: "1" })
|
|
948
|
+
] }),
|
|
949
|
+
/* @__PURE__ */ jsxs(RadialGradient, { id: "adaInner", cx: "50%", cy: "50%", r: "50%", children: [
|
|
950
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "0%", stopColor: "#FF8A3C", stopOpacity: "0.55" }),
|
|
951
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "100%", stopColor: "#FF7028", stopOpacity: "0" })
|
|
952
|
+
] }),
|
|
953
|
+
/* @__PURE__ */ jsxs(RadialGradient, { id: "adaOuter", cx: "50%", cy: "50%", r: "50%", children: [
|
|
954
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "0%", stopColor: "#FF7A28", stopOpacity: "0.4" }),
|
|
955
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "100%", stopColor: "#FF6018", stopOpacity: "0" })
|
|
956
|
+
] }),
|
|
957
|
+
/* @__PURE__ */ jsxs(RadialGradient, { id: "adaSpark", cx: "50%", cy: "50%", r: "50%", children: [
|
|
958
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "0%", stopColor: "#FFFAEB", stopOpacity: "1" }),
|
|
959
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "60%", stopColor: "#FFD095", stopOpacity: "0.9" }),
|
|
960
|
+
/* @__PURE__ */ jsx2(Stop, { offset: "100%", stopColor: "#FFA85A", stopOpacity: "0" })
|
|
961
|
+
] })
|
|
962
|
+
] }),
|
|
963
|
+
/* @__PURE__ */ jsx2(
|
|
964
|
+
AnimatedCircle,
|
|
965
|
+
{
|
|
966
|
+
cx: center,
|
|
967
|
+
cy: center,
|
|
968
|
+
stroke: "#FF7A28",
|
|
969
|
+
strokeWidth: 1.5,
|
|
970
|
+
fill: "none",
|
|
971
|
+
animatedProps: ripple1Props
|
|
972
|
+
}
|
|
973
|
+
),
|
|
974
|
+
/* @__PURE__ */ jsx2(
|
|
975
|
+
AnimatedCircle,
|
|
976
|
+
{
|
|
977
|
+
cx: center,
|
|
978
|
+
cy: center,
|
|
979
|
+
stroke: "#FF7A28",
|
|
980
|
+
strokeWidth: 1.5,
|
|
981
|
+
fill: "none",
|
|
982
|
+
animatedProps: ripple2Props
|
|
983
|
+
}
|
|
984
|
+
),
|
|
985
|
+
/* @__PURE__ */ jsx2(
|
|
986
|
+
AnimatedCircle,
|
|
987
|
+
{
|
|
988
|
+
cx: center,
|
|
989
|
+
cy: center,
|
|
990
|
+
stroke: "#FF7A28",
|
|
991
|
+
strokeWidth: 1.5,
|
|
992
|
+
fill: "none",
|
|
993
|
+
animatedProps: ripple3Props
|
|
994
|
+
}
|
|
995
|
+
),
|
|
996
|
+
/* @__PURE__ */ jsx2(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaOuter)", animatedProps: outerProps }),
|
|
997
|
+
/* @__PURE__ */ jsx2(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaInner)", animatedProps: innerProps }),
|
|
998
|
+
/* @__PURE__ */ jsx2(AnimatedCircle, { cx: center, cy: center, fill: "url(#adaCore)", animatedProps: coreProps }),
|
|
999
|
+
/* @__PURE__ */ jsx2(
|
|
1000
|
+
AnimatedCircle,
|
|
1001
|
+
{
|
|
1002
|
+
r: Math.max(3, size * 0.05),
|
|
1003
|
+
fill: "url(#adaSpark)",
|
|
1004
|
+
animatedProps: spark1Props
|
|
1005
|
+
}
|
|
1006
|
+
),
|
|
1007
|
+
/* @__PURE__ */ jsx2(
|
|
1008
|
+
AnimatedCircle,
|
|
1009
|
+
{
|
|
1010
|
+
r: Math.max(2.5, size * 0.04),
|
|
1011
|
+
fill: "url(#adaSpark)",
|
|
1012
|
+
animatedProps: spark2Props
|
|
1013
|
+
}
|
|
1014
|
+
),
|
|
1015
|
+
/* @__PURE__ */ jsx2(
|
|
1016
|
+
AnimatedCircle,
|
|
1017
|
+
{
|
|
1018
|
+
r: Math.max(2, size * 0.032),
|
|
1019
|
+
fill: "url(#adaSpark)",
|
|
1020
|
+
animatedProps: spark3Props
|
|
1021
|
+
}
|
|
1022
|
+
),
|
|
1023
|
+
showInscription && /* @__PURE__ */ jsx2(
|
|
1024
|
+
SvgText,
|
|
1025
|
+
{
|
|
1026
|
+
x: center,
|
|
1027
|
+
y: center + size * 0.11,
|
|
1028
|
+
fontSize: size * 0.34,
|
|
1029
|
+
fontWeight: "600",
|
|
1030
|
+
fill: "#FFF5E0",
|
|
1031
|
+
textAnchor: "middle",
|
|
1032
|
+
letterSpacing: -size * 5e-3,
|
|
1033
|
+
children: "iA"
|
|
1034
|
+
}
|
|
1035
|
+
)
|
|
1036
|
+
] }) });
|
|
1037
|
+
}
|
|
1038
|
+
var styles2 = StyleSheet3.create({
|
|
1039
|
+
wrap: {
|
|
1040
|
+
alignItems: "center",
|
|
1041
|
+
justifyContent: "center"
|
|
1042
|
+
}
|
|
1043
|
+
});
|
|
1044
|
+
|
|
1045
|
+
// src/surfaces/SurfaceDrawer.tsx
|
|
1046
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
1047
|
+
var SCREEN_H = Dimensions.get("window").height;
|
|
1048
|
+
var fontFam = Platform2.OS === "ios" ? "SF Pro Rounded" : void 0;
|
|
1049
|
+
function SurfaceDrawer({
|
|
1050
|
+
visible,
|
|
1051
|
+
onClose,
|
|
1052
|
+
title,
|
|
1053
|
+
subtitle,
|
|
1054
|
+
privacy,
|
|
1055
|
+
showOrb = true,
|
|
1056
|
+
heightRatio = 0.85,
|
|
1057
|
+
children
|
|
1058
|
+
}) {
|
|
1059
|
+
const sheetH = Math.round(SCREEN_H * heightRatio);
|
|
1060
|
+
const fadeAnim = useRef(new Animated3.Value(0)).current;
|
|
1061
|
+
const slideAnim = useRef(new Animated3.Value(sheetH)).current;
|
|
1062
|
+
useEffect2(() => {
|
|
1063
|
+
if (visible) {
|
|
1064
|
+
Animated3.parallel([
|
|
1065
|
+
Animated3.timing(fadeAnim, { toValue: 1, duration: 220, useNativeDriver: true }),
|
|
1066
|
+
Animated3.spring(slideAnim, { toValue: 0, useNativeDriver: true, tension: 80, friction: 12 })
|
|
1067
|
+
]).start();
|
|
1068
|
+
} else {
|
|
1069
|
+
fadeAnim.setValue(0);
|
|
1070
|
+
slideAnim.setValue(sheetH);
|
|
1071
|
+
}
|
|
1072
|
+
}, [visible]);
|
|
1073
|
+
const close = () => {
|
|
1074
|
+
Animated3.parallel([
|
|
1075
|
+
Animated3.timing(fadeAnim, { toValue: 0, duration: 160, useNativeDriver: true }),
|
|
1076
|
+
Animated3.timing(slideAnim, { toValue: sheetH, duration: 200, useNativeDriver: true })
|
|
1077
|
+
]).start(() => onClose());
|
|
1078
|
+
};
|
|
1079
|
+
if (!visible) return null;
|
|
1080
|
+
return /* @__PURE__ */ jsxs2(Animated3.View, { style: [styles3.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
|
|
1081
|
+
/* @__PURE__ */ jsx3(Pressable3, { style: StyleSheet4.absoluteFill, onPress: close }),
|
|
1082
|
+
/* @__PURE__ */ jsxs2(
|
|
1083
|
+
Animated3.View,
|
|
1084
|
+
{
|
|
1085
|
+
style: [
|
|
1086
|
+
styles3.sheet,
|
|
1087
|
+
{ height: sheetH, transform: [{ translateY: slideAnim }] }
|
|
1088
|
+
],
|
|
1089
|
+
children: [
|
|
1090
|
+
/* @__PURE__ */ jsx3(View4, { style: styles3.handle }),
|
|
1091
|
+
/* @__PURE__ */ jsxs2(View4, { style: styles3.header, children: [
|
|
1092
|
+
showOrb ? /* @__PURE__ */ jsx3(View4, { style: styles3.headerOrb, children: /* @__PURE__ */ jsx3(AdaOrb, { size: 36, state: "idle" }) }) : null,
|
|
1093
|
+
/* @__PURE__ */ jsxs2(View4, { style: { flex: 1 }, children: [
|
|
1094
|
+
/* @__PURE__ */ jsx3(Text3, { style: styles3.title, children: title }),
|
|
1095
|
+
subtitle ? /* @__PURE__ */ jsx3(Text3, { style: styles3.subtitle, children: subtitle }) : null,
|
|
1096
|
+
privacy ? /* @__PURE__ */ jsxs2(View4, { style: styles3.privacyPill, children: [
|
|
1097
|
+
/* @__PURE__ */ jsxs2(Svg2, { width: 11, height: 11, viewBox: "0 0 24 24", fill: "none", stroke: ada2.inkSoft, strokeWidth: 2.5, strokeLinecap: "round", strokeLinejoin: "round", children: [
|
|
1098
|
+
/* @__PURE__ */ jsx3(Path, { d: "M19 11H5a2 2 0 00-2 2v7a2 2 0 002 2h14a2 2 0 002-2v-7a2 2 0 00-2-2z" }),
|
|
1099
|
+
/* @__PURE__ */ jsx3(Path, { d: "M7 11V7a5 5 0 0110 0v4" })
|
|
1100
|
+
] }),
|
|
1101
|
+
/* @__PURE__ */ jsx3(Text3, { style: styles3.privacyText, children: "Only you can see this" })
|
|
1102
|
+
] }) : null
|
|
1103
|
+
] }),
|
|
1104
|
+
/* @__PURE__ */ jsx3(
|
|
1105
|
+
Pressable3,
|
|
1106
|
+
{
|
|
1107
|
+
accessibilityRole: "button",
|
|
1108
|
+
accessibilityLabel: "Close",
|
|
1109
|
+
style: styles3.closeBtn,
|
|
1110
|
+
onPress: close,
|
|
1111
|
+
hitSlop: 12,
|
|
1112
|
+
children: /* @__PURE__ */ jsx3(Svg2, { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: ada2.inkSoft, strokeWidth: 2.2, strokeLinecap: "round", children: /* @__PURE__ */ jsx3(Path, { d: "M18 6L6 18M6 6l12 12" }) })
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
] }),
|
|
1116
|
+
/* @__PURE__ */ jsx3(View4, { style: styles3.body, children })
|
|
1117
|
+
]
|
|
1118
|
+
}
|
|
1119
|
+
)
|
|
1120
|
+
] });
|
|
1121
|
+
}
|
|
1122
|
+
var styles3 = StyleSheet4.create({
|
|
1123
|
+
overlay: {
|
|
1124
|
+
position: "absolute",
|
|
1125
|
+
top: 0,
|
|
1126
|
+
left: 0,
|
|
1127
|
+
right: 0,
|
|
1128
|
+
bottom: 0,
|
|
1129
|
+
backgroundColor: "rgba(26, 20, 16, 0.35)",
|
|
1130
|
+
justifyContent: "flex-end",
|
|
1131
|
+
zIndex: 999,
|
|
1132
|
+
elevation: 999
|
|
1133
|
+
},
|
|
1134
|
+
sheet: {
|
|
1135
|
+
backgroundColor: ada2.paper,
|
|
1136
|
+
borderTopLeftRadius: 28,
|
|
1137
|
+
borderTopRightRadius: 28,
|
|
1138
|
+
overflow: "hidden"
|
|
1139
|
+
},
|
|
1140
|
+
handle: {
|
|
1141
|
+
width: 40,
|
|
1142
|
+
height: 4,
|
|
1143
|
+
borderRadius: 2,
|
|
1144
|
+
backgroundColor: ada2.inkMuted,
|
|
1145
|
+
opacity: 0.3,
|
|
1146
|
+
alignSelf: "center",
|
|
1147
|
+
marginTop: 10,
|
|
1148
|
+
marginBottom: 6
|
|
1149
|
+
},
|
|
1150
|
+
header: {
|
|
1151
|
+
flexDirection: "row",
|
|
1152
|
+
alignItems: "center",
|
|
1153
|
+
paddingHorizontal: 22,
|
|
1154
|
+
paddingVertical: 16,
|
|
1155
|
+
gap: 12,
|
|
1156
|
+
borderBottomWidth: 1,
|
|
1157
|
+
borderBottomColor: ada2.border
|
|
1158
|
+
},
|
|
1159
|
+
headerOrb: {
|
|
1160
|
+
width: 36,
|
|
1161
|
+
height: 36,
|
|
1162
|
+
alignItems: "center",
|
|
1163
|
+
justifyContent: "center"
|
|
1164
|
+
},
|
|
1165
|
+
title: {
|
|
1166
|
+
fontSize: 17,
|
|
1167
|
+
fontWeight: "600",
|
|
1168
|
+
color: ada2.ink,
|
|
1169
|
+
letterSpacing: -0.2,
|
|
1170
|
+
fontFamily: fontFam
|
|
1171
|
+
},
|
|
1172
|
+
subtitle: {
|
|
1173
|
+
fontSize: 13,
|
|
1174
|
+
fontWeight: "400",
|
|
1175
|
+
color: ada2.inkSoft,
|
|
1176
|
+
marginTop: 2,
|
|
1177
|
+
letterSpacing: -0.1,
|
|
1178
|
+
fontFamily: fontFam
|
|
1179
|
+
},
|
|
1180
|
+
privacyPill: {
|
|
1181
|
+
flexDirection: "row",
|
|
1182
|
+
alignItems: "center",
|
|
1183
|
+
gap: 5,
|
|
1184
|
+
marginTop: 4
|
|
1185
|
+
},
|
|
1186
|
+
privacyText: {
|
|
1187
|
+
fontSize: 11,
|
|
1188
|
+
fontWeight: "500",
|
|
1189
|
+
color: ada2.inkSoft,
|
|
1190
|
+
letterSpacing: 0.2,
|
|
1191
|
+
fontFamily: fontFam
|
|
1192
|
+
},
|
|
1193
|
+
closeBtn: {
|
|
1194
|
+
width: 32,
|
|
1195
|
+
height: 32,
|
|
1196
|
+
borderRadius: 16,
|
|
1197
|
+
backgroundColor: ada2.paperSoft,
|
|
1198
|
+
justifyContent: "center",
|
|
1199
|
+
alignItems: "center"
|
|
1200
|
+
},
|
|
1201
|
+
body: {
|
|
1202
|
+
flex: 1
|
|
1203
|
+
}
|
|
1204
|
+
});
|
|
1205
|
+
|
|
1206
|
+
// src/surfaces/SurfaceCard.tsx
|
|
1207
|
+
import {
|
|
1208
|
+
View as View5,
|
|
1209
|
+
Text as Text4,
|
|
1210
|
+
Pressable as Pressable4,
|
|
1211
|
+
StyleSheet as StyleSheet5,
|
|
1212
|
+
Platform as Platform3
|
|
1213
|
+
} from "react-native";
|
|
1214
|
+
import { ada as ada3, borderRadius as borderRadius2 } from "@ateam-ai-mobile/core";
|
|
1215
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1216
|
+
var fontFam2 = Platform3.OS === "ios" ? "SF Pro Rounded" : void 0;
|
|
1217
|
+
function SurfaceCard({
|
|
1218
|
+
icon,
|
|
1219
|
+
title,
|
|
1220
|
+
subtitle,
|
|
1221
|
+
onPress,
|
|
1222
|
+
onLongPress,
|
|
1223
|
+
showArrow,
|
|
1224
|
+
children
|
|
1225
|
+
}) {
|
|
1226
|
+
const arrow = showArrow ?? !!onPress;
|
|
1227
|
+
const handlePress = () => {
|
|
1228
|
+
if (!onPress) return;
|
|
1229
|
+
getHaptics()?.impact("light");
|
|
1230
|
+
onPress();
|
|
1231
|
+
};
|
|
1232
|
+
const handleLongPress = () => {
|
|
1233
|
+
if (!onLongPress) return;
|
|
1234
|
+
getHaptics()?.impact("medium");
|
|
1235
|
+
onLongPress();
|
|
1236
|
+
};
|
|
1237
|
+
return /* @__PURE__ */ jsxs3(
|
|
1238
|
+
Pressable4,
|
|
1239
|
+
{
|
|
1240
|
+
accessibilityRole: "button",
|
|
1241
|
+
accessibilityLabel: [icon, title, subtitle].filter(Boolean).join(", "),
|
|
1242
|
+
accessibilityHint: onLongPress ? "Long-press for more options" : void 0,
|
|
1243
|
+
style: styles4.card,
|
|
1244
|
+
onPress: onPress ? handlePress : void 0,
|
|
1245
|
+
onLongPress: onLongPress ? handleLongPress : void 0,
|
|
1246
|
+
disabled: !onPress && !onLongPress,
|
|
1247
|
+
children: [
|
|
1248
|
+
/* @__PURE__ */ jsxs3(View5, { style: styles4.row, children: [
|
|
1249
|
+
icon ? /* @__PURE__ */ jsx4(Text4, { style: styles4.icon, children: icon }) : null,
|
|
1250
|
+
/* @__PURE__ */ jsxs3(View5, { style: { flex: 1 }, children: [
|
|
1251
|
+
/* @__PURE__ */ jsx4(Text4, { style: styles4.title, children: title }),
|
|
1252
|
+
subtitle ? /* @__PURE__ */ jsx4(Text4, { style: styles4.subtitle, children: subtitle }) : null
|
|
1253
|
+
] }),
|
|
1254
|
+
arrow ? /* @__PURE__ */ jsx4(Text4, { style: styles4.arrow, children: "\u2192" }) : null
|
|
1255
|
+
] }),
|
|
1256
|
+
children ? /* @__PURE__ */ jsx4(View5, { style: styles4.body, children }) : null
|
|
1257
|
+
]
|
|
1258
|
+
}
|
|
1259
|
+
);
|
|
1260
|
+
}
|
|
1261
|
+
var styles4 = StyleSheet5.create({
|
|
1262
|
+
card: {
|
|
1263
|
+
backgroundColor: ada3.paperSoft,
|
|
1264
|
+
borderWidth: 1,
|
|
1265
|
+
borderColor: ada3.border,
|
|
1266
|
+
borderRadius: borderRadius2.xl,
|
|
1267
|
+
paddingHorizontal: 16,
|
|
1268
|
+
paddingVertical: 14
|
|
1269
|
+
},
|
|
1270
|
+
row: {
|
|
1271
|
+
flexDirection: "row",
|
|
1272
|
+
alignItems: "center",
|
|
1273
|
+
gap: 14
|
|
1274
|
+
},
|
|
1275
|
+
icon: {
|
|
1276
|
+
fontSize: 22
|
|
1277
|
+
},
|
|
1278
|
+
title: {
|
|
1279
|
+
fontSize: 15,
|
|
1280
|
+
fontWeight: "600",
|
|
1281
|
+
color: ada3.ink,
|
|
1282
|
+
letterSpacing: -0.2,
|
|
1283
|
+
fontFamily: fontFam2
|
|
1284
|
+
},
|
|
1285
|
+
subtitle: {
|
|
1286
|
+
fontSize: 13,
|
|
1287
|
+
fontWeight: "400",
|
|
1288
|
+
color: ada3.inkMuted,
|
|
1289
|
+
marginTop: 2,
|
|
1290
|
+
letterSpacing: -0.1,
|
|
1291
|
+
fontFamily: fontFam2
|
|
1292
|
+
},
|
|
1293
|
+
arrow: {
|
|
1294
|
+
fontSize: 18,
|
|
1295
|
+
color: ada3.inkMuted,
|
|
1296
|
+
fontWeight: "400"
|
|
1297
|
+
},
|
|
1298
|
+
body: {
|
|
1299
|
+
marginTop: 10,
|
|
1300
|
+
paddingTop: 10,
|
|
1301
|
+
borderTopWidth: 1,
|
|
1302
|
+
borderTopColor: ada3.border
|
|
1303
|
+
}
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// src/surfaces/SurfaceFullscreen.tsx
|
|
1307
|
+
import { useEffect as useEffect3, useRef as useRef2 } from "react";
|
|
1308
|
+
import {
|
|
1309
|
+
View as View6,
|
|
1310
|
+
Text as Text5,
|
|
1311
|
+
Pressable as Pressable5,
|
|
1312
|
+
StyleSheet as StyleSheet6,
|
|
1313
|
+
Animated as Animated4,
|
|
1314
|
+
Platform as Platform4
|
|
1315
|
+
} from "react-native";
|
|
1316
|
+
import Svg3, { Path as Path2 } from "react-native-svg";
|
|
1317
|
+
import { ada as ada4 } from "@ateam-ai-mobile/core";
|
|
1318
|
+
import { jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
1319
|
+
var fontFam3 = Platform4.OS === "ios" ? "SF Pro Rounded" : void 0;
|
|
1320
|
+
function SurfaceFullscreen({
|
|
1321
|
+
visible,
|
|
1322
|
+
onClose,
|
|
1323
|
+
status,
|
|
1324
|
+
hero,
|
|
1325
|
+
children,
|
|
1326
|
+
showWordmark = true,
|
|
1327
|
+
closeIcon = "x",
|
|
1328
|
+
closeLabel = "Close"
|
|
1329
|
+
}) {
|
|
1330
|
+
const fadeAnim = useRef2(new Animated4.Value(0)).current;
|
|
1331
|
+
const heroScale = useRef2(new Animated4.Value(0.6)).current;
|
|
1332
|
+
useEffect3(() => {
|
|
1333
|
+
if (visible) {
|
|
1334
|
+
Animated4.parallel([
|
|
1335
|
+
Animated4.timing(fadeAnim, { toValue: 1, duration: 280, useNativeDriver: true }),
|
|
1336
|
+
Animated4.spring(heroScale, { toValue: 1, useNativeDriver: true, tension: 60, friction: 11 })
|
|
1337
|
+
]).start();
|
|
1338
|
+
} else {
|
|
1339
|
+
fadeAnim.setValue(0);
|
|
1340
|
+
heroScale.setValue(0.6);
|
|
1341
|
+
}
|
|
1342
|
+
}, [visible]);
|
|
1343
|
+
const close = () => {
|
|
1344
|
+
Animated4.parallel([
|
|
1345
|
+
Animated4.timing(fadeAnim, { toValue: 0, duration: 180, useNativeDriver: true }),
|
|
1346
|
+
Animated4.timing(heroScale, { toValue: 0.6, duration: 180, useNativeDriver: true })
|
|
1347
|
+
]).start(() => onClose());
|
|
1348
|
+
};
|
|
1349
|
+
if (!visible) return null;
|
|
1350
|
+
const renderCloseIcon = () => {
|
|
1351
|
+
if (closeIcon === "hangup") {
|
|
1352
|
+
return /* @__PURE__ */ jsx5(Svg3, { width: 22, height: 22, viewBox: "0 0 24 24", fill: ada4.paper, children: /* @__PURE__ */ jsx5(Path2, { d: "M3.68 16.07l3.92-3.11c.28-.23.68-.28 1.01-.14l2.77 1.15c.36.15.61.5.61.89v2.82c4.34-.76 7.93-4.35 8.69-8.69h-2.82c-.39 0-.74-.25-.89-.61L16.12 5.6c-.13-.33-.09-.73.14-1.01l3.11-3.92c.29-.37.81-.41 1.15-.08l3.24 3.24c.2.2.31.47.31.76C24 14.44 14.44 24 4.59 24c-.29 0-.56-.11-.76-.31L.59 20.45c-.33-.34-.29-.86.09-1.15z" }) });
|
|
1353
|
+
}
|
|
1354
|
+
if (closeIcon === "x") {
|
|
1355
|
+
return /* @__PURE__ */ jsx5(Svg3, { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: ada4.paper, strokeWidth: 2.5, strokeLinecap: "round", children: /* @__PURE__ */ jsx5(Path2, { d: "M18 6L6 18M6 6l12 12" }) });
|
|
1356
|
+
}
|
|
1357
|
+
return closeIcon;
|
|
1358
|
+
};
|
|
1359
|
+
return /* @__PURE__ */ jsxs4(Animated4.View, { style: [styles5.overlay, { opacity: fadeAnim }], pointerEvents: "auto", children: [
|
|
1360
|
+
/* @__PURE__ */ jsxs4(View6, { style: styles5.topBar, children: [
|
|
1361
|
+
showWordmark ? /* @__PURE__ */ jsx5(Text5, { style: styles5.wordmark, children: "ada" }) : null,
|
|
1362
|
+
status ? /* @__PURE__ */ jsx5(Text5, { style: styles5.status, children: status }) : null
|
|
1363
|
+
] }),
|
|
1364
|
+
hero ? /* @__PURE__ */ jsx5(View6, { style: styles5.heroWrap, children: /* @__PURE__ */ jsx5(Animated4.View, { style: { transform: [{ scale: heroScale }] }, children: hero }) }) : null,
|
|
1365
|
+
/* @__PURE__ */ jsx5(View6, { style: styles5.body, children }),
|
|
1366
|
+
/* @__PURE__ */ jsxs4(View6, { style: styles5.bottomBar, children: [
|
|
1367
|
+
/* @__PURE__ */ jsx5(
|
|
1368
|
+
Pressable5,
|
|
1369
|
+
{
|
|
1370
|
+
accessibilityRole: "button",
|
|
1371
|
+
accessibilityLabel: closeLabel,
|
|
1372
|
+
style: styles5.closeBtn,
|
|
1373
|
+
onPress: close,
|
|
1374
|
+
hitSlop: 12,
|
|
1375
|
+
children: renderCloseIcon()
|
|
1376
|
+
}
|
|
1377
|
+
),
|
|
1378
|
+
closeLabel ? /* @__PURE__ */ jsx5(Text5, { style: styles5.closeLabel, children: closeLabel }) : null
|
|
1379
|
+
] })
|
|
1380
|
+
] });
|
|
1381
|
+
}
|
|
1382
|
+
var styles5 = StyleSheet6.create({
|
|
1383
|
+
overlay: {
|
|
1384
|
+
position: "absolute",
|
|
1385
|
+
top: 0,
|
|
1386
|
+
left: 0,
|
|
1387
|
+
right: 0,
|
|
1388
|
+
bottom: 0,
|
|
1389
|
+
backgroundColor: ada4.paper,
|
|
1390
|
+
zIndex: 1e3,
|
|
1391
|
+
elevation: 1e3
|
|
1392
|
+
},
|
|
1393
|
+
topBar: {
|
|
1394
|
+
paddingTop: Platform4.OS === "ios" ? 64 : 36,
|
|
1395
|
+
paddingHorizontal: 24,
|
|
1396
|
+
alignItems: "center",
|
|
1397
|
+
gap: 6
|
|
1398
|
+
},
|
|
1399
|
+
wordmark: {
|
|
1400
|
+
fontSize: 14,
|
|
1401
|
+
fontFamily: fontFam3,
|
|
1402
|
+
fontWeight: "500",
|
|
1403
|
+
color: ada4.inkMuted,
|
|
1404
|
+
letterSpacing: 4
|
|
1405
|
+
},
|
|
1406
|
+
status: {
|
|
1407
|
+
fontSize: 17,
|
|
1408
|
+
fontFamily: fontFam3,
|
|
1409
|
+
fontWeight: "500",
|
|
1410
|
+
color: ada4.ink,
|
|
1411
|
+
letterSpacing: -0.2
|
|
1412
|
+
},
|
|
1413
|
+
heroWrap: {
|
|
1414
|
+
paddingTop: 24,
|
|
1415
|
+
paddingBottom: 12,
|
|
1416
|
+
alignItems: "center",
|
|
1417
|
+
justifyContent: "center"
|
|
1418
|
+
},
|
|
1419
|
+
body: {
|
|
1420
|
+
flex: 1
|
|
1421
|
+
},
|
|
1422
|
+
bottomBar: {
|
|
1423
|
+
paddingBottom: Platform4.OS === "ios" ? 48 : 32,
|
|
1424
|
+
paddingTop: 12,
|
|
1425
|
+
alignItems: "center",
|
|
1426
|
+
gap: 8
|
|
1427
|
+
},
|
|
1428
|
+
closeBtn: {
|
|
1429
|
+
width: 64,
|
|
1430
|
+
height: 64,
|
|
1431
|
+
borderRadius: 32,
|
|
1432
|
+
backgroundColor: ada4.ink,
|
|
1433
|
+
justifyContent: "center",
|
|
1434
|
+
alignItems: "center",
|
|
1435
|
+
shadowColor: ada4.ink,
|
|
1436
|
+
shadowOffset: { width: 0, height: 8 },
|
|
1437
|
+
shadowOpacity: 0.18,
|
|
1438
|
+
shadowRadius: 14,
|
|
1439
|
+
elevation: 8
|
|
1440
|
+
},
|
|
1441
|
+
closeLabel: {
|
|
1442
|
+
fontSize: 13,
|
|
1443
|
+
fontFamily: fontFam3,
|
|
1444
|
+
fontWeight: "500",
|
|
1445
|
+
color: ada4.inkMuted,
|
|
1446
|
+
letterSpacing: 0.3
|
|
1447
|
+
}
|
|
1448
|
+
});
|
|
1449
|
+
|
|
1450
|
+
// src/registry/store.ts
|
|
1451
|
+
import { create } from "zustand";
|
|
1452
|
+
var useSurfaceStore = create((set, get) => ({
|
|
1453
|
+
manifests: {},
|
|
1454
|
+
activeDrawer: null,
|
|
1455
|
+
activeFullscreen: null,
|
|
1456
|
+
activeProps: {},
|
|
1457
|
+
mountKey: 0,
|
|
1458
|
+
register: (manifest) => set((s) => ({ manifests: { ...s.manifests, [manifest.id]: manifest } })),
|
|
1459
|
+
unregister: (id) => set((s) => {
|
|
1460
|
+
const next = { ...s.manifests };
|
|
1461
|
+
delete next[id];
|
|
1462
|
+
const nextProps = { ...s.activeProps };
|
|
1463
|
+
delete nextProps[id];
|
|
1464
|
+
return { manifests: next, activeProps: nextProps };
|
|
1465
|
+
}),
|
|
1466
|
+
show: (id, props) => {
|
|
1467
|
+
const m = get().manifests[id];
|
|
1468
|
+
if (!m) {
|
|
1469
|
+
console.warn("[SurfaceStore] show: unknown surface id", id);
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
set((s) => {
|
|
1473
|
+
const nextProps = props !== void 0 ? { ...s.activeProps, [id]: props } : s.activeProps;
|
|
1474
|
+
const mountKey = s.mountKey + 1;
|
|
1475
|
+
if (m.type === "drawer") return { activeDrawer: id, activeProps: nextProps, mountKey };
|
|
1476
|
+
if (m.type === "fullscreen") return { activeFullscreen: id, activeProps: nextProps, mountKey };
|
|
1477
|
+
return { mountKey };
|
|
1478
|
+
});
|
|
1479
|
+
},
|
|
1480
|
+
hide: (id) => {
|
|
1481
|
+
const s = get();
|
|
1482
|
+
if (!id) {
|
|
1483
|
+
const cleanup2 = { ...s.activeProps };
|
|
1484
|
+
if (s.activeDrawer) delete cleanup2[s.activeDrawer];
|
|
1485
|
+
if (s.activeFullscreen) delete cleanup2[s.activeFullscreen];
|
|
1486
|
+
set({ activeDrawer: null, activeFullscreen: null, activeProps: cleanup2 });
|
|
1487
|
+
return;
|
|
1488
|
+
}
|
|
1489
|
+
const cleanup = { ...s.activeProps };
|
|
1490
|
+
delete cleanup[id];
|
|
1491
|
+
const patch = { activeProps: cleanup };
|
|
1492
|
+
if (s.activeDrawer === id) patch.activeDrawer = null;
|
|
1493
|
+
if (s.activeFullscreen === id) patch.activeFullscreen = null;
|
|
1494
|
+
set(patch);
|
|
1495
|
+
},
|
|
1496
|
+
list: (filter) => {
|
|
1497
|
+
const all = Object.values(get().manifests);
|
|
1498
|
+
return all.filter((m) => {
|
|
1499
|
+
if (filter?.type && m.type !== filter.type) return false;
|
|
1500
|
+
if (filter?.visibility && !filter.visibility.includes(m.visibility)) return false;
|
|
1501
|
+
if (filter?.placement) {
|
|
1502
|
+
if (filter.placement === "unset") {
|
|
1503
|
+
if (m.placement) return false;
|
|
1504
|
+
} else if (m.placement !== filter.placement) {
|
|
1505
|
+
return false;
|
|
1506
|
+
}
|
|
1507
|
+
}
|
|
1508
|
+
return true;
|
|
1509
|
+
});
|
|
1510
|
+
}
|
|
1511
|
+
}));
|
|
1512
|
+
function isMountSurface(m) {
|
|
1513
|
+
return m.type === "drawer" || m.type === "fullscreen";
|
|
1514
|
+
}
|
|
1515
|
+
function isInlineSurface(m) {
|
|
1516
|
+
return m.type === "card" || m.type === "header" || m.type === "ambient" || m.type === "nudge";
|
|
1517
|
+
}
|
|
1518
|
+
|
|
1519
|
+
// src/registry/SurfaceDispatcher.tsx
|
|
1520
|
+
import { useEffect as useEffect4 } from "react";
|
|
1521
|
+
import { pluginCommandEmitter } from "@ateam-ai-mobile/core";
|
|
1522
|
+
import { Fragment, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
1523
|
+
function SurfaceDispatcher() {
|
|
1524
|
+
const manifests = useSurfaceStore((s) => s.manifests);
|
|
1525
|
+
const activeDrawer = useSurfaceStore((s) => s.activeDrawer);
|
|
1526
|
+
const activeFullscreen = useSurfaceStore((s) => s.activeFullscreen);
|
|
1527
|
+
const activeProps = useSurfaceStore((s) => s.activeProps);
|
|
1528
|
+
const mountKey = useSurfaceStore((s) => s.mountKey);
|
|
1529
|
+
const hide = useSurfaceStore((s) => s.hide);
|
|
1530
|
+
useEffect4(() => {
|
|
1531
|
+
const unsub = pluginCommandEmitter.on("plugin-close", ({ pluginId } = {}) => {
|
|
1532
|
+
if (!pluginId) return;
|
|
1533
|
+
const s = useSurfaceStore.getState();
|
|
1534
|
+
if (s.activeDrawer === pluginId || s.activeFullscreen === pluginId) {
|
|
1535
|
+
s.hide(pluginId);
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
return unsub;
|
|
1539
|
+
}, []);
|
|
1540
|
+
const drawer = activeDrawer ? manifests[activeDrawer] : null;
|
|
1541
|
+
const fullscreen = activeFullscreen ? manifests[activeFullscreen] : null;
|
|
1542
|
+
const drawerProps = activeDrawer ? activeProps[activeDrawer] : void 0;
|
|
1543
|
+
const fullscreenProps = activeFullscreen ? activeProps[activeFullscreen] : void 0;
|
|
1544
|
+
return /* @__PURE__ */ jsxs5(Fragment, { children: [
|
|
1545
|
+
drawer && drawer.type === "drawer" && drawer.Component ? /* @__PURE__ */ jsx6(
|
|
1546
|
+
drawer.Component,
|
|
1547
|
+
{
|
|
1548
|
+
dismiss: () => hide(drawer.id),
|
|
1549
|
+
props: drawerProps
|
|
1550
|
+
},
|
|
1551
|
+
`drawer-${drawer.id}-${mountKey}`
|
|
1552
|
+
) : null,
|
|
1553
|
+
fullscreen && fullscreen.type === "fullscreen" && fullscreen.Component ? /* @__PURE__ */ jsx6(
|
|
1554
|
+
fullscreen.Component,
|
|
1555
|
+
{
|
|
1556
|
+
dismiss: () => hide(fullscreen.id),
|
|
1557
|
+
props: fullscreenProps
|
|
1558
|
+
},
|
|
1559
|
+
`fullscreen-${fullscreen.id}-${mountKey}`
|
|
1560
|
+
) : null
|
|
1561
|
+
] });
|
|
1562
|
+
}
|
|
1563
|
+
|
|
1564
|
+
// src/utils/sha256.ts
|
|
1565
|
+
var K = new Uint32Array([
|
|
1566
|
+
1116352408,
|
|
1567
|
+
1899447441,
|
|
1568
|
+
3049323471,
|
|
1569
|
+
3921009573,
|
|
1570
|
+
961987163,
|
|
1571
|
+
1508970993,
|
|
1572
|
+
2453635748,
|
|
1573
|
+
2870763221,
|
|
1574
|
+
3624381080,
|
|
1575
|
+
310598401,
|
|
1576
|
+
607225278,
|
|
1577
|
+
1426881987,
|
|
1578
|
+
1925078388,
|
|
1579
|
+
2162078206,
|
|
1580
|
+
2614888103,
|
|
1581
|
+
3248222580,
|
|
1582
|
+
3835390401,
|
|
1583
|
+
4022224774,
|
|
1584
|
+
264347078,
|
|
1585
|
+
604807628,
|
|
1586
|
+
770255983,
|
|
1587
|
+
1249150122,
|
|
1588
|
+
1555081692,
|
|
1589
|
+
1996064986,
|
|
1590
|
+
2554220882,
|
|
1591
|
+
2821834349,
|
|
1592
|
+
2952996808,
|
|
1593
|
+
3210313671,
|
|
1594
|
+
3336571891,
|
|
1595
|
+
3584528711,
|
|
1596
|
+
113926993,
|
|
1597
|
+
338241895,
|
|
1598
|
+
666307205,
|
|
1599
|
+
773529912,
|
|
1600
|
+
1294757372,
|
|
1601
|
+
1396182291,
|
|
1602
|
+
1695183700,
|
|
1603
|
+
1986661051,
|
|
1604
|
+
2177026350,
|
|
1605
|
+
2456956037,
|
|
1606
|
+
2730485921,
|
|
1607
|
+
2820302411,
|
|
1608
|
+
3259730800,
|
|
1609
|
+
3345764771,
|
|
1610
|
+
3516065817,
|
|
1611
|
+
3600352804,
|
|
1612
|
+
4094571909,
|
|
1613
|
+
275423344,
|
|
1614
|
+
430227734,
|
|
1615
|
+
506948616,
|
|
1616
|
+
659060556,
|
|
1617
|
+
883997877,
|
|
1618
|
+
958139571,
|
|
1619
|
+
1322822218,
|
|
1620
|
+
1537002063,
|
|
1621
|
+
1747873779,
|
|
1622
|
+
1955562222,
|
|
1623
|
+
2024104815,
|
|
1624
|
+
2227730452,
|
|
1625
|
+
2361852424,
|
|
1626
|
+
2428436474,
|
|
1627
|
+
2756734187,
|
|
1628
|
+
3204031479,
|
|
1629
|
+
3329325298
|
|
1630
|
+
]);
|
|
1631
|
+
var rotr = (n, x) => x >>> n | x << 32 - n;
|
|
1632
|
+
function sha256Hex(message) {
|
|
1633
|
+
const utf8 = unescape(encodeURIComponent(message));
|
|
1634
|
+
const bytes = new Uint8Array(utf8.length);
|
|
1635
|
+
for (let i = 0; i < utf8.length; i++) bytes[i] = utf8.charCodeAt(i);
|
|
1636
|
+
const bitLen = bytes.length * 8;
|
|
1637
|
+
const padLen = (56 - (bytes.length + 1) % 64 + 64) % 64;
|
|
1638
|
+
const buf = new Uint8Array(bytes.length + 1 + padLen + 8);
|
|
1639
|
+
buf.set(bytes);
|
|
1640
|
+
buf[bytes.length] = 128;
|
|
1641
|
+
const len = buf.length;
|
|
1642
|
+
buf[len - 4] = bitLen >>> 24 & 255;
|
|
1643
|
+
buf[len - 3] = bitLen >>> 16 & 255;
|
|
1644
|
+
buf[len - 2] = bitLen >>> 8 & 255;
|
|
1645
|
+
buf[len - 1] = bitLen & 255;
|
|
1646
|
+
const h = new Uint32Array([
|
|
1647
|
+
1779033703,
|
|
1648
|
+
3144134277,
|
|
1649
|
+
1013904242,
|
|
1650
|
+
2773480762,
|
|
1651
|
+
1359893119,
|
|
1652
|
+
2600822924,
|
|
1653
|
+
528734635,
|
|
1654
|
+
1541459225
|
|
1655
|
+
]);
|
|
1656
|
+
const w = new Uint32Array(64);
|
|
1657
|
+
for (let i = 0; i < buf.length; i += 64) {
|
|
1658
|
+
for (let t = 0; t < 16; t++) {
|
|
1659
|
+
const j = i + t * 4;
|
|
1660
|
+
w[t] = buf[j] << 24 | buf[j + 1] << 16 | buf[j + 2] << 8 | buf[j + 3];
|
|
1661
|
+
}
|
|
1662
|
+
for (let t = 16; t < 64; t++) {
|
|
1663
|
+
const s0 = rotr(7, w[t - 15]) ^ rotr(18, w[t - 15]) ^ w[t - 15] >>> 3;
|
|
1664
|
+
const s1 = rotr(17, w[t - 2]) ^ rotr(19, w[t - 2]) ^ w[t - 2] >>> 10;
|
|
1665
|
+
w[t] = w[t - 16] + s0 + w[t - 7] + s1 | 0;
|
|
1666
|
+
}
|
|
1667
|
+
let a = h[0], b = h[1], c = h[2], d = h[3], e = h[4], f = h[5], g = h[6], hh = h[7];
|
|
1668
|
+
for (let t = 0; t < 64; t++) {
|
|
1669
|
+
const S1 = rotr(6, e) ^ rotr(11, e) ^ rotr(25, e);
|
|
1670
|
+
const ch = e & f ^ ~e & g;
|
|
1671
|
+
const temp1 = hh + S1 + ch + K[t] + w[t] | 0;
|
|
1672
|
+
const S0 = rotr(2, a) ^ rotr(13, a) ^ rotr(22, a);
|
|
1673
|
+
const maj = a & b ^ a & c ^ b & c;
|
|
1674
|
+
const temp2 = S0 + maj | 0;
|
|
1675
|
+
hh = g;
|
|
1676
|
+
g = f;
|
|
1677
|
+
f = e;
|
|
1678
|
+
e = d + temp1 | 0;
|
|
1679
|
+
d = c;
|
|
1680
|
+
c = b;
|
|
1681
|
+
b = a;
|
|
1682
|
+
a = temp1 + temp2 | 0;
|
|
1683
|
+
}
|
|
1684
|
+
h[0] = h[0] + a | 0;
|
|
1685
|
+
h[1] = h[1] + b | 0;
|
|
1686
|
+
h[2] = h[2] + c | 0;
|
|
1687
|
+
h[3] = h[3] + d | 0;
|
|
1688
|
+
h[4] = h[4] + e | 0;
|
|
1689
|
+
h[5] = h[5] + f | 0;
|
|
1690
|
+
h[6] = h[6] + g | 0;
|
|
1691
|
+
h[7] = h[7] + hh | 0;
|
|
1692
|
+
}
|
|
1693
|
+
let out = "";
|
|
1694
|
+
for (let i = 0; i < 8; i++) {
|
|
1695
|
+
out += h[i].toString(16).padStart(8, "0");
|
|
1696
|
+
}
|
|
1697
|
+
return out;
|
|
1698
|
+
}
|
|
1699
|
+
|
|
1700
|
+
// src/bundles/integrity.ts
|
|
1701
|
+
function assertBundleIntegrity(content, verifiedHash, label) {
|
|
1702
|
+
const digest = sha256Hex(content);
|
|
1703
|
+
if (!verifiedHash) {
|
|
1704
|
+
throw new Error(`[bundle-integrity] refusing to evaluate ${label}: no verified SHA-256 (fail closed).`);
|
|
1705
|
+
}
|
|
1706
|
+
if (digest !== verifiedHash.toLowerCase()) {
|
|
1707
|
+
throw new Error(
|
|
1708
|
+
`[bundle-integrity] integrity FAIL at eval for ${label}: content sha256 ${digest.slice(0, 12)}\u2026 \u2260 verified ${verifiedHash.slice(0, 12)}\u2026. Refusing to evaluate.`
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
|
|
1713
|
+
// src/bundles/storage.ts
|
|
1714
|
+
function selectOwnedCacheEntries(entries, tenant) {
|
|
1715
|
+
if (!tenant) return [];
|
|
1716
|
+
return (entries || []).filter((e) => !!e && e.tenant === tenant);
|
|
1717
|
+
}
|
|
1718
|
+
var MemoryBundleStorage = class {
|
|
1719
|
+
constructor() {
|
|
1720
|
+
this.index = {};
|
|
1721
|
+
this.files = /* @__PURE__ */ new Map();
|
|
1722
|
+
}
|
|
1723
|
+
async loadIndex() {
|
|
1724
|
+
return this.index;
|
|
1725
|
+
}
|
|
1726
|
+
async saveIndex(index) {
|
|
1727
|
+
this.index = index;
|
|
1728
|
+
}
|
|
1729
|
+
async readBundle(localPath) {
|
|
1730
|
+
return this.files.get(localPath) ?? null;
|
|
1731
|
+
}
|
|
1732
|
+
async writeBundle(key, content) {
|
|
1733
|
+
const localPath = `memory://${key}`;
|
|
1734
|
+
this.files.set(localPath, content);
|
|
1735
|
+
return localPath;
|
|
1736
|
+
}
|
|
1737
|
+
async deleteBundle(localPath) {
|
|
1738
|
+
this.files.delete(localPath);
|
|
1739
|
+
}
|
|
1740
|
+
async clearAll() {
|
|
1741
|
+
this.index = {};
|
|
1742
|
+
this.files.clear();
|
|
1743
|
+
}
|
|
1744
|
+
};
|
|
1745
|
+
var _storage = new MemoryBundleStorage();
|
|
1746
|
+
function setBundleStorage(storage) {
|
|
1747
|
+
_storage = storage;
|
|
1748
|
+
}
|
|
1749
|
+
function getBundleStorage() {
|
|
1750
|
+
return _storage;
|
|
1751
|
+
}
|
|
1752
|
+
|
|
1753
|
+
// src/bridge/native.ts
|
|
1754
|
+
import { useCallback, useMemo as useMemo3, useRef as useRef3, useEffect as useEffect5 } from "react";
|
|
1755
|
+
import {
|
|
1756
|
+
connectorCall,
|
|
1757
|
+
mcpFetch as mcpFetchApi,
|
|
1758
|
+
unwrapMcpResult,
|
|
1759
|
+
postCommandResult,
|
|
1760
|
+
getTenant,
|
|
1761
|
+
getUser,
|
|
1762
|
+
pluginCommandEmitter as pluginCommandEmitter2,
|
|
1763
|
+
useChatStore
|
|
1764
|
+
} from "@ateam-ai-mobile/core";
|
|
1765
|
+
function buildContext(pluginId, connectorId) {
|
|
1766
|
+
const user = getUser();
|
|
1767
|
+
return {
|
|
1768
|
+
tenant: getTenant(),
|
|
1769
|
+
pluginId,
|
|
1770
|
+
connectorId,
|
|
1771
|
+
user: user ? { id: user.actorId || "", name: user.name || "", roles: user.roles || [] } : void 0
|
|
1772
|
+
};
|
|
1773
|
+
}
|
|
1774
|
+
function useNativeBridge(pluginId, connectorId, commandBufferRef) {
|
|
1775
|
+
const initCallbackRef = useRef3(null);
|
|
1776
|
+
const commandListenersRef = useRef3(/* @__PURE__ */ new Set());
|
|
1777
|
+
const initializedRef = useRef3(false);
|
|
1778
|
+
const fireInit = useCallback(() => {
|
|
1779
|
+
if (initializedRef.current || !initCallbackRef.current) return;
|
|
1780
|
+
initCallbackRef.current(buildContext(pluginId, connectorId));
|
|
1781
|
+
initializedRef.current = true;
|
|
1782
|
+
}, [pluginId, connectorId]);
|
|
1783
|
+
const onInit = useCallback((cb) => {
|
|
1784
|
+
initCallbackRef.current = cb;
|
|
1785
|
+
setTimeout(fireInit, 0);
|
|
1786
|
+
}, [fireInit]);
|
|
1787
|
+
const mcpCall = useCallback(async (toolOrConnector, argsOrTool, cIdOrArgs) => {
|
|
1788
|
+
let tool;
|
|
1789
|
+
let args;
|
|
1790
|
+
let cId;
|
|
1791
|
+
if (typeof argsOrTool === "string") {
|
|
1792
|
+
cId = toolOrConnector;
|
|
1793
|
+
tool = argsOrTool;
|
|
1794
|
+
args = cIdOrArgs || {};
|
|
1795
|
+
} else {
|
|
1796
|
+
tool = toolOrConnector;
|
|
1797
|
+
args = argsOrTool;
|
|
1798
|
+
cId = typeof cIdOrArgs === "string" ? cIdOrArgs : void 0;
|
|
1799
|
+
}
|
|
1800
|
+
const targetConnector = cId || connectorId;
|
|
1801
|
+
if (!targetConnector) {
|
|
1802
|
+
console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2014 no connectorId. tool=${tool}`);
|
|
1803
|
+
throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
|
|
1804
|
+
}
|
|
1805
|
+
try {
|
|
1806
|
+
const data = await connectorCall(targetConnector, tool, args);
|
|
1807
|
+
if (!data.ok && data.error) {
|
|
1808
|
+
throw new Error(data.error);
|
|
1809
|
+
}
|
|
1810
|
+
return unwrapMcpResult(data.result);
|
|
1811
|
+
} catch (err) {
|
|
1812
|
+
console.warn(`[NativeBridge:${pluginId}] mcpCall failed \u2192 connector=${targetConnector} tool=${tool}:`, err?.message || err);
|
|
1813
|
+
throw err;
|
|
1814
|
+
}
|
|
1815
|
+
}, [pluginId, connectorId]);
|
|
1816
|
+
const mcpFetch = useCallback(async (body) => {
|
|
1817
|
+
return mcpFetchApi(body);
|
|
1818
|
+
}, []);
|
|
1819
|
+
const sendCommand = useCallback((type, payload) => {
|
|
1820
|
+
if (type === "plugin.command.result" && payload?.correlationId) {
|
|
1821
|
+
postCommandResult(payload.correlationId, payload.result, payload.error);
|
|
1822
|
+
return;
|
|
1823
|
+
}
|
|
1824
|
+
}, [pluginId]);
|
|
1825
|
+
const onCommand = useCallback((cb) => {
|
|
1826
|
+
commandListenersRef.current.add(cb);
|
|
1827
|
+
if (commandBufferRef) {
|
|
1828
|
+
const buf = commandBufferRef.current;
|
|
1829
|
+
const remaining = [];
|
|
1830
|
+
for (const cmd of buf) {
|
|
1831
|
+
if (cmd.pluginId === pluginId && cmd.command !== "__focus") {
|
|
1832
|
+
try {
|
|
1833
|
+
cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
|
|
1834
|
+
} catch {
|
|
1835
|
+
}
|
|
1836
|
+
} else {
|
|
1837
|
+
remaining.push(cmd);
|
|
1838
|
+
}
|
|
1839
|
+
}
|
|
1840
|
+
commandBufferRef.current = remaining;
|
|
1841
|
+
}
|
|
1842
|
+
for (const cmd of pluginCommandEmitter2.drainPluginCommands(pluginId)) {
|
|
1843
|
+
try {
|
|
1844
|
+
cb(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
|
|
1845
|
+
} catch {
|
|
1846
|
+
}
|
|
1847
|
+
}
|
|
1848
|
+
return () => {
|
|
1849
|
+
commandListenersRef.current.delete(cb);
|
|
1850
|
+
};
|
|
1851
|
+
}, [pluginId, commandBufferRef]);
|
|
1852
|
+
useEffect5(() => {
|
|
1853
|
+
const unsub = pluginCommandEmitter2.on("plugin-command", (cmd) => {
|
|
1854
|
+
if (cmd.pluginId !== pluginId) return;
|
|
1855
|
+
if (cmd.command === "__focus") return;
|
|
1856
|
+
commandListenersRef.current.forEach((fn) => {
|
|
1857
|
+
try {
|
|
1858
|
+
fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
|
|
1859
|
+
} catch {
|
|
1860
|
+
}
|
|
1861
|
+
});
|
|
1862
|
+
});
|
|
1863
|
+
return unsub;
|
|
1864
|
+
}, [pluginId]);
|
|
1865
|
+
useEffect5(() => {
|
|
1866
|
+
const timer = setTimeout(() => {
|
|
1867
|
+
const dispatch = (cmd) => {
|
|
1868
|
+
commandListenersRef.current.forEach((fn) => {
|
|
1869
|
+
try {
|
|
1870
|
+
fn(cmd.command, { ...cmd.args, correlationId: cmd.correlationId });
|
|
1871
|
+
} catch {
|
|
1872
|
+
}
|
|
1873
|
+
});
|
|
1874
|
+
};
|
|
1875
|
+
if (commandBufferRef) {
|
|
1876
|
+
const remaining = [];
|
|
1877
|
+
for (const cmd of commandBufferRef.current) {
|
|
1878
|
+
if (cmd.pluginId === pluginId && cmd.command !== "__focus") dispatch(cmd);
|
|
1879
|
+
else remaining.push(cmd);
|
|
1880
|
+
}
|
|
1881
|
+
commandBufferRef.current = remaining;
|
|
1882
|
+
}
|
|
1883
|
+
for (const cmd of pluginCommandEmitter2.drainPluginCommands(pluginId)) {
|
|
1884
|
+
dispatch(cmd);
|
|
1885
|
+
}
|
|
1886
|
+
}, 500);
|
|
1887
|
+
return () => clearTimeout(timer);
|
|
1888
|
+
}, [pluginId, commandBufferRef]);
|
|
1889
|
+
useEffect5(() => {
|
|
1890
|
+
return () => {
|
|
1891
|
+
commandListenersRef.current.clear();
|
|
1892
|
+
initializedRef.current = false;
|
|
1893
|
+
};
|
|
1894
|
+
}, []);
|
|
1895
|
+
const close = useCallback(() => {
|
|
1896
|
+
pluginCommandEmitter2.emit("plugin-close", { pluginId });
|
|
1897
|
+
}, [pluginId]);
|
|
1898
|
+
const sendMessage = useCallback((text) => {
|
|
1899
|
+
const t = (text || "").trim();
|
|
1900
|
+
if (!t) return;
|
|
1901
|
+
try {
|
|
1902
|
+
useChatStore.getState().sendMessage(t);
|
|
1903
|
+
} catch (err) {
|
|
1904
|
+
console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
|
|
1905
|
+
}
|
|
1906
|
+
}, []);
|
|
1907
|
+
const prepareMessage = useCallback((text) => {
|
|
1908
|
+
const t = (text || "").trim();
|
|
1909
|
+
if (!t) return;
|
|
1910
|
+
pluginCommandEmitter2.emit("prepare-chat-message", { text: t });
|
|
1911
|
+
}, []);
|
|
1912
|
+
return useMemo3(
|
|
1913
|
+
() => ({ onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage }),
|
|
1914
|
+
[onInit, mcpCall, mcpFetch, sendCommand, onCommand, close, sendMessage, prepareMessage]
|
|
1915
|
+
);
|
|
1916
|
+
}
|
|
1917
|
+
function createNativeBridge(pluginId, connectorId) {
|
|
1918
|
+
const commandListeners = /* @__PURE__ */ new Set();
|
|
1919
|
+
return {
|
|
1920
|
+
onInit(cb) {
|
|
1921
|
+
setTimeout(() => {
|
|
1922
|
+
cb(buildContext(pluginId, connectorId));
|
|
1923
|
+
}, 0);
|
|
1924
|
+
},
|
|
1925
|
+
async mcpCall(tool, args, cId) {
|
|
1926
|
+
const targetConnector = cId || connectorId;
|
|
1927
|
+
if (!targetConnector) {
|
|
1928
|
+
throw new Error(`[NativeBridge:${pluginId}] mcpCall requires a connectorId`);
|
|
1929
|
+
}
|
|
1930
|
+
const data = await connectorCall(targetConnector, tool, args);
|
|
1931
|
+
if (!data.ok && data.error) throw new Error(data.error);
|
|
1932
|
+
return unwrapMcpResult(data.result);
|
|
1933
|
+
},
|
|
1934
|
+
async mcpFetch(body) {
|
|
1935
|
+
return mcpFetchApi(body);
|
|
1936
|
+
},
|
|
1937
|
+
sendCommand(_type, _payload) {
|
|
1938
|
+
},
|
|
1939
|
+
onCommand(cb) {
|
|
1940
|
+
commandListeners.add(cb);
|
|
1941
|
+
return () => {
|
|
1942
|
+
commandListeners.delete(cb);
|
|
1943
|
+
};
|
|
1944
|
+
},
|
|
1945
|
+
close() {
|
|
1946
|
+
pluginCommandEmitter2.emit("plugin-close", { pluginId });
|
|
1947
|
+
},
|
|
1948
|
+
sendMessage(text) {
|
|
1949
|
+
const t = (text || "").trim();
|
|
1950
|
+
if (!t) return;
|
|
1951
|
+
try {
|
|
1952
|
+
useChatStore.getState().sendMessage(t);
|
|
1953
|
+
} catch (err) {
|
|
1954
|
+
console.warn("[bridge.sendMessage] failed:", err instanceof Error ? err.message : String(err));
|
|
1955
|
+
}
|
|
1956
|
+
},
|
|
1957
|
+
prepareMessage(text) {
|
|
1958
|
+
const t = (text || "").trim();
|
|
1959
|
+
if (!t) return;
|
|
1960
|
+
pluginCommandEmitter2.emit("prepare-chat-message", { text: t });
|
|
1961
|
+
}
|
|
1962
|
+
};
|
|
1963
|
+
}
|
|
1964
|
+
|
|
1965
|
+
// src/deviceStateHost.ts
|
|
1966
|
+
var UNKNOWN_QUALITY = {
|
|
1967
|
+
freshness: { state: "unknown", observedAt: null, ageMs: null },
|
|
1968
|
+
confidence: { level: "unknown" }
|
|
1969
|
+
};
|
|
1970
|
+
function deniedSnapshot(domain) {
|
|
1971
|
+
const base = { quality: UNKNOWN_QUALITY };
|
|
1972
|
+
switch (domain) {
|
|
1973
|
+
case "spatial":
|
|
1974
|
+
return {
|
|
1975
|
+
...base,
|
|
1976
|
+
position: null,
|
|
1977
|
+
movement: { speedMps: null, bearingDeg: null, distanceRecentM: null, state: "unknown" },
|
|
1978
|
+
orientation: { headingDeg: null },
|
|
1979
|
+
recentPath: []
|
|
1980
|
+
};
|
|
1981
|
+
case "visual":
|
|
1982
|
+
return {
|
|
1983
|
+
...base,
|
|
1984
|
+
camera: { available: false, active: false, facing: "unknown" },
|
|
1985
|
+
latest: null,
|
|
1986
|
+
recentTimeline: { windowMs: 0, frames: [] }
|
|
1987
|
+
};
|
|
1988
|
+
case "deviceContext":
|
|
1989
|
+
return {
|
|
1990
|
+
...base,
|
|
1991
|
+
power: { batteryLevel: null, charging: null, lowPowerMode: null },
|
|
1992
|
+
thermal: { state: "unknown" },
|
|
1993
|
+
resources: { memoryPressure: "unknown", storagePressure: "unknown" }
|
|
1994
|
+
};
|
|
1995
|
+
case "connectivity":
|
|
1996
|
+
return {
|
|
1997
|
+
...base,
|
|
1998
|
+
reachability: "unknown",
|
|
1999
|
+
transport: "unknown",
|
|
2000
|
+
qualityClass: "unknown",
|
|
2001
|
+
stability: "unknown",
|
|
2002
|
+
latencyMs: null
|
|
2003
|
+
};
|
|
2004
|
+
case "interaction":
|
|
2005
|
+
return {
|
|
2006
|
+
...base,
|
|
2007
|
+
lifecycle: { appState: "unknown", screenOn: null },
|
|
2008
|
+
audio: { captureActive: null, playbackActive: null, route: "unknown", interruption: "unknown" }
|
|
2009
|
+
};
|
|
2010
|
+
case "activity":
|
|
2011
|
+
return {
|
|
2012
|
+
...base,
|
|
2013
|
+
// `confidence` is REQUIRED on both blocks in the freeze, precisely
|
|
2014
|
+
// because an inferred activity must never be handed over without
|
|
2015
|
+
// saying how sure the inference is. An empty snapshot is the least
|
|
2016
|
+
// sure of all.
|
|
2017
|
+
human: { state: "unknown", since: null, confidence: { level: "unknown" } },
|
|
2018
|
+
devicePose: { placement: "unknown", orientation: "unknown", confidence: { level: "unknown" } },
|
|
2019
|
+
recentTransitions: []
|
|
2020
|
+
};
|
|
2021
|
+
case "environment":
|
|
2022
|
+
return {
|
|
2023
|
+
...base,
|
|
2024
|
+
lighting: { level: "unknown", lux: null },
|
|
2025
|
+
acoustics: { level: "unknown", approximateDb: null },
|
|
2026
|
+
// 'unknown' rather than 'uncertain': uncertain means we looked and
|
|
2027
|
+
// could not decide, and nothing looked at all here.
|
|
2028
|
+
enclosure: { state: "unknown" },
|
|
2029
|
+
proximity: { distanceClass: "unknown" },
|
|
2030
|
+
nearbySignals: { bleAvailable: false, beaconCount: null, uwbAvailable: false }
|
|
2031
|
+
};
|
|
2032
|
+
case "capability": {
|
|
2033
|
+
const unknownStatus = { support: "unknown", permission: "unknown", availability: "unknown" };
|
|
2034
|
+
return {
|
|
2035
|
+
...base,
|
|
2036
|
+
camera: unknownStatus,
|
|
2037
|
+
microphone: unknownStatus,
|
|
2038
|
+
location: { ...unknownStatus, precision: "unknown", backgroundPermission: "unknown" },
|
|
2039
|
+
motion: unknownStatus,
|
|
2040
|
+
bluetooth: unknownStatus,
|
|
2041
|
+
uwb: unknownStatus,
|
|
2042
|
+
nfc: unknownStatus,
|
|
2043
|
+
notifications: unknownStatus,
|
|
2044
|
+
health: unknownStatus,
|
|
2045
|
+
backgroundExecution: unknownStatus
|
|
2046
|
+
};
|
|
2047
|
+
}
|
|
2048
|
+
default:
|
|
2049
|
+
return base;
|
|
2050
|
+
}
|
|
2051
|
+
}
|
|
2052
|
+
function deniedDomain(domain, why) {
|
|
2053
|
+
return {
|
|
2054
|
+
async getSnapshot() {
|
|
2055
|
+
return deniedSnapshot(domain);
|
|
2056
|
+
},
|
|
2057
|
+
async start() {
|
|
2058
|
+
throw denial(domain, why);
|
|
2059
|
+
},
|
|
2060
|
+
async stop() {
|
|
2061
|
+
throw denial(domain, why);
|
|
2062
|
+
},
|
|
2063
|
+
subscribe() {
|
|
2064
|
+
throw denial(domain, why);
|
|
2065
|
+
}
|
|
2066
|
+
};
|
|
2067
|
+
}
|
|
2068
|
+
function denial(domain, why) {
|
|
2069
|
+
const err = new Error(`deviceState.${domain}: ${why}`);
|
|
2070
|
+
err.code = "RESTRICTED";
|
|
2071
|
+
err.domain = domain;
|
|
2072
|
+
return err;
|
|
2073
|
+
}
|
|
2074
|
+
var ALL_DOMAINS = [
|
|
2075
|
+
"spatial",
|
|
2076
|
+
"visual",
|
|
2077
|
+
"activity",
|
|
2078
|
+
"deviceContext",
|
|
2079
|
+
"connectivity",
|
|
2080
|
+
"capability",
|
|
2081
|
+
"environment",
|
|
2082
|
+
"interaction"
|
|
2083
|
+
];
|
|
2084
|
+
var _provider = {};
|
|
2085
|
+
function setDeviceStateProvider(state) {
|
|
2086
|
+
_provider = state || {};
|
|
2087
|
+
}
|
|
2088
|
+
function gateDeviceState(declared) {
|
|
2089
|
+
const allowed = new Set(declared || []);
|
|
2090
|
+
const out = {};
|
|
2091
|
+
for (const domain of ALL_DOMAINS) {
|
|
2092
|
+
if (!allowed.has(domain)) {
|
|
2093
|
+
out[domain] = deniedDomain(
|
|
2094
|
+
domain,
|
|
2095
|
+
`not declared in this plugin's stateDomains. Add '${domain}' to use it.`
|
|
2096
|
+
);
|
|
2097
|
+
continue;
|
|
2098
|
+
}
|
|
2099
|
+
const engine = _provider[domain];
|
|
2100
|
+
out[domain] = engine ?? deniedDomain(
|
|
2101
|
+
domain,
|
|
2102
|
+
"no engine in this build \u2014 the native side does not exist yet. See docs/DEVICE_API_STATUS.md."
|
|
2103
|
+
);
|
|
2104
|
+
}
|
|
2105
|
+
return out;
|
|
2106
|
+
}
|
|
2107
|
+
function implementedStateDomains() {
|
|
2108
|
+
return ALL_DOMAINS.filter((d) => !!_provider[d]);
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
// src/tools/registry.ts
|
|
2112
|
+
var registry = /* @__PURE__ */ new Map();
|
|
2113
|
+
var contextProvider = null;
|
|
2114
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
2115
|
+
function setDeviceToolContextProvider(fn) {
|
|
2116
|
+
contextProvider = fn;
|
|
2117
|
+
}
|
|
2118
|
+
function defaultContext(pluginId, capabilities, stateDomains) {
|
|
2119
|
+
return {
|
|
2120
|
+
bridge: createNativeBridge(pluginId),
|
|
2121
|
+
native: gatePluginNative(getPluginNative(), capabilities, pluginId),
|
|
2122
|
+
deviceState: gateDeviceState(stateDomains)
|
|
2123
|
+
};
|
|
2124
|
+
}
|
|
2125
|
+
function registerDeviceTools(pluginId, tools, capabilities, stateDomains) {
|
|
2126
|
+
const accepted = [];
|
|
2127
|
+
for (const tool of tools || []) {
|
|
2128
|
+
if (!tool || typeof tool.name !== "string" || typeof tool.run !== "function") {
|
|
2129
|
+
console.warn(`[deviceTools] ${pluginId}: skipping malformed tool entry`);
|
|
2130
|
+
continue;
|
|
2131
|
+
}
|
|
2132
|
+
const existing = registry.get(tool.name);
|
|
2133
|
+
if (existing && existing.pluginId !== pluginId) {
|
|
2134
|
+
console.warn(
|
|
2135
|
+
`[deviceTools] ${pluginId}: "${tool.name}" already registered by ${existing.pluginId} \u2014 refusing to override`
|
|
2136
|
+
);
|
|
2137
|
+
continue;
|
|
2138
|
+
}
|
|
2139
|
+
registry.set(tool.name, { pluginId, tool, capabilities, stateDomains });
|
|
2140
|
+
accepted.push(tool.name);
|
|
2141
|
+
}
|
|
2142
|
+
if (accepted.length > 0) {
|
|
2143
|
+
console.log(`[deviceTools] ${pluginId} registered: ${accepted.join(", ")}`);
|
|
2144
|
+
}
|
|
2145
|
+
return accepted;
|
|
2146
|
+
}
|
|
2147
|
+
function unregisterDeviceTools(pluginId) {
|
|
2148
|
+
for (const [name, entry] of registry.entries()) {
|
|
2149
|
+
if (entry.pluginId === pluginId) registry.delete(name);
|
|
2150
|
+
}
|
|
2151
|
+
}
|
|
2152
|
+
function hasDeviceTool(name) {
|
|
2153
|
+
return registry.has(name);
|
|
2154
|
+
}
|
|
2155
|
+
function listDeviceTools() {
|
|
2156
|
+
return Array.from(registry.entries()).map(([name, { pluginId, tool }]) => ({
|
|
2157
|
+
name,
|
|
2158
|
+
pluginId,
|
|
2159
|
+
description: tool.description,
|
|
2160
|
+
input_schema: tool.input_schema
|
|
2161
|
+
}));
|
|
2162
|
+
}
|
|
2163
|
+
async function callDeviceTool(name, args = {}, opts = {}) {
|
|
2164
|
+
const entry = registry.get(name);
|
|
2165
|
+
if (!entry) {
|
|
2166
|
+
return { ok: false, reason: "not_found", error: `No device tool registered as "${name}"` };
|
|
2167
|
+
}
|
|
2168
|
+
const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2169
|
+
let timer;
|
|
2170
|
+
try {
|
|
2171
|
+
const build = contextProvider || defaultContext;
|
|
2172
|
+
const ctx = {
|
|
2173
|
+
...build(entry.pluginId, entry.capabilities, entry.stateDomains),
|
|
2174
|
+
signal: opts.signal
|
|
2175
|
+
};
|
|
2176
|
+
const result = await Promise.race([
|
|
2177
|
+
Promise.resolve(entry.tool.run(args, ctx)),
|
|
2178
|
+
new Promise((_, reject) => {
|
|
2179
|
+
timer = setTimeout(
|
|
2180
|
+
() => reject(new Error(`Device tool "${name}" timed out after ${timeoutMs}ms`)),
|
|
2181
|
+
timeoutMs
|
|
2182
|
+
);
|
|
2183
|
+
})
|
|
2184
|
+
]);
|
|
2185
|
+
return { ok: true, result };
|
|
2186
|
+
} catch (err) {
|
|
2187
|
+
const message = err?.message || String(err);
|
|
2188
|
+
const timedOut = message.includes("timed out after");
|
|
2189
|
+
return { ok: false, reason: timedOut ? "timeout" : "failed", error: message };
|
|
2190
|
+
} finally {
|
|
2191
|
+
if (timer) clearTimeout(timer);
|
|
2192
|
+
}
|
|
2193
|
+
}
|
|
2194
|
+
function clearDeviceTools() {
|
|
2195
|
+
registry.clear();
|
|
2196
|
+
}
|
|
2197
|
+
|
|
2198
|
+
// src/bundles/bundleManager.ts
|
|
2199
|
+
var ReactShim = {
|
|
2200
|
+
__esModule: true,
|
|
2201
|
+
default: React2,
|
|
2202
|
+
...React2,
|
|
2203
|
+
// Explicitly include hooks (React 19 may not have them as own enumerable props)
|
|
2204
|
+
useState,
|
|
2205
|
+
useEffect: useEffect6,
|
|
2206
|
+
useCallback: useCallback2,
|
|
2207
|
+
useMemo: useMemo4,
|
|
2208
|
+
useRef: useRef4,
|
|
2209
|
+
useContext,
|
|
2210
|
+
useReducer,
|
|
2211
|
+
useLayoutEffect,
|
|
2212
|
+
useImperativeHandle,
|
|
2213
|
+
useDebugValue,
|
|
2214
|
+
createElement: React2.createElement,
|
|
2215
|
+
Fragment: React2.Fragment,
|
|
2216
|
+
Component: React2.Component,
|
|
2217
|
+
PureComponent: React2.PureComponent,
|
|
2218
|
+
createContext: React2.createContext,
|
|
2219
|
+
forwardRef: React2.forwardRef,
|
|
2220
|
+
memo: React2.memo,
|
|
2221
|
+
Children: React2.Children,
|
|
2222
|
+
cloneElement: React2.cloneElement,
|
|
2223
|
+
isValidElement: React2.isValidElement,
|
|
2224
|
+
createRef: React2.createRef,
|
|
2225
|
+
lazy: React2.lazy,
|
|
2226
|
+
Suspense: React2.Suspense
|
|
2227
|
+
};
|
|
2228
|
+
var PluginSDKShim = Object.freeze({
|
|
2229
|
+
...sdk_exports,
|
|
2230
|
+
SurfaceDrawer,
|
|
2231
|
+
SurfaceCard,
|
|
2232
|
+
SurfaceFullscreen,
|
|
2233
|
+
useSurfaceStore,
|
|
2234
|
+
SurfaceDispatcher,
|
|
2235
|
+
PluginSDK: Object.freeze({
|
|
2236
|
+
...PluginSDK,
|
|
2237
|
+
register: PluginSDK.register
|
|
2238
|
+
}),
|
|
2239
|
+
register
|
|
2240
|
+
});
|
|
2241
|
+
var requireShim = (name) => {
|
|
2242
|
+
if (name === "react") return ReactShim;
|
|
2243
|
+
if (name === "react-native") return ReactNative;
|
|
2244
|
+
if (name === "@adas/plugin-sdk") return PluginSDKShim;
|
|
2245
|
+
const hostModule = resolveBundleModule(name);
|
|
2246
|
+
if (hostModule !== void 0) return hostModule;
|
|
2247
|
+
console.warn(`[BundleManager] Unknown module: ${name} \u2014 register it via registerBundleModules()`);
|
|
2248
|
+
return {};
|
|
2249
|
+
};
|
|
2250
|
+
var CACHE_TTL = 7 * 24 * 60 * 60 * 1e3;
|
|
2251
|
+
var BundleManager = class {
|
|
2252
|
+
constructor() {
|
|
2253
|
+
/** In-memory module cache: bundleUrl → evaluated module.exports. Holds ONE
|
|
2254
|
+
* tenant's modules at a time — the tenant boundary is `cacheTenant` /
|
|
2255
|
+
* `requireTenant()`, NOT the key. */
|
|
2256
|
+
this.modules = /* @__PURE__ */ new Map();
|
|
2257
|
+
/** What is actually RUNNING, keyed by bundleUrl (for the current tenant). See
|
|
2258
|
+
* getLoadedBundles(). */
|
|
2259
|
+
this.provenance = /* @__PURE__ */ new Map();
|
|
2260
|
+
/** In-flight download deduplication: bundleUrl → Promise */
|
|
2261
|
+
this.inflight = /* @__PURE__ */ new Map();
|
|
2262
|
+
/**
|
|
2263
|
+
* The tenant the in-memory caches (modules / provenance / inflight) currently
|
|
2264
|
+
* belong to. This is the tenant BOUNDARY for evaluated code.
|
|
2265
|
+
*
|
|
2266
|
+
* A bundleUrl resolves PER-TENANT on the server — one url, different bytes for
|
|
2267
|
+
* different tenants — so a memory hit keyed on url alone would hand tenant B
|
|
2268
|
+
* the module tenant A already evaluated and ran. PR #9 scoped the DISK cache
|
|
2269
|
+
* (per-tenant index + hash gate); the in-memory map was left url-keyed. This
|
|
2270
|
+
* closes that: the caches only ever hold ONE tenant's modules, and a switch
|
|
2271
|
+
* drops them before anything is served.
|
|
2272
|
+
*/
|
|
2273
|
+
this.cacheTenant = null;
|
|
2274
|
+
/**
|
|
2275
|
+
* pluginIds this manager registered device tools for. A tenant switch drops
|
|
2276
|
+
* the in-memory modules AND unregisters these, so the incoming tenant's bundle
|
|
2277
|
+
* re-registers into a clean registry instead of being refused as a duplicate
|
|
2278
|
+
* of the previous tenant's tools.
|
|
2279
|
+
*/
|
|
2280
|
+
this.registeredToolPluginIds = /* @__PURE__ */ new Set();
|
|
2281
|
+
/**
|
|
2282
|
+
* Enforce the in-memory tenant boundary. Call at the top of every public load
|
|
2283
|
+
* path, BEFORE any cache read.
|
|
2284
|
+
*
|
|
2285
|
+
* FAIL CLOSED: without a tenant identity we cannot prove a cached module is
|
|
2286
|
+
* this session's, so we drop everything and refuse rather than risk serving
|
|
2287
|
+
* one tenant's evaluated code to another. On a tenant change we drop the
|
|
2288
|
+
* previous tenant's modules (and their tool registrations) before proceeding.
|
|
2289
|
+
*/
|
|
2290
|
+
/**
|
|
2291
|
+
* Monotonic load generation. Bumped on every in-memory wipe (a tenant change,
|
|
2292
|
+
* fail-closed, or clearAll). A load captures it at ENTRY; a completion whose
|
|
2293
|
+
* generation no longer matches is stale and is discarded before it can commit.
|
|
2294
|
+
* This closes the async race the sequential guard cannot: tenant A's in-flight
|
|
2295
|
+
* loadModule can resolve AFTER a switch to B — clearing `inflight` does not
|
|
2296
|
+
* cancel that Promise — and would otherwise write A's module / provenance /
|
|
2297
|
+
* device tools into the singleton now marked cacheTenant=B.
|
|
2298
|
+
*/
|
|
2299
|
+
this.epoch = 0;
|
|
2300
|
+
}
|
|
2301
|
+
requireTenant() {
|
|
2302
|
+
const tenant = getTenant2();
|
|
2303
|
+
if (!tenant) {
|
|
2304
|
+
this.dropInMemory();
|
|
2305
|
+
this.cacheTenant = null;
|
|
2306
|
+
throw new Error(
|
|
2307
|
+
"[BundleManager] refusing to load a tenant-scoped bundle without a tenant identity (fail closed)"
|
|
2308
|
+
);
|
|
2309
|
+
}
|
|
2310
|
+
if (tenant !== this.cacheTenant) {
|
|
2311
|
+
this.dropInMemory();
|
|
2312
|
+
this.cacheTenant = tenant;
|
|
2313
|
+
}
|
|
2314
|
+
return tenant;
|
|
2315
|
+
}
|
|
2316
|
+
/** Drop every in-memory cache and unregister the device tools this manager
|
|
2317
|
+
* registered — used on a tenant change and by clearAll(). */
|
|
2318
|
+
dropInMemory() {
|
|
2319
|
+
for (const pluginId of this.registeredToolPluginIds) {
|
|
2320
|
+
try {
|
|
2321
|
+
unregisterDeviceTools(pluginId);
|
|
2322
|
+
} catch {
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
this.registeredToolPluginIds.clear();
|
|
2326
|
+
this.modules.clear();
|
|
2327
|
+
this.provenance.clear();
|
|
2328
|
+
this.inflight.clear();
|
|
2329
|
+
this.epoch++;
|
|
2330
|
+
}
|
|
2331
|
+
/** True once the boundary has moved since a load captured `epoch` — i.e. a
|
|
2332
|
+
* tenant switch (or wipe) happened while this load was in flight. Its results
|
|
2333
|
+
* belong to a tenant that is no longer current and must be discarded. */
|
|
2334
|
+
isStale(epoch, tenant) {
|
|
2335
|
+
return this.epoch !== epoch || getTenant2() !== tenant;
|
|
2336
|
+
}
|
|
2337
|
+
/** Flip an existing provenance record to source:'memory' — a later serve of an
|
|
2338
|
+
* already-evaluated bundle came from the in-memory cache, not a fresh
|
|
2339
|
+
* download/disk read. No-op if nothing was recorded yet. */
|
|
2340
|
+
markMemoryHit(bundleUrl) {
|
|
2341
|
+
const prev = this.provenance.get(bundleUrl);
|
|
2342
|
+
if (prev && prev.source !== "memory") {
|
|
2343
|
+
this.provenance.set(bundleUrl, { ...prev, source: "memory" });
|
|
2344
|
+
}
|
|
2345
|
+
}
|
|
2346
|
+
// ── Public API ──────────────────────────────────────────────
|
|
2347
|
+
/**
|
|
2348
|
+
* Load a plugin component. Returns the RegisteredUIPlugin object.
|
|
2349
|
+
* Handles: in-memory cache → disk cache → download → evaluate → extract.
|
|
2350
|
+
*
|
|
2351
|
+
* `expectedHash` (SHA-256 hex from cp.getContextPlugin manifest) MUST be
|
|
2352
|
+
* provided for any bundle the user has not previously verified — without
|
|
2353
|
+
* it, a tampered bundle is `eval`'d as JS with full RN runtime + every
|
|
2354
|
+
* native capability the manifest declares.
|
|
2355
|
+
*/
|
|
2356
|
+
async load(pluginId, bundleUrl, version, expectedHash) {
|
|
2357
|
+
const tenant = this.requireTenant();
|
|
2358
|
+
const epoch = this.epoch;
|
|
2359
|
+
const cached = this.getComponentFromMemory(pluginId, bundleUrl);
|
|
2360
|
+
if (cached) {
|
|
2361
|
+
this.markMemoryHit(bundleUrl);
|
|
2362
|
+
return cached;
|
|
2363
|
+
}
|
|
2364
|
+
let modulePromise = this.inflight.get(bundleUrl);
|
|
2365
|
+
if (!modulePromise) {
|
|
2366
|
+
const promise = this.loadModule(bundleUrl, version, expectedHash, tenant, epoch);
|
|
2367
|
+
modulePromise = promise;
|
|
2368
|
+
this.inflight.set(bundleUrl, promise);
|
|
2369
|
+
promise.catch(() => {
|
|
2370
|
+
}).finally(() => {
|
|
2371
|
+
if (this.inflight.get(bundleUrl) === promise) this.inflight.delete(bundleUrl);
|
|
2372
|
+
});
|
|
2373
|
+
}
|
|
2374
|
+
await modulePromise;
|
|
2375
|
+
if (this.isStale(epoch, tenant)) {
|
|
2376
|
+
throw new Error("[BundleManager] tenant changed while loading \u2014 discarding (fail closed)");
|
|
2377
|
+
}
|
|
2378
|
+
const component = this.getComponentFromMemory(pluginId, bundleUrl);
|
|
2379
|
+
if (!component) {
|
|
2380
|
+
throw new Error(
|
|
2381
|
+
`Bundle loaded from ${bundleUrl} but no component found for "${pluginId}". Available exports: ${this.describeModule(this.modules.get(bundleUrl))}`
|
|
2382
|
+
);
|
|
2383
|
+
}
|
|
2384
|
+
return component;
|
|
2385
|
+
}
|
|
2386
|
+
/**
|
|
2387
|
+
* Load a bundle for its `tools/` section only — no UI required.
|
|
2388
|
+
*
|
|
2389
|
+
* Same download, same integrity check, same evaluation as `load()`; the only
|
|
2390
|
+
* difference is that a bundle exporting tools and no `Component` is a success
|
|
2391
|
+
* here rather than "no component found". Returns the tool names registered.
|
|
2392
|
+
*/
|
|
2393
|
+
async loadTools(pluginId, bundleUrl, version, expectedHash) {
|
|
2394
|
+
const tenant = this.requireTenant();
|
|
2395
|
+
const epoch = this.epoch;
|
|
2396
|
+
let modulePromise = this.inflight.get(bundleUrl);
|
|
2397
|
+
if (!modulePromise) {
|
|
2398
|
+
const promise = this.loadModule(bundleUrl, version, expectedHash, tenant, epoch);
|
|
2399
|
+
modulePromise = promise;
|
|
2400
|
+
this.inflight.set(bundleUrl, promise);
|
|
2401
|
+
promise.catch(() => {
|
|
2402
|
+
}).finally(() => {
|
|
2403
|
+
if (this.inflight.get(bundleUrl) === promise) this.inflight.delete(bundleUrl);
|
|
2404
|
+
});
|
|
2405
|
+
}
|
|
2406
|
+
await modulePromise;
|
|
2407
|
+
if (this.isStale(epoch, tenant)) {
|
|
2408
|
+
throw new Error("[BundleManager] tenant changed while loading tools \u2014 discarding (fail closed)");
|
|
2409
|
+
}
|
|
2410
|
+
const names = listDeviceTools().filter((t) => t.pluginId === pluginId).map((t) => t.name);
|
|
2411
|
+
if (names.length === 0) {
|
|
2412
|
+
console.warn(
|
|
2413
|
+
`[BundleManager] ${pluginId}: bundle loaded from ${bundleUrl} but registered no tools. Available exports: ${this.describeModule(this.modules.get(bundleUrl))}`
|
|
2414
|
+
);
|
|
2415
|
+
}
|
|
2416
|
+
return names;
|
|
2417
|
+
}
|
|
2418
|
+
/**
|
|
2419
|
+
* Prewarm: load every plugin bundle in the background after app boot, so a
|
|
2420
|
+
* later open is instant (in-memory hit) instead of round-tripping the
|
|
2421
|
+
* network. Errors are swallowed per-plugin — one bad bundle shouldn't
|
|
2422
|
+
* block others. Concurrency capped at 3.
|
|
2423
|
+
*/
|
|
2424
|
+
async prewarmAll(items) {
|
|
2425
|
+
const queue = [...items];
|
|
2426
|
+
const concurrency = Math.min(3, queue.length);
|
|
2427
|
+
const start = Date.now();
|
|
2428
|
+
let okCount = 0;
|
|
2429
|
+
let failCount = 0;
|
|
2430
|
+
const worker = async () => {
|
|
2431
|
+
while (queue.length > 0) {
|
|
2432
|
+
const it = queue.shift();
|
|
2433
|
+
if (!it) return;
|
|
2434
|
+
try {
|
|
2435
|
+
await this.load(it.pluginId, it.bundleUrl, it.version, it.bundleHash);
|
|
2436
|
+
okCount++;
|
|
2437
|
+
} catch (err) {
|
|
2438
|
+
failCount++;
|
|
2439
|
+
console.log(`[prewarm] ${it.pluginId}: ${err instanceof Error ? err.message : String(err)}`);
|
|
2440
|
+
}
|
|
2441
|
+
}
|
|
2442
|
+
};
|
|
2443
|
+
await Promise.all(Array.from({ length: concurrency }, worker));
|
|
2444
|
+
console.log(`[prewarm] ${okCount} loaded, ${failCount} failed in ${Date.now() - start}ms`);
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* Clear all caches (in-memory + storage).
|
|
2448
|
+
*/
|
|
2449
|
+
async clearAll() {
|
|
2450
|
+
this.dropInMemory();
|
|
2451
|
+
this.cacheTenant = null;
|
|
2452
|
+
try {
|
|
2453
|
+
await getBundleStorage().clearAll();
|
|
2454
|
+
} catch (err) {
|
|
2455
|
+
console.warn("[BundleManager] clearAll error:", err);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
/**
|
|
2459
|
+
* Clear cache for a specific plugin/bundle.
|
|
2460
|
+
*/
|
|
2461
|
+
async clearPlugin(pluginId) {
|
|
2462
|
+
const storage = getBundleStorage();
|
|
2463
|
+
const index = await storage.loadIndex();
|
|
2464
|
+
const entry = index[pluginId];
|
|
2465
|
+
if (entry) {
|
|
2466
|
+
this.modules.delete(entry.bundleUrl);
|
|
2467
|
+
delete index[pluginId];
|
|
2468
|
+
await storage.saveIndex(index);
|
|
2469
|
+
try {
|
|
2470
|
+
await storage.deleteBundle(entry.localPath);
|
|
2471
|
+
} catch {
|
|
2472
|
+
}
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
/**
|
|
2476
|
+
* What every loaded bundle IS, right now: the sha256 of the bytes running,
|
|
2477
|
+
* the hash the server advertised, and where they came from.
|
|
2478
|
+
*
|
|
2479
|
+
* Deliberately not filtered or prettified — this is the raw answer to "did my
|
|
2480
|
+
* redeploy reach the phone?", and it is comparable directly against the
|
|
2481
|
+
* manifest `bundleHash` and the server's X-Bundle-SHA256 header.
|
|
2482
|
+
*/
|
|
2483
|
+
getLoadedBundles() {
|
|
2484
|
+
return [...this.provenance.values()];
|
|
2485
|
+
}
|
|
2486
|
+
// ── Private: module loading pipeline ────────────────────────
|
|
2487
|
+
async loadModule(bundleUrl, version, expectedHash, expectedTenant, expectedEpoch) {
|
|
2488
|
+
if (this.isStale(expectedEpoch, expectedTenant)) {
|
|
2489
|
+
throw new Error("[BundleManager] tenant changed before load started \u2014 discarding (fail closed)");
|
|
2490
|
+
}
|
|
2491
|
+
if (this.modules.has(bundleUrl)) {
|
|
2492
|
+
const prev = this.provenance.get(bundleUrl);
|
|
2493
|
+
if (prev) this.provenance.set(bundleUrl, { ...prev, source: "memory", expected: expectedHash?.toLowerCase() ?? prev.expected });
|
|
2494
|
+
return;
|
|
2495
|
+
}
|
|
2496
|
+
const storage = getBundleStorage();
|
|
2497
|
+
const index = await storage.loadIndex();
|
|
2498
|
+
const expectedLc = expectedHash ? expectedHash.toLowerCase() : null;
|
|
2499
|
+
let content = null;
|
|
2500
|
+
let verifiedHash = null;
|
|
2501
|
+
let servedFrom = "download";
|
|
2502
|
+
for (const entry of Object.values(index)) {
|
|
2503
|
+
if (entry.bundleUrl === bundleUrl && entry.version === version) {
|
|
2504
|
+
const age = Date.now() - entry.timestamp;
|
|
2505
|
+
const hashOk = expectedLc && entry.bundleHash && entry.bundleHash.toLowerCase() === expectedLc;
|
|
2506
|
+
if (age < CACHE_TTL && hashOk) {
|
|
2507
|
+
content = await storage.readBundle(entry.localPath);
|
|
2508
|
+
if (content) {
|
|
2509
|
+
verifiedHash = entry.bundleHash.toLowerCase();
|
|
2510
|
+
servedFrom = "disk-cache";
|
|
2511
|
+
break;
|
|
2512
|
+
}
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2515
|
+
}
|
|
2516
|
+
if (!content) {
|
|
2517
|
+
const downloaded = await this.download(bundleUrl, expectedLc);
|
|
2518
|
+
content = downloaded.content;
|
|
2519
|
+
verifiedHash = downloaded.sha256.toLowerCase();
|
|
2520
|
+
if (this.isStale(expectedEpoch, expectedTenant)) {
|
|
2521
|
+
throw new Error("[BundleManager] tenant changed during download \u2014 discarding (fail closed)");
|
|
2522
|
+
}
|
|
2523
|
+
try {
|
|
2524
|
+
const localPath = await storage.writeBundle(`${verifiedHash}.js`, content);
|
|
2525
|
+
if (!this.isStale(expectedEpoch, expectedTenant)) {
|
|
2526
|
+
index[bundleUrl] = {
|
|
2527
|
+
localPath,
|
|
2528
|
+
version,
|
|
2529
|
+
timestamp: Date.now(),
|
|
2530
|
+
bundleUrl,
|
|
2531
|
+
bundleHash: downloaded.sha256,
|
|
2532
|
+
tenant: expectedTenant
|
|
2533
|
+
};
|
|
2534
|
+
await storage.saveIndex(index);
|
|
2535
|
+
}
|
|
2536
|
+
} catch (err) {
|
|
2537
|
+
console.warn("[BundleManager] Cache write error:", err);
|
|
2538
|
+
}
|
|
2539
|
+
}
|
|
2540
|
+
if (this.isStale(expectedEpoch, expectedTenant)) {
|
|
2541
|
+
throw new Error("[BundleManager] tenant changed while loading \u2014 discarding stale bundle before eval (fail closed)");
|
|
2542
|
+
}
|
|
2543
|
+
const mod = this.evaluate(content, bundleUrl, verifiedHash);
|
|
2544
|
+
this.modules.set(bundleUrl, mod);
|
|
2545
|
+
this.provenance.set(bundleUrl, {
|
|
2546
|
+
bundleUrl,
|
|
2547
|
+
version,
|
|
2548
|
+
hash: verifiedHash,
|
|
2549
|
+
expected: expectedLc,
|
|
2550
|
+
source: servedFrom,
|
|
2551
|
+
// The captured owner — same rule as the disk stamp. The final stale check
|
|
2552
|
+
// above guarantees getTenant() === expectedTenant here, but binding it to
|
|
2553
|
+
// the captured value keeps the ownership record impossible to mislabel.
|
|
2554
|
+
tenant: expectedTenant,
|
|
2555
|
+
loadedAt: Date.now()
|
|
2556
|
+
});
|
|
2557
|
+
console.log(
|
|
2558
|
+
`[BundleManager] RUNNING ${bundleUrl} v${version} sha=${(verifiedHash || "none").slice(0, 12)} (${servedFrom}${expectedLc && verifiedHash && expectedLc !== verifiedHash ? `, EXPECTED ${expectedLc.slice(0, 12)} \u2014 STALE` : ""})`
|
|
2559
|
+
);
|
|
2560
|
+
this.registerToolsFromModule(mod, bundleUrl);
|
|
2561
|
+
}
|
|
2562
|
+
/**
|
|
2563
|
+
* Pick up every `tools` section in an evaluated bundle and put it in the
|
|
2564
|
+
* device tool registry. Runs for UI, hybrid and tools-only bundles alike: a
|
|
2565
|
+
* plugin that ships both gets its tools registered without its UI mounting.
|
|
2566
|
+
*/
|
|
2567
|
+
registerToolsFromModule(mod, bundleUrl) {
|
|
2568
|
+
if (!mod) return;
|
|
2569
|
+
for (const key of Object.keys(mod)) {
|
|
2570
|
+
const val = mod[key];
|
|
2571
|
+
if (!val || typeof val !== "object" || !Array.isArray(val.tools) || val.tools.length === 0) {
|
|
2572
|
+
continue;
|
|
2573
|
+
}
|
|
2574
|
+
const pluginId = typeof val.id === "string" ? val.id : `${bundleUrl}#${key}`;
|
|
2575
|
+
try {
|
|
2576
|
+
registerDeviceTools(pluginId, val.tools, val.capabilities, val.stateDomains);
|
|
2577
|
+
this.registeredToolPluginIds.add(pluginId);
|
|
2578
|
+
} catch (err) {
|
|
2579
|
+
console.warn(
|
|
2580
|
+
`[BundleManager] tool registration failed for ${pluginId}:`,
|
|
2581
|
+
err instanceof Error ? err.message : String(err)
|
|
2582
|
+
);
|
|
2583
|
+
}
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
/**
|
|
2587
|
+
* Download + integrity-verify a bundle. Refuses to return when:
|
|
2588
|
+
* - manifest hash and computed hash disagree
|
|
2589
|
+
* - server's `X-Bundle-SHA256` disagrees with manifest hash
|
|
2590
|
+
* - no expected hash AND no server hash header (cannot prove the bundle
|
|
2591
|
+
* wasn't tampered with — fail closed rather than trust the wire).
|
|
2592
|
+
*/
|
|
2593
|
+
async download(bundleUrl, expectedHash) {
|
|
2594
|
+
const cacheBust = `${bundleUrl}${bundleUrl.includes("?") ? "&" : "?"}t=${Date.now()}`;
|
|
2595
|
+
const response = await apiFetchRaw(cacheBust);
|
|
2596
|
+
if (!response.ok) {
|
|
2597
|
+
const body = await response.text().catch(() => "");
|
|
2598
|
+
throw new Error(`Download failed: ${response.status} ${response.statusText} \u2014 ${body.substring(0, 200)}`);
|
|
2599
|
+
}
|
|
2600
|
+
const content = await response.text();
|
|
2601
|
+
if (content.length < 50 || content.trimStart().startsWith("<!")) {
|
|
2602
|
+
throw new Error("Bundle content is empty or HTML \u2014 server may have returned an error page");
|
|
2603
|
+
}
|
|
2604
|
+
const headerHash = (response.headers.get("x-bundle-sha256") || "").toLowerCase();
|
|
2605
|
+
const computed = sha256Hex(content);
|
|
2606
|
+
if (expectedHash) {
|
|
2607
|
+
if (computed !== expectedHash) {
|
|
2608
|
+
throw new Error(
|
|
2609
|
+
`Bundle integrity FAIL: expected sha256=${expectedHash.slice(0, 12)}... got ${computed.slice(0, 12)}.... Refusing to evaluate.`
|
|
2610
|
+
);
|
|
2611
|
+
}
|
|
2612
|
+
if (headerHash && headerHash !== expectedHash) {
|
|
2613
|
+
throw new Error(
|
|
2614
|
+
`Bundle hash mismatch: manifest=${expectedHash.slice(0, 12)}... server=${headerHash.slice(0, 12)}.... Refusing to evaluate.`
|
|
2615
|
+
);
|
|
2616
|
+
}
|
|
2617
|
+
} else if (headerHash) {
|
|
2618
|
+
if (computed !== headerHash) {
|
|
2619
|
+
throw new Error(
|
|
2620
|
+
`Bundle body sha256 (${computed.slice(0, 12)}...) does not match server's X-Bundle-SHA256 (${headerHash.slice(0, 12)}...).`
|
|
2621
|
+
);
|
|
2622
|
+
}
|
|
2623
|
+
console.warn(`[BundleManager] No manifest bundleHash \u2014 verified via X-Bundle-SHA256 header only`);
|
|
2624
|
+
} else {
|
|
2625
|
+
throw new Error(
|
|
2626
|
+
`No bundleHash in manifest and no X-Bundle-SHA256 header from server. Refusing to evaluate untrusted bundle.`
|
|
2627
|
+
);
|
|
2628
|
+
}
|
|
2629
|
+
return { content, sha256: computed };
|
|
2630
|
+
}
|
|
2631
|
+
evaluate(content, bundleUrl, verifiedHash) {
|
|
2632
|
+
assertBundleIntegrity(content, verifiedHash, bundleUrl);
|
|
2633
|
+
let cleaned = content.replace(
|
|
2634
|
+
/__toESM\(require\(("[^"]+")\)(?:,\s*\d+)?\)/g,
|
|
2635
|
+
"require($1)"
|
|
2636
|
+
);
|
|
2637
|
+
cleaned = cleaned.replace(
|
|
2638
|
+
/module\.exports\s*=\s*__toCommonJS\((\w+)\)/g,
|
|
2639
|
+
"module.exports = $1"
|
|
2640
|
+
);
|
|
2641
|
+
const mod = { exports: {} };
|
|
2642
|
+
try {
|
|
2643
|
+
const factory = new Function(
|
|
2644
|
+
"module",
|
|
2645
|
+
"exports",
|
|
2646
|
+
"require",
|
|
2647
|
+
"__filename",
|
|
2648
|
+
"__dirname",
|
|
2649
|
+
cleaned
|
|
2650
|
+
);
|
|
2651
|
+
factory(mod, mod.exports, requireShim, bundleUrl, "adas-plugin-bundles");
|
|
2652
|
+
} catch (err) {
|
|
2653
|
+
throw new Error(`Bundle evaluation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
2654
|
+
}
|
|
2655
|
+
const raw = mod.exports;
|
|
2656
|
+
const materialized = {};
|
|
2657
|
+
for (const key of Object.getOwnPropertyNames(raw)) {
|
|
2658
|
+
try {
|
|
2659
|
+
materialized[key] = raw[key];
|
|
2660
|
+
} catch {
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
return materialized;
|
|
2664
|
+
}
|
|
2665
|
+
// ── Private: component extraction ───────────────────────────
|
|
2666
|
+
/**
|
|
2667
|
+
* Find a plugin component in a loaded module.
|
|
2668
|
+
* Handles: default export, direct key match, multi-plugin .id match.
|
|
2669
|
+
*/
|
|
2670
|
+
getComponentFromMemory(pluginId, bundleUrl) {
|
|
2671
|
+
const mod = this.modules.get(bundleUrl);
|
|
2672
|
+
if (!mod) return null;
|
|
2673
|
+
const shortName = pluginId.includes(":") ? pluginId.split(":").pop() : pluginId;
|
|
2674
|
+
if (mod[pluginId] && typeof mod[pluginId].Component === "function") {
|
|
2675
|
+
return mod[pluginId];
|
|
2676
|
+
}
|
|
2677
|
+
const pascal = shortName.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase());
|
|
2678
|
+
if (mod[pascal] && typeof mod[pascal] === "object" && typeof mod[pascal].Component === "function") {
|
|
2679
|
+
return mod[pascal];
|
|
2680
|
+
}
|
|
2681
|
+
if (mod.default && typeof mod.default === "object" && typeof mod.default.Component === "function") {
|
|
2682
|
+
return mod.default;
|
|
2683
|
+
}
|
|
2684
|
+
for (const key of Object.keys(mod)) {
|
|
2685
|
+
const val = mod[key];
|
|
2686
|
+
if (val && typeof val === "object" && val.id === shortName && typeof val.Component === "function") {
|
|
2687
|
+
return val;
|
|
2688
|
+
}
|
|
2689
|
+
}
|
|
2690
|
+
return null;
|
|
2691
|
+
}
|
|
2692
|
+
describeModule(mod) {
|
|
2693
|
+
if (!mod) return "(empty)";
|
|
2694
|
+
return Object.keys(mod).map((k) => {
|
|
2695
|
+
const v = mod[k];
|
|
2696
|
+
if (v && typeof v === "object" && v.id) return `${k}(id="${v.id}")`;
|
|
2697
|
+
return k;
|
|
2698
|
+
}).join(", ");
|
|
2699
|
+
}
|
|
2700
|
+
};
|
|
2701
|
+
var bundleManager = new BundleManager();
|
|
2702
|
+
|
|
2703
|
+
// src/tools/loader.ts
|
|
2704
|
+
import { getTenant as getTenant3 } from "@ateam-ai-mobile/core";
|
|
2705
|
+
var inflight = null;
|
|
2706
|
+
var loadedOnce = false;
|
|
2707
|
+
async function ensureDeviceToolsLoaded({ force = false } = {}) {
|
|
2708
|
+
if (loadedOnce && !force) return listDeviceTools().map((t) => t.name);
|
|
2709
|
+
if (inflight) return inflight;
|
|
2710
|
+
inflight = (async () => {
|
|
2711
|
+
try {
|
|
2712
|
+
const summaries = await listPlugins();
|
|
2713
|
+
const manifests = await Promise.all(
|
|
2714
|
+
(summaries || []).map(
|
|
2715
|
+
(s) => getPlugin(s.id).catch(() => null)
|
|
2716
|
+
)
|
|
2717
|
+
);
|
|
2718
|
+
const targets = manifests.filter((m) => {
|
|
2719
|
+
const declared = m?.capabilities?.device_tools;
|
|
2720
|
+
return Array.isArray(declared) && declared.length > 0;
|
|
2721
|
+
});
|
|
2722
|
+
if (targets.length === 0) {
|
|
2723
|
+
const recovered = await loadFromDiskCache();
|
|
2724
|
+
if (recovered.length > 0) {
|
|
2725
|
+
console.log(`[deviceTools] catalog listed none \u2014 recovered from cache: ${recovered.join(", ")}`);
|
|
2726
|
+
return recovered;
|
|
2727
|
+
}
|
|
2728
|
+
console.log("[deviceTools] no device tools available yet \u2014 will look again next call");
|
|
2729
|
+
return [];
|
|
2730
|
+
}
|
|
2731
|
+
await Promise.all(
|
|
2732
|
+
targets.map(async (m) => {
|
|
2733
|
+
const bundleUrl = getNativeBundleUrl(m);
|
|
2734
|
+
if (!bundleUrl) {
|
|
2735
|
+
console.warn(`[deviceTools] ${m.id} declares device_tools but has no native bundle`);
|
|
2736
|
+
return;
|
|
2737
|
+
}
|
|
2738
|
+
try {
|
|
2739
|
+
await bundleManager.loadTools(
|
|
2740
|
+
m.id,
|
|
2741
|
+
bundleUrl,
|
|
2742
|
+
m.version || "1.0.0",
|
|
2743
|
+
getNativeBundleHash(m) || void 0
|
|
2744
|
+
);
|
|
2745
|
+
} catch (err) {
|
|
2746
|
+
console.warn(`[deviceTools] ${m.id} failed to load:`, err?.message || String(err));
|
|
2747
|
+
}
|
|
2748
|
+
})
|
|
2749
|
+
);
|
|
2750
|
+
const names = listDeviceTools().map((t) => t.name);
|
|
2751
|
+
loadedOnce = names.length > 0;
|
|
2752
|
+
console.log(`[deviceTools] ready: ${names.length > 0 ? names.join(", ") : "(none registered \u2014 will retry next call)"}`);
|
|
2753
|
+
return names;
|
|
2754
|
+
} finally {
|
|
2755
|
+
inflight = null;
|
|
2756
|
+
}
|
|
2757
|
+
})();
|
|
2758
|
+
return inflight;
|
|
2759
|
+
}
|
|
2760
|
+
async function loadFromDiskCache() {
|
|
2761
|
+
try {
|
|
2762
|
+
const index = await getBundleStorage().loadIndex();
|
|
2763
|
+
const entries = Object.values(index || {});
|
|
2764
|
+
if (entries.length === 0) return [];
|
|
2765
|
+
const tenant = getTenant3();
|
|
2766
|
+
if (!tenant) {
|
|
2767
|
+
console.warn("[deviceTools] disk-cache recovery skipped \u2014 no active tenant");
|
|
2768
|
+
return [];
|
|
2769
|
+
}
|
|
2770
|
+
const mine = selectOwnedCacheEntries(entries, tenant);
|
|
2771
|
+
const skipped = entries.length - mine.length;
|
|
2772
|
+
if (skipped > 0) {
|
|
2773
|
+
console.log(`[deviceTools] disk-cache recovery: ${mine.length} for "${tenant}", skipped ${skipped} not owned by it`);
|
|
2774
|
+
}
|
|
2775
|
+
if (mine.length === 0) return [];
|
|
2776
|
+
await Promise.all(mine.map(async (e) => {
|
|
2777
|
+
try {
|
|
2778
|
+
await bundleManager.loadTools("cache:" + e.bundleUrl, e.bundleUrl, e.version, e.bundleHash);
|
|
2779
|
+
} catch {
|
|
2780
|
+
}
|
|
2781
|
+
}));
|
|
2782
|
+
return listDeviceTools().map((t) => t.name);
|
|
2783
|
+
} catch (err) {
|
|
2784
|
+
console.warn("[deviceTools] disk-cache recovery failed:", err?.message || String(err));
|
|
2785
|
+
return [];
|
|
2786
|
+
}
|
|
2787
|
+
}
|
|
2788
|
+
function resetDeviceToolLoader() {
|
|
2789
|
+
loadedOnce = false;
|
|
2790
|
+
}
|
|
2791
|
+
|
|
2792
|
+
export {
|
|
2793
|
+
__export,
|
|
2794
|
+
register,
|
|
2795
|
+
registerTools,
|
|
2796
|
+
registerService,
|
|
2797
|
+
PLATFORM_VOICE_CALLER,
|
|
2798
|
+
View,
|
|
2799
|
+
Text,
|
|
2800
|
+
ScrollView,
|
|
2801
|
+
FlatList,
|
|
2802
|
+
SectionList,
|
|
2803
|
+
Pressable,
|
|
2804
|
+
TouchableOpacity,
|
|
2805
|
+
TextInput,
|
|
2806
|
+
Image,
|
|
2807
|
+
ActivityIndicator,
|
|
2808
|
+
Modal,
|
|
2809
|
+
Switch,
|
|
2810
|
+
StyleSheet,
|
|
2811
|
+
Animated,
|
|
2812
|
+
Platform,
|
|
2813
|
+
RefreshControl,
|
|
2814
|
+
Card,
|
|
2815
|
+
Badge,
|
|
2816
|
+
TabBar,
|
|
2817
|
+
ListItem,
|
|
2818
|
+
SearchBar,
|
|
2819
|
+
EmptyState,
|
|
2820
|
+
ErrorState,
|
|
2821
|
+
LoadingState,
|
|
2822
|
+
ActionButton,
|
|
2823
|
+
setHapticsProvider,
|
|
2824
|
+
getHaptics,
|
|
2825
|
+
setPluginNativeProvider,
|
|
2826
|
+
getPluginNative,
|
|
2827
|
+
gatePluginNative,
|
|
2828
|
+
setCameraPreviewProvider,
|
|
2829
|
+
getCameraPreviewProvider,
|
|
2830
|
+
registerNativePlugin,
|
|
2831
|
+
unregisterNativePlugin,
|
|
2832
|
+
nativePluginRegistry,
|
|
2833
|
+
registerBundleModules,
|
|
2834
|
+
CameraPreview,
|
|
2835
|
+
setPluginThemeColors,
|
|
2836
|
+
usePluginTheme,
|
|
2837
|
+
useApi,
|
|
2838
|
+
PluginSDK,
|
|
2839
|
+
setDeviceStateProvider,
|
|
2840
|
+
gateDeviceState,
|
|
2841
|
+
implementedStateDomains,
|
|
2842
|
+
useNativeBridge,
|
|
2843
|
+
createNativeBridge,
|
|
2844
|
+
setDeviceToolContextProvider,
|
|
2845
|
+
registerDeviceTools,
|
|
2846
|
+
unregisterDeviceTools,
|
|
2847
|
+
hasDeviceTool,
|
|
2848
|
+
listDeviceTools,
|
|
2849
|
+
callDeviceTool,
|
|
2850
|
+
clearDeviceTools,
|
|
2851
|
+
supportsNative,
|
|
2852
|
+
getNativeComponentId,
|
|
2853
|
+
getNativeBundleUrl,
|
|
2854
|
+
getNativeBundleHash,
|
|
2855
|
+
supportsWebView,
|
|
2856
|
+
listPlugins,
|
|
2857
|
+
getPlugin,
|
|
2858
|
+
resolvePluginUrl,
|
|
2859
|
+
AdaOrb,
|
|
2860
|
+
SurfaceDrawer,
|
|
2861
|
+
SurfaceCard,
|
|
2862
|
+
SurfaceFullscreen,
|
|
2863
|
+
useSurfaceStore,
|
|
2864
|
+
isMountSurface,
|
|
2865
|
+
isInlineSurface,
|
|
2866
|
+
SurfaceDispatcher,
|
|
2867
|
+
MemoryBundleStorage,
|
|
2868
|
+
setBundleStorage,
|
|
2869
|
+
bundleManager,
|
|
2870
|
+
ensureDeviceToolsLoaded,
|
|
2871
|
+
resetDeviceToolLoader
|
|
2872
|
+
};
|
|
2873
|
+
//# sourceMappingURL=chunk-PKDQLNBL.mjs.map
|