@elizaos/plugin-messages 2.0.3-beta.5 → 2.0.3-beta.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/dist/components/MessagesAppView.d.ts +3 -0
  2. package/dist/components/MessagesAppView.d.ts.map +1 -0
  3. package/dist/components/MessagesAppView.helpers.d.ts +20 -0
  4. package/dist/components/MessagesAppView.helpers.d.ts.map +1 -0
  5. package/dist/components/MessagesAppView.helpers.js +56 -0
  6. package/dist/components/MessagesAppView.helpers.js.map +1 -0
  7. package/dist/components/MessagesAppView.interact.d.ts +2 -0
  8. package/dist/components/MessagesAppView.interact.d.ts.map +1 -0
  9. package/dist/components/MessagesAppView.interact.js +49 -0
  10. package/dist/components/MessagesAppView.interact.js.map +1 -0
  11. package/dist/components/MessagesAppView.js +642 -0
  12. package/dist/components/MessagesAppView.js.map +1 -0
  13. package/dist/components/MessagesSpatialView.d.ts +47 -0
  14. package/dist/components/MessagesSpatialView.d.ts.map +1 -0
  15. package/dist/components/MessagesSpatialView.js +152 -0
  16. package/dist/components/MessagesSpatialView.js.map +1 -0
  17. package/dist/components/MessagesView.d.ts +13 -0
  18. package/dist/components/MessagesView.d.ts.map +1 -0
  19. package/dist/components/MessagesView.js +144 -0
  20. package/dist/components/MessagesView.js.map +1 -0
  21. package/dist/components/messages-app.d.ts +5 -0
  22. package/dist/components/messages-app.d.ts.map +1 -0
  23. package/dist/components/messages-app.js +20 -0
  24. package/dist/components/messages-app.js.map +1 -0
  25. package/dist/components/messages-view-bundle.d.ts +3 -0
  26. package/dist/components/messages-view-bundle.d.ts.map +1 -0
  27. package/dist/components/messages-view-bundle.js +7 -0
  28. package/dist/components/messages-view-bundle.js.map +1 -0
  29. package/dist/index.d.ts +7 -0
  30. package/dist/index.d.ts.map +1 -0
  31. package/dist/index.js +20 -0
  32. package/dist/index.js.map +1 -0
  33. package/dist/plugin.d.ts +4 -0
  34. package/dist/plugin.d.ts.map +1 -0
  35. package/dist/plugin.js +29 -0
  36. package/dist/plugin.js.map +1 -0
  37. package/dist/register-terminal-view.d.ts +15 -0
  38. package/dist/register-terminal-view.d.ts.map +1 -0
  39. package/dist/register-terminal-view.js +28 -0
  40. package/dist/register-terminal-view.js.map +1 -0
  41. package/dist/register.d.ts +2 -0
  42. package/dist/register.d.ts.map +1 -0
  43. package/dist/register.js +10 -0
  44. package/dist/register.js.map +1 -0
  45. package/dist/ui.d.ts +4 -0
  46. package/dist/ui.d.ts.map +1 -0
  47. package/dist/ui.js +15 -0
  48. package/dist/ui.js.map +1 -0
  49. package/dist/views/bundle.js +338 -0
  50. package/dist/views/bundle.js.map +1 -0
  51. package/dist/views/dist-Cd2YtKy4.js +270 -0
  52. package/dist/views/dist-Cd2YtKy4.js.map +1 -0
  53. package/dist/views/web-BNoqOavR.js +90 -0
  54. package/dist/views/web-BNoqOavR.js.map +1 -0
  55. package/dist/views/web-DeLuzxfk.js +32 -0
  56. package/dist/views/web-DeLuzxfk.js.map +1 -0
  57. package/package.json +8 -8
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/MessagesAppView.tsx"],"sourcesContent":["import type { SmsMessageSummary } from \"@elizaos/capacitor-messages\";\nimport { Messages } from \"@elizaos/capacitor-messages\";\nimport { System, type SystemStatus } from \"@elizaos/capacitor-system\";\nimport { useAgentElement } from \"@elizaos/ui/agent-surface\";\nimport { consumePendingMessageRecipient } from \"@elizaos/ui/app-navigate-view\";\nimport { PermissionRecoveryCallout } from \"@elizaos/ui/components\";\nimport type { OverlayAppContext } from \"@elizaos/ui/components/apps/overlay-app-api\";\nimport { Button } from \"@elizaos/ui/components/ui/button\";\nimport { Input } from \"@elizaos/ui/components/ui/input\";\nimport { Textarea } from \"@elizaos/ui/components/ui/textarea\";\nimport {\n ArrowLeft,\n ChevronLeft,\n MessageSquareText,\n Plus,\n Send,\n ShieldCheck,\n} from \"lucide-react\";\nimport {\n type ChangeEvent,\n memo,\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useState,\n} from \"react\";\nimport {\n buildThreads,\n smsRole,\n type ThreadSummary,\n} from \"./MessagesAppView.helpers.js\";\n\nconst SENT_SMS_TYPE = 2;\n\nfunction formatTime(epochMs: number): string {\n const date = new Date(epochMs);\n if (Number.isNaN(date.getTime())) return \"\";\n const now = new Date();\n const sameDay =\n date.getFullYear() === now.getFullYear() &&\n date.getMonth() === now.getMonth() &&\n date.getDate() === now.getDate();\n if (sameDay) {\n return date.toLocaleTimeString(undefined, {\n hour: \"2-digit\",\n minute: \"2-digit\",\n });\n }\n return date.toLocaleDateString(undefined, {\n month: \"short\",\n day: \"numeric\",\n });\n}\n\nfunction threadInitial(address: string): string {\n const trimmed = address.trim();\n if (trimmed.length === 0) return \"#\";\n const firstLetter = trimmed.split(\"\").find((ch) => /[a-z]/i.test(ch));\n if (firstLetter) return firstLetter.toUpperCase();\n const firstDigit = trimmed.replace(/[^0-9]/g, \"\").slice(-1);\n return firstDigit || \"#\";\n}\n\nfunction isMessagesPermissionError(message: string): boolean {\n const normalized = message.toLowerCase();\n return (\n normalized.includes(\"permission\") ||\n normalized.includes(\"denied\") ||\n normalized.includes(\"access is needed\") ||\n normalized.includes(\"read_sms\") ||\n normalized.includes(\"send_sms\")\n );\n}\n\nfunction StatChip({\n icon,\n label,\n accent = false,\n}: {\n icon?: ReactNode;\n label: string;\n accent?: boolean;\n}) {\n return (\n <span\n className=\"inline-flex items-center gap-1.5 px-1 py-1 text-xs font-medium\"\n style={{\n color: accent ? \"var(--accent)\" : \"var(--muted)\",\n }}\n >\n {icon ? (\n <span\n aria-hidden\n className=\"flex h-3.5 w-3.5 items-center justify-center\"\n >\n {icon}\n </span>\n ) : null}\n {label}\n </span>\n );\n}\n\nfunction ChatBubblesMotif() {\n return (\n <svg width=\"96\" height=\"96\" viewBox=\"0 0 96 96\" fill=\"none\" role=\"img\">\n <title>Chat bubbles</title>\n <rect\n x=\"10\"\n y=\"20\"\n width=\"56\"\n height=\"34\"\n rx=\"12\"\n fill=\"var(--accent-subtle)\"\n stroke=\"var(--accent)\"\n strokeWidth=\"2\"\n />\n <path d=\"M24 54 L24 64 L36 54 Z\" fill=\"var(--accent-subtle)\" />\n <rect\n x=\"38\"\n y=\"44\"\n width=\"48\"\n height=\"30\"\n rx=\"11\"\n fill=\"var(--surface)\"\n stroke=\"var(--border)\"\n strokeWidth=\"2\"\n />\n <path d=\"M72 74 L72 82 L62 74 Z\" fill=\"var(--surface)\" />\n <circle cx=\"24\" cy=\"37\" r=\"2.5\" fill=\"var(--accent)\" />\n <circle cx=\"34\" cy=\"37\" r=\"2.5\" fill=\"var(--accent)\" />\n <circle cx=\"44\" cy=\"37\" r=\"2.5\" fill=\"var(--accent)\" />\n <circle cx=\"56\" cy=\"59\" r=\"2.5\" fill=\"var(--muted)\" />\n <circle cx=\"66\" cy=\"59\" r=\"2.5\" fill=\"var(--muted)\" />\n <circle cx=\"76\" cy=\"59\" r=\"2.5\" fill=\"var(--muted)\" />\n </svg>\n );\n}\n\nconst MessagesThreadButton = memo(function MessagesThreadButton({\n thread,\n selected,\n onOpen,\n}: {\n thread: ThreadSummary;\n selected: boolean;\n onOpen: (thread: ThreadSummary) => void;\n}) {\n const { ref, agentProps } = useAgentElement<HTMLButtonElement>({\n id: `thread-${thread.id}`,\n role: \"list-item\",\n label: thread.address || \"Unknown\",\n group: \"messages-threads\",\n status: selected ? \"active\" : undefined,\n description: `Open the SMS thread with ${thread.address || \"Unknown\"}`,\n });\n return (\n <button\n ref={ref}\n {...agentProps}\n type=\"button\"\n onClick={() => onOpen(thread)}\n aria-current={selected ? \"true\" : undefined}\n className=\"flex w-full items-start gap-3 px-3 py-2.5 text-left transition-colors focus:outline-none\"\n style={selected ? { background: \"var(--accent-subtle)\" } : undefined}\n data-testid={`messages-thread-${thread.id}`}\n >\n <span\n className=\"flex h-10 w-10 shrink-0 items-center justify-center rounded-full text-sm font-semibold\"\n style={{ background: \"var(--accent-subtle)\", color: \"var(--accent)\" }}\n >\n {threadInitial(thread.address)}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex items-center justify-between gap-2\">\n <span className=\"truncate text-sm font-semibold text-txt\">\n {thread.address || \"Unknown\"}\n </span>\n <span className=\"shrink-0 px-1 py-0.5 text-2xs text-muted\">\n {formatTime(thread.lastMessage.date)}\n </span>\n </span>\n <span className=\"mt-1 line-clamp-2 text-xs text-muted\">\n {thread.lastMessage.body}\n </span>\n </span>\n {thread.unreadCount > 0 ? (\n <span\n className=\"rounded-full px-1.5 py-0.5 text-2xs font-semibold\"\n style={{\n background: \"var(--accent)\",\n color: \"var(--accent-foreground)\",\n }}\n >\n {thread.unreadCount}\n </span>\n ) : null}\n </button>\n );\n});\n\nexport function MessagesAppView({ exitToApps, t }: OverlayAppContext) {\n const [messages, setMessages] = useState<SmsMessageSummary[]>([]);\n const [systemStatus, setSystemStatus] = useState<SystemStatus | null>(null);\n const [selectedThreadId, setSelectedThreadId] = useState<string | null>(null);\n const [composeAddress, setComposeAddress] = useState(\"\");\n const [composeBody, setComposeBody] = useState(\"\");\n const [loading, setLoading] = useState(true);\n const [sending, setSending] = useState(false);\n const [requestingRole, setRequestingRole] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [notice, setNotice] = useState<string | null>(null);\n const [showComposer, setShowComposer] = useState(false);\n\n const refresh = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n // System status (default-SMS role) loads regardless so the role banner\n // always shows. Reading/sending SMS via the bridge needs READ/SEND_SMS —\n // request it on first open (feature-gated; idempotent). Tolerates older\n // bridges without the request path by falling through to listMessages.\n const statusResult = await System.getStatus().catch(() => null);\n setSystemStatus(statusResult);\n const perm = await Messages.requestPermissions().catch(() => null);\n if (perm && perm.sms !== \"granted\") {\n setMessages([]);\n setError(\n \"SMS access is needed to read and send messages. Grant it in your device settings, then retry.\",\n );\n return;\n }\n const messageResult = await Messages.listMessages({ limit: 200 });\n setMessages(messageResult.messages);\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n setMessages([]);\n } finally {\n setLoading(false);\n }\n }, []);\n\n // Load on mount, then quietly poll so newly received SMS surface without a\n // manual control. The bridge has no push channel, so a 20s interval keeps the\n // thread list fresh; it is cleared on unmount.\n useEffect(() => {\n void refresh();\n const interval = setInterval(() => void refresh(), 20000);\n return () => clearInterval(interval);\n }, [refresh]);\n\n // Seed the composer from a cross-view handoff (e.g. a Contacts \"Message\"\n // control that navigated here with a number). Single-shot: the recipient is\n // consumed so a later plain navigation to Messages does not re-seed a stale\n // \"To\" field.\n useEffect(() => {\n const pending = consumePendingMessageRecipient();\n if (pending) {\n setSelectedThreadId(null);\n setComposeAddress(pending);\n setComposeBody(\"\");\n setShowComposer(true);\n setNotice(null);\n setError(null);\n }\n }, []);\n\n const threads = useMemo(() => buildThreads(messages), [messages]);\n const selectedThread = useMemo(\n () => threads.find((thread) => thread.id === selectedThreadId) ?? null,\n [selectedThreadId, threads],\n );\n const currentSmsRole = smsRole(systemStatus);\n const ownsSmsRole = currentSmsRole?.held === true;\n const unreadTotal = threads.reduce(\n (total, thread) => total + thread.unreadCount,\n 0,\n );\n const canSend =\n composeAddress.trim().length > 0 &&\n composeBody.trim().length > 0 &&\n !sending;\n\n const openThread = useCallback((thread: ThreadSummary) => {\n setSelectedThreadId(thread.id);\n setComposeAddress(thread.address);\n setShowComposer(true);\n setNotice(null);\n setError(null);\n }, []);\n\n const openNewComposer = useCallback(() => {\n setSelectedThreadId(null);\n setComposeAddress(\"\");\n setComposeBody(\"\");\n setShowComposer(true);\n setNotice(null);\n setError(null);\n }, []);\n\n const backToThreads = useCallback(() => {\n setShowComposer(false);\n setSelectedThreadId(null);\n }, []);\n\n const requestSmsRole = useCallback(async () => {\n setRequestingRole(true);\n setError(null);\n try {\n await System.requestRole({ role: \"sms\" });\n const next = await System.getStatus();\n setSystemStatus(next);\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setRequestingRole(false);\n }\n }, []);\n\n const send = useCallback(async () => {\n if (!canSend) return;\n setSending(true);\n setError(null);\n setNotice(null);\n try {\n await Messages.sendSms({\n address: composeAddress.trim(),\n body: composeBody.trim(),\n });\n setComposeBody(\"\");\n setNotice(\"Message sent.\");\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setSending(false);\n }\n }, [canSend, composeAddress, composeBody, refresh]);\n\n const title = showComposer\n ? selectedThread?.address ||\n t(\"messages.new\", { defaultValue: \"New message\" })\n : t(\"messages.title\", { defaultValue: \"Messages\" });\n\n const backLabel = showComposer\n ? t(\"messages.backToThreads\", { defaultValue: \"Back to threads\" })\n : t(\"nav.back\", { defaultValue: \"Back\" });\n const back = useAgentElement<HTMLButtonElement>({\n id: \"action-back\",\n role: \"button\",\n label: backLabel,\n group: \"messages-header\",\n description: showComposer\n ? \"Return from the composer to the thread list\"\n : \"Leave Messages and return to the apps grid\",\n });\n const newMessage = useAgentElement<HTMLButtonElement>({\n id: \"action-new-message\",\n role: \"button\",\n label: t(\"messages.newShort\", { defaultValue: \"New\" }),\n group: \"messages-header\",\n description: \"Open the composer to start a new text message\",\n });\n const emptyNewMessage = useAgentElement<HTMLButtonElement>({\n id: \"action-empty-new-message\",\n role: \"button\",\n label: t(\"messages.new\", { defaultValue: \"New message\" }),\n group: \"messages-threads\",\n description: \"Start a new text message from the empty thread list\",\n });\n const requestRole = useAgentElement<HTMLButtonElement>({\n id: \"action-set-default-sms\",\n role: \"button\",\n label: t(\"messages.setDefaultSms\", { defaultValue: \"Set default SMS\" }),\n group: \"messages-sms-role\",\n description: \"Request the Android default SMS role for this app\",\n });\n const addressInput = useAgentElement<HTMLInputElement>({\n id: \"input-recipient\",\n role: \"text-input\",\n label: t(\"messages.to\", { defaultValue: \"To\" }),\n group: \"messages-composer\",\n description: \"Recipient phone number for the outgoing text message\",\n getValue: () => composeAddress,\n onFill: setComposeAddress,\n });\n const bodyInput = useAgentElement<HTMLTextAreaElement>({\n id: \"input-message-body\",\n role: \"textarea\",\n label: t(\"messages.placeholder\", { defaultValue: \"Message\" }),\n group: \"messages-composer\",\n description: \"Body text of the outgoing message\",\n getValue: () => composeBody,\n onFill: setComposeBody,\n });\n const sendButton = useAgentElement<HTMLButtonElement>({\n id: \"action-send\",\n role: \"button\",\n label: t(\"messages.send\", { defaultValue: \"Send\" }),\n group: \"messages-composer\",\n description: \"Send the composed text message\",\n });\n\n return (\n <div\n data-testid=\"messages-shell\"\n className=\"fixed inset-0 z-50 flex h-[100vh] flex-col overflow-hidden bg-bg pb-[var(--safe-area-bottom,0px)] pl-[var(--safe-area-left,0px)] pr-[var(--safe-area-right,0px)] pt-[var(--safe-area-top,0px)] supports-[height:100dvh]:h-[100dvh]\"\n >\n <header className=\"flex shrink-0 items-center justify-between gap-3 px-3 py-2\">\n <div className=\"flex min-w-0 items-center gap-3\">\n <Button\n ref={back.ref}\n {...back.agentProps}\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-9 w-9 shrink-0 text-muted hover:text-txt\"\n onClick={showComposer ? backToThreads : exitToApps}\n aria-label={backLabel}\n >\n {showComposer ? (\n <ChevronLeft className=\"h-4 w-4\" />\n ) : (\n <ArrowLeft className=\"h-4 w-4\" />\n )}\n </Button>\n <div className=\"min-w-0\">\n <h1 className=\"truncate text-base font-semibold text-txt\">\n {title}\n </h1>\n <p className=\"sr-only truncate text-xs text-muted\">\n {ownsSmsRole\n ? t(\"messages.smsReady\", { defaultValue: \"Default SMS app\" })\n : t(\"messages.smsBridge\", {\n defaultValue: \"Android SMS bridge\",\n })}\n </p>\n </div>\n </div>\n <div className=\"flex shrink-0 items-center gap-2\">\n <Button\n ref={newMessage.ref}\n {...newMessage.agentProps}\n variant=\"default\"\n size=\"sm\"\n className=\"px-3\"\n onClick={openNewComposer}\n data-testid=\"messages-new\"\n >\n <Plus className=\"mr-1.5 h-4 w-4\" />\n {t(\"messages.newShort\", { defaultValue: \"New\" })}\n </Button>\n </div>\n </header>\n\n {currentSmsRole && !ownsSmsRole ? (\n <div className=\"shrink-0 px-3 py-2\">\n <div className=\"mx-auto flex max-w-5xl flex-col gap-3 md:flex-row md:items-center md:justify-between\">\n <div className=\"flex items-start gap-3 text-sm\">\n <ShieldCheck className=\"mt-0.5 h-4 w-4 shrink-0 text-muted\" />\n <div>\n <div className=\"font-medium text-txt\">\n {t(\"messages.smsRoleTitle\", {\n defaultValue: \"SMS role\",\n })}\n </div>\n <div className=\"sr-only text-xs text-muted\">\n {t(\"messages.smsRoleBody\", {\n defaultValue:\n \"Reading and sending real SMS requires Android to grant the default SMS role.\",\n })}\n </div>\n </div>\n </div>\n <Button\n ref={requestRole.ref}\n {...requestRole.agentProps}\n variant=\"outline\"\n size=\"sm\"\n onClick={requestSmsRole}\n disabled={requestingRole}\n data-testid=\"messages-request-sms-role\"\n >\n {requestingRole\n ? t(\"messages.requesting\", { defaultValue: \"Requesting…\" })\n : t(\"messages.setDefaultSms\", {\n defaultValue: \"Set default SMS\",\n })}\n </Button>\n </div>\n </div>\n ) : null}\n\n {error && isMessagesPermissionError(error) ? (\n <div className=\"shrink-0 px-4 pt-3\">\n <PermissionRecoveryCallout\n permission=\"messages\"\n title={t(\"messages.permissionTitle\", {\n defaultValue: \"SMS access is off\",\n })}\n description={error}\n onRetry={refresh}\n retryLabel={t(\"actions.retry\", { defaultValue: \"Try again\" })}\n className=\"mx-auto max-w-5xl\"\n testId=\"messages-permission-callout\"\n />\n </div>\n ) : error || notice ? (\n <div className=\"shrink-0 px-4 pt-3\">\n <div\n role={error ? \"alert\" : \"status\"}\n className={`mx-auto max-w-5xl px-1 py-2 text-sm ${\n error ? \"text-danger\" : \"text-muted\"\n }`}\n >\n {error ?? notice}\n </div>\n </div>\n ) : null}\n\n <main className=\"flex min-h-0 flex-1 flex-col\">\n <section\n className={`min-h-0 flex-1 flex-col ${\n showComposer ? \"hidden\" : \"flex\"\n }`}\n data-testid=\"messages-thread-list\"\n >\n {loading && threads.length === 0 ? (\n <div className=\"flex flex-1 items-center justify-center px-4 text-sm text-muted\">\n {t(\"messages.loading\", { defaultValue: \"Loading\" })}\n </div>\n ) : threads.length === 0 ? (\n <div className=\"flex flex-1 flex-col items-center justify-center px-6 pb-32 text-center\">\n <span className=\"flex h-20 w-20 items-center justify-center\">\n <ChatBubblesMotif />\n </span>\n <h2 className=\"mt-5 text-base font-semibold text-txt\">\n {t(\"messages.empty.title\", { defaultValue: \"None\" })}\n </h2>\n <p className=\"sr-only mt-1 max-w-xs text-sm text-muted\">\n {t(\"messages.empty.body\", {\n defaultValue:\n \"Start a conversation — texts you send and receive show up here.\",\n })}\n </p>\n <div className=\"sr-only mt-4 flex flex-wrap items-center justify-center gap-2\">\n <StatChip\n label={t(\"messages.threadCount\", {\n defaultValue: \"0 threads\",\n })}\n />\n </div>\n <button\n ref={emptyNewMessage.ref}\n {...emptyNewMessage.agentProps}\n type=\"button\"\n onClick={openNewComposer}\n className=\"mt-6 inline-flex items-center gap-2 px-5 py-2.5 text-sm font-semibold transition-colors\"\n style={{\n background: \"var(--accent)\",\n color: \"var(--accent-foreground)\",\n }}\n onMouseEnter={(e) => {\n e.currentTarget.style.background = \"var(--accent-hover)\";\n }}\n onMouseLeave={(e) => {\n e.currentTarget.style.background = \"var(--accent)\";\n }}\n >\n <Plus className=\"h-4 w-4\" />\n {t(\"messages.new\", { defaultValue: \"New message\" })}\n </button>\n </div>\n ) : (\n <div className=\"chat-native-scrollbar min-h-0 flex-1 overflow-y-auto pb-32\">\n <div className=\"flex flex-wrap items-center gap-3 px-3 py-2\">\n <StatChip\n label={t(\"messages.threadCountN\", {\n defaultValue: `${threads.length} threads`,\n })}\n />\n <StatChip\n label={t(\"messages.unreadCountN\", {\n defaultValue: `${unreadTotal} unread`,\n })}\n accent={unreadTotal > 0}\n />\n </div>\n <div>\n {threads.map((thread) => (\n <MessagesThreadButton\n key={thread.id}\n thread={thread}\n selected={thread.id === selectedThreadId}\n onOpen={openThread}\n />\n ))}\n </div>\n </div>\n )}\n </section>\n\n <section\n className={`min-h-0 flex-1 flex-col ${showComposer ? \"flex\" : \"hidden\"}`}\n data-testid=\"messages-composer-panel\"\n >\n {showComposer ? (\n <>\n <div className=\"shrink-0 px-3 py-2\">\n <label\n htmlFor=\"messages-compose-address\"\n className=\"text-xs text-muted\"\n >\n {t(\"messages.to\", { defaultValue: \"To\" })}\n </label>\n <Input\n ref={addressInput.ref}\n {...addressInput.agentProps}\n id=\"messages-compose-address\"\n value={composeAddress}\n onChange={(event: ChangeEvent<HTMLInputElement>) =>\n setComposeAddress(event.target.value)\n }\n placeholder=\"+1 555 123 4567\"\n inputMode=\"tel\"\n className=\"mt-1\"\n data-testid=\"messages-compose-address\"\n />\n </div>\n\n <div className=\"chat-native-scrollbar flex-1 overflow-y-auto px-4 py-4\">\n {selectedThread ? (\n <div className=\"mx-auto flex max-w-2xl flex-col gap-2\">\n {selectedThread.messages.map((message) => {\n const sent = message.type === SENT_SMS_TYPE;\n return (\n <div\n key={message.id}\n className={`flex ${sent ? \"justify-end\" : \"justify-start\"}`}\n >\n <div\n className=\"max-w-[78%] rounded-2xl px-3 py-2\"\n style={\n sent\n ? {\n background: \"var(--accent)\",\n color: \"var(--accent-foreground)\",\n }\n : {\n background: \"var(--surface)\",\n color: \"var(--text)\",\n }\n }\n >\n <div className=\"whitespace-pre-wrap break-words text-sm\">\n {message.body}\n </div>\n <div\n className=\"mt-1 text-right text-2xs\"\n style={{\n opacity: 0.7,\n color: sent\n ? \"var(--accent-foreground)\"\n : \"var(--muted)\",\n }}\n >\n {formatTime(message.date)}\n </div>\n </div>\n </div>\n );\n })}\n </div>\n ) : (\n <div className=\"flex h-full items-center justify-center text-center\">\n <div className=\"max-w-sm\">\n <MessageSquareText className=\"mx-auto h-12 w-12 text-muted\" />\n <div className=\"mt-3 text-sm font-medium text-txt\">\n {t(\"messages.composeTitle\", {\n defaultValue: \"Start a text message\",\n })}\n </div>\n <p className=\"sr-only mt-1 text-xs text-muted\">\n {t(\"messages.composeBody\", {\n defaultValue:\n \"Enter a phone number and message body. Android handles carrier delivery through the SMS bridge.\",\n })}\n </p>\n </div>\n </div>\n )}\n </div>\n\n <div className=\"shrink-0 bg-bg/95 px-3 py-2\">\n <div className=\"mx-auto flex max-w-2xl items-end gap-2\">\n <Textarea\n ref={bodyInput.ref}\n {...bodyInput.agentProps}\n value={composeBody}\n onChange={(event) => setComposeBody(event.target.value)}\n placeholder={t(\"messages.placeholder\", {\n defaultValue: \"Message\",\n })}\n className=\"min-h-[44px] resize-none\"\n rows={2}\n data-testid=\"messages-compose-body\"\n />\n <Button\n ref={sendButton.ref}\n {...sendButton.agentProps}\n size=\"icon\"\n className=\"h-11 w-11 shrink-0\"\n onClick={() => void send()}\n disabled={!canSend}\n aria-label={t(\"messages.send\", { defaultValue: \"Send\" })}\n data-testid=\"messages-send\"\n >\n <Send className=\"h-4 w-4\" />\n </Button>\n </div>\n </div>\n </>\n ) : (\n <div className=\"flex flex-1 items-center justify-center px-6 text-center\">\n <div className=\"max-w-sm\">\n <MessageSquareText className=\"mx-auto h-12 w-12 text-muted\" />\n <div className=\"mt-3 text-sm font-medium text-txt\">\n {t(\"messages.selectTitle\", {\n defaultValue: \"Select a conversation\",\n })}\n </div>\n <p className=\"sr-only mt-1 text-xs text-muted\">\n {t(\"messages.selectBody\", {\n defaultValue:\n \"Review existing SMS threads or start a new text message.\",\n })}\n </p>\n </div>\n </div>\n )}\n </section>\n </main>\n </div>\n );\n}\n"],"mappings":"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","names":["MessagesThreadButton"]}
@@ -0,0 +1,47 @@
1
+ /**
2
+ * MessagesSpatialView - the SMS messaging surface authored once with the
3
+ * spatial vocabulary, so it renders correctly wherever it is displayed:
4
+ *
5
+ * - GUI / XR - mounted in `<SpatialSurface>` (DOM; XR scales up).
6
+ * - TUI - rendered to real terminal lines by the agent terminal, via
7
+ * `registerSpatialTerminalView` (see `register-terminal-view.tsx`).
8
+ *
9
+ * It is purely presentational (a snapshot + an action callback in, primitives
10
+ * out) and imports only the cross-modality primitives plus a type-only view of
11
+ * the native SMS summary, so it is safe to render in the Node agent process
12
+ * where the terminal lives (no Capacitor runtime import).
13
+ */
14
+ import type { SmsMessageSummary } from "@elizaos/capacitor-messages";
15
+ /** A grouped SMS conversation, sorted with its newest message last. */
16
+ export interface MessagesThreadSummary {
17
+ id: string;
18
+ address: string;
19
+ messages: SmsMessageSummary[];
20
+ lastMessage: SmsMessageSummary;
21
+ unreadCount: number;
22
+ }
23
+ export interface MessagesSnapshot {
24
+ threads: MessagesThreadSummary[];
25
+ selectedThreadId: string | null;
26
+ composeAddress: string;
27
+ composeBody: string;
28
+ /** This device holds the Android default-SMS role (full inbox). */
29
+ ownsSmsRole: boolean;
30
+ /** When not held, the package name of whichever app holds the role. */
31
+ smsRoleHolder: string | null;
32
+ loading?: boolean;
33
+ error?: string | null;
34
+ }
35
+ export interface MessagesSpatialViewProps {
36
+ snapshot: MessagesSnapshot;
37
+ /**
38
+ * Dispatched action ids: `open-thread:<id>` (open a conversation), `send`,
39
+ * `request-sms-role`, `refresh`, `compose-address:<value>`,
40
+ * `compose-body:<value>`.
41
+ */
42
+ onAction?: (action: string) => void;
43
+ }
44
+ export declare function MessagesSpatialView({ snapshot, onAction, }: MessagesSpatialViewProps): import("react/jsx-runtime").JSX.Element;
45
+ /** Group a flat SMS list into threads, newest-last per thread, newest first. */
46
+ export declare function buildMessagesThreads(messages: SmsMessageSummary[]): MessagesThreadSummary[];
47
+ //# sourceMappingURL=MessagesSpatialView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessagesSpatialView.d.ts","sourceRoot":"","sources":["../../src/components/MessagesSpatialView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAgBrE,uEAAuE;AACvE,MAAM,WAAW,qBAAqB;IACpC,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,iBAAiB,EAAE,CAAC;IAC9B,WAAW,EAAE,iBAAiB,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,qBAAqB,EAAE,CAAC;IACjC,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,cAAc,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,WAAW,EAAE,OAAO,CAAC;IACrB,uEAAuE;IACvE,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACvB;AAyBD,MAAM,WAAW,wBAAwB;IACvC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B;;;;OAIG;IACH,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;CACrC;AAED,wBAAgB,mBAAmB,CAAC,EAClC,QAAQ,EACR,QAAQ,GACT,EAAE,wBAAwB,2CA8H1B;AAED,gFAAgF;AAChF,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,iBAAiB,EAAE,GAC5B,qBAAqB,EAAE,CAuBzB"}
@@ -0,0 +1,152 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import {
3
+ Button,
4
+ Card,
5
+ Divider,
6
+ Field,
7
+ HStack,
8
+ List,
9
+ Text,
10
+ VStack
11
+ } from "@elizaos/ui/spatial";
12
+ const INBOUND_SMS_TYPE = 1;
13
+ const SENT_SMS_TYPE = 2;
14
+ function messageDirection(type) {
15
+ return type === SENT_SMS_TYPE ? "out" : "in";
16
+ }
17
+ function directionMark(type) {
18
+ return messageDirection(type) === "out" ? ">" : "<";
19
+ }
20
+ function directionTone(type) {
21
+ return messageDirection(type) === "out" ? "primary" : "default";
22
+ }
23
+ function roleLabel(snapshot) {
24
+ if (snapshot.ownsSmsRole) return "sms-default";
25
+ if (snapshot.smsRoleHolder) return `bridge:${snapshot.smsRoleHolder}`;
26
+ return "bridge-only";
27
+ }
28
+ function roleTone(snapshot) {
29
+ return snapshot.ownsSmsRole ? "success" : "warning";
30
+ }
31
+ function MessagesSpatialView({
32
+ snapshot,
33
+ onAction
34
+ }) {
35
+ const dispatch = (action) => () => onAction?.(action);
36
+ const selectedThread = snapshot.threads.find((t) => t.id === snapshot.selectedThreadId) ?? null;
37
+ const canSend = snapshot.composeAddress.trim().length > 0 && snapshot.composeBody.trim().length > 0;
38
+ return /* @__PURE__ */ jsxs(Card, { gap: 1, padding: 1, children: [
39
+ /* @__PURE__ */ jsxs(HStack, { gap: 1, align: "center", children: [
40
+ /* @__PURE__ */ jsx(Text, { style: "caption", tone: roleTone(snapshot), grow: 1, children: roleLabel(snapshot) }),
41
+ /* @__PURE__ */ jsx(Text, { style: "caption", tone: "muted", children: snapshot.loading ? "loading" : `${snapshot.threads.length} threads` })
42
+ ] }),
43
+ snapshot.error ? /* @__PURE__ */ jsx(Text, { tone: "danger", style: "caption", children: snapshot.error }) : null,
44
+ !snapshot.ownsSmsRole ? /* @__PURE__ */ jsx(
45
+ Button,
46
+ {
47
+ variant: "outline",
48
+ tone: "warning",
49
+ agent: "request-sms-role",
50
+ onPress: dispatch("request-sms-role"),
51
+ children: "Set default SMS"
52
+ }
53
+ ) : null,
54
+ /* @__PURE__ */ jsx(Divider, { label: "threads" }),
55
+ snapshot.threads.length === 0 ? /* @__PURE__ */ jsx(Text, { tone: "muted", align: "center", style: "caption", children: "None" }) : /* @__PURE__ */ jsx(List, { gap: 0, children: snapshot.threads.slice(0, 8).map((thread) => {
56
+ const selected = thread.id === snapshot.selectedThreadId;
57
+ return /* @__PURE__ */ jsxs(HStack, { gap: 1, align: "center", children: [
58
+ /* @__PURE__ */ jsx(Text, { tone: directionTone(thread.lastMessage.type), children: directionMark(thread.lastMessage.type) }),
59
+ /* @__PURE__ */ jsxs(VStack, { gap: 0, grow: 1, children: [
60
+ /* @__PURE__ */ jsx(Text, { bold: true, wrap: false, children: thread.address || "Unknown" }),
61
+ /* @__PURE__ */ jsx(Text, { style: "caption", tone: "muted", wrap: false, children: thread.lastMessage.body })
62
+ ] }),
63
+ thread.unreadCount > 0 ? /* @__PURE__ */ jsx(Text, { style: "caption", tone: "primary", children: String(thread.unreadCount) }) : null,
64
+ /* @__PURE__ */ jsx(
65
+ Button,
66
+ {
67
+ variant: selected ? "solid" : "ghost",
68
+ tone: "primary",
69
+ agent: `open-thread-${thread.id}`,
70
+ onPress: dispatch(`open-thread:${thread.id}`),
71
+ children: "Open"
72
+ }
73
+ )
74
+ ] }, thread.id);
75
+ }) }),
76
+ /* @__PURE__ */ jsx(Divider, { label: selectedThread ? selectedThread.address : "compose" }),
77
+ selectedThread ? /* @__PURE__ */ jsx(List, { gap: 0, children: selectedThread.messages.slice(-6).map((message) => /* @__PURE__ */ jsxs(HStack, { gap: 1, align: "start", children: [
78
+ /* @__PURE__ */ jsx(Text, { tone: directionTone(message.type), style: "caption", children: messageDirection(message.type) }),
79
+ /* @__PURE__ */ jsx(Text, { grow: 1, children: message.body })
80
+ ] }, message.id)) }) : null,
81
+ /* @__PURE__ */ jsx(
82
+ Field,
83
+ {
84
+ label: "To",
85
+ value: snapshot.composeAddress,
86
+ placeholder: "phone number",
87
+ agent: "compose-address",
88
+ onChange: (value) => onAction?.(`compose-address:${value}`)
89
+ }
90
+ ),
91
+ /* @__PURE__ */ jsx(
92
+ Field,
93
+ {
94
+ label: "Body",
95
+ kind: "textarea",
96
+ value: snapshot.composeBody,
97
+ placeholder: "message",
98
+ agent: "compose-body",
99
+ onChange: (value) => onAction?.(`compose-body:${value}`)
100
+ }
101
+ ),
102
+ /* @__PURE__ */ jsxs(HStack, { gap: 1, wrap: true, children: [
103
+ /* @__PURE__ */ jsx(
104
+ Button,
105
+ {
106
+ grow: 1,
107
+ disabled: !canSend,
108
+ agent: "send",
109
+ onPress: dispatch("send"),
110
+ children: "Send"
111
+ }
112
+ ),
113
+ /* @__PURE__ */ jsx(
114
+ Button,
115
+ {
116
+ variant: "outline",
117
+ tone: "default",
118
+ agent: "refresh",
119
+ onPress: dispatch("refresh"),
120
+ children: "Refresh"
121
+ }
122
+ )
123
+ ] })
124
+ ] });
125
+ }
126
+ function buildMessagesThreads(messages) {
127
+ const byThread = /* @__PURE__ */ new Map();
128
+ for (const message of messages) {
129
+ const key = message.threadId || message.address || message.id;
130
+ const list = byThread.get(key) ?? [];
131
+ list.push(message);
132
+ byThread.set(key, list);
133
+ }
134
+ return Array.from(byThread.entries()).map(([id, threadMessages]) => {
135
+ const sorted = [...threadMessages].sort((a, b) => a.date - b.date);
136
+ const lastMessage = sorted[sorted.length - 1];
137
+ return {
138
+ id,
139
+ address: lastMessage.address,
140
+ messages: sorted,
141
+ lastMessage,
142
+ unreadCount: sorted.filter(
143
+ (m) => !m.read && m.type === INBOUND_SMS_TYPE
144
+ ).length
145
+ };
146
+ }).sort((a, b) => b.lastMessage.date - a.lastMessage.date);
147
+ }
148
+ export {
149
+ MessagesSpatialView,
150
+ buildMessagesThreads
151
+ };
152
+ //# sourceMappingURL=MessagesSpatialView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/MessagesSpatialView.tsx"],"sourcesContent":["/**\n * MessagesSpatialView - the SMS messaging surface authored once with the\n * spatial vocabulary, so it renders correctly wherever it is displayed:\n *\n * - GUI / XR - mounted in `<SpatialSurface>` (DOM; XR scales up).\n * - TUI - rendered to real terminal lines by the agent terminal, via\n * `registerSpatialTerminalView` (see `register-terminal-view.tsx`).\n *\n * It is purely presentational (a snapshot + an action callback in, primitives\n * out) and imports only the cross-modality primitives plus a type-only view of\n * the native SMS summary, so it is safe to render in the Node agent process\n * where the terminal lives (no Capacitor runtime import).\n */\n\nimport type { SmsMessageSummary } from \"@elizaos/capacitor-messages\";\nimport {\n Button,\n Card,\n Divider,\n Field,\n HStack,\n List,\n type SpatialTone,\n Text,\n VStack,\n} from \"@elizaos/ui/spatial\";\n\nconst INBOUND_SMS_TYPE = 1;\nconst SENT_SMS_TYPE = 2;\n\n/** A grouped SMS conversation, sorted with its newest message last. */\nexport interface MessagesThreadSummary {\n id: string;\n address: string;\n messages: SmsMessageSummary[];\n lastMessage: SmsMessageSummary;\n unreadCount: number;\n}\n\nexport interface MessagesSnapshot {\n threads: MessagesThreadSummary[];\n selectedThreadId: string | null;\n composeAddress: string;\n composeBody: string;\n /** This device holds the Android default-SMS role (full inbox). */\n ownsSmsRole: boolean;\n /** When not held, the package name of whichever app holds the role. */\n smsRoleHolder: string | null;\n loading?: boolean;\n error?: string | null;\n}\n\n/** in/out marker for a single message, by Android SMS type discriminant. */\nfunction messageDirection(type: number): \"in\" | \"out\" {\n return type === SENT_SMS_TYPE ? \"out\" : \"in\";\n}\n\nfunction directionMark(type: number): string {\n return messageDirection(type) === \"out\" ? \">\" : \"<\";\n}\n\nfunction directionTone(type: number): SpatialTone {\n return messageDirection(type) === \"out\" ? \"primary\" : \"default\";\n}\n\nfunction roleLabel(snapshot: MessagesSnapshot): string {\n if (snapshot.ownsSmsRole) return \"sms-default\";\n if (snapshot.smsRoleHolder) return `bridge:${snapshot.smsRoleHolder}`;\n return \"bridge-only\";\n}\n\nfunction roleTone(snapshot: MessagesSnapshot): SpatialTone {\n return snapshot.ownsSmsRole ? \"success\" : \"warning\";\n}\n\nexport interface MessagesSpatialViewProps {\n snapshot: MessagesSnapshot;\n /**\n * Dispatched action ids: `open-thread:<id>` (open a conversation), `send`,\n * `request-sms-role`, `refresh`, `compose-address:<value>`,\n * `compose-body:<value>`.\n */\n onAction?: (action: string) => void;\n}\n\nexport function MessagesSpatialView({\n snapshot,\n onAction,\n}: MessagesSpatialViewProps) {\n const dispatch = (action: string) => () => onAction?.(action);\n const selectedThread =\n snapshot.threads.find((t) => t.id === snapshot.selectedThreadId) ?? null;\n const canSend =\n snapshot.composeAddress.trim().length > 0 &&\n snapshot.composeBody.trim().length > 0;\n\n return (\n <Card gap={1} padding={1}>\n <HStack gap={1} align=\"center\">\n <Text style=\"caption\" tone={roleTone(snapshot)} grow={1}>\n {roleLabel(snapshot)}\n </Text>\n <Text style=\"caption\" tone=\"muted\">\n {snapshot.loading ? \"loading\" : `${snapshot.threads.length} threads`}\n </Text>\n </HStack>\n\n {snapshot.error ? (\n <Text tone=\"danger\" style=\"caption\">\n {snapshot.error}\n </Text>\n ) : null}\n\n {!snapshot.ownsSmsRole ? (\n <Button\n variant=\"outline\"\n tone=\"warning\"\n agent=\"request-sms-role\"\n onPress={dispatch(\"request-sms-role\")}\n >\n Set default SMS\n </Button>\n ) : null}\n\n <Divider label=\"threads\" />\n {snapshot.threads.length === 0 ? (\n <Text tone=\"muted\" align=\"center\" style=\"caption\">\n None\n </Text>\n ) : (\n <List gap={0}>\n {snapshot.threads.slice(0, 8).map((thread) => {\n const selected = thread.id === snapshot.selectedThreadId;\n return (\n <HStack key={thread.id} gap={1} align=\"center\">\n <Text tone={directionTone(thread.lastMessage.type)}>\n {directionMark(thread.lastMessage.type)}\n </Text>\n <VStack gap={0} grow={1}>\n <Text bold wrap={false}>\n {thread.address || \"Unknown\"}\n </Text>\n <Text style=\"caption\" tone=\"muted\" wrap={false}>\n {thread.lastMessage.body}\n </Text>\n </VStack>\n {thread.unreadCount > 0 ? (\n <Text style=\"caption\" tone=\"primary\">\n {String(thread.unreadCount)}\n </Text>\n ) : null}\n <Button\n variant={selected ? \"solid\" : \"ghost\"}\n tone=\"primary\"\n agent={`open-thread-${thread.id}`}\n onPress={dispatch(`open-thread:${thread.id}`)}\n >\n Open\n </Button>\n </HStack>\n );\n })}\n </List>\n )}\n\n <Divider label={selectedThread ? selectedThread.address : \"compose\"} />\n {selectedThread ? (\n <List gap={0}>\n {selectedThread.messages.slice(-6).map((message) => (\n <HStack key={message.id} gap={1} align=\"start\">\n <Text tone={directionTone(message.type)} style=\"caption\">\n {messageDirection(message.type)}\n </Text>\n <Text grow={1}>{message.body}</Text>\n </HStack>\n ))}\n </List>\n ) : null}\n\n <Field\n label=\"To\"\n value={snapshot.composeAddress}\n placeholder=\"phone number\"\n agent=\"compose-address\"\n onChange={(value) => onAction?.(`compose-address:${value}`)}\n />\n <Field\n label=\"Body\"\n kind=\"textarea\"\n value={snapshot.composeBody}\n placeholder=\"message\"\n agent=\"compose-body\"\n onChange={(value) => onAction?.(`compose-body:${value}`)}\n />\n <HStack gap={1} wrap>\n <Button\n grow={1}\n disabled={!canSend}\n agent=\"send\"\n onPress={dispatch(\"send\")}\n >\n Send\n </Button>\n <Button\n variant=\"outline\"\n tone=\"default\"\n agent=\"refresh\"\n onPress={dispatch(\"refresh\")}\n >\n Refresh\n </Button>\n </HStack>\n </Card>\n );\n}\n\n/** Group a flat SMS list into threads, newest-last per thread, newest first. */\nexport function buildMessagesThreads(\n messages: SmsMessageSummary[],\n): MessagesThreadSummary[] {\n const byThread = new Map<string, SmsMessageSummary[]>();\n for (const message of messages) {\n const key = message.threadId || message.address || message.id;\n const list = byThread.get(key) ?? [];\n list.push(message);\n byThread.set(key, list);\n }\n return Array.from(byThread.entries())\n .map(([id, threadMessages]) => {\n const sorted = [...threadMessages].sort((a, b) => a.date - b.date);\n const lastMessage = sorted[sorted.length - 1];\n return {\n id,\n address: lastMessage.address,\n messages: sorted,\n lastMessage,\n unreadCount: sorted.filter(\n (m) => !m.read && m.type === INBOUND_SMS_TYPE,\n ).length,\n };\n })\n .sort((a, b) => b.lastMessage.date - a.lastMessage.date);\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * MessagesView — the single GUI/XR data wrapper for the Messages surface.
3
+ *
4
+ * It owns the live Android SMS data (inbox fetch, default-SMS role status,
5
+ * compose state, pending-recipient handoff, send / request-role actions) and
6
+ * renders the one presentational {@link MessagesSpatialView} inside a
7
+ * {@link SpatialSurface}. Omitting the `modality` prop lets `SpatialSurface`
8
+ * auto-detect GUI vs XR via `window.__elizaXRContext`, so the SAME component
9
+ * serves both surfaces. The TUI surface renders the same `MessagesSpatialView`
10
+ * through the terminal registry (see `register-terminal-view.tsx`).
11
+ */
12
+ export declare function MessagesView(): import("react/jsx-runtime").JSX.Element;
13
+ //# sourceMappingURL=MessagesView.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"MessagesView.d.ts","sourceRoot":"","sources":["../../src/components/MessagesView.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAcH,wBAAgB,YAAY,4CAqJ3B"}
@@ -0,0 +1,144 @@
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { Messages } from "@elizaos/capacitor-messages";
3
+ import { System } from "@elizaos/capacitor-system";
4
+ import { consumePendingMessageRecipient } from "@elizaos/ui/app-navigate-view";
5
+ import { SpatialSurface } from "@elizaos/ui/spatial";
6
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
7
+ import { buildThreads, smsRole } from "./MessagesAppView.helpers.js";
8
+ import {
9
+ MessagesSpatialView
10
+ } from "./MessagesSpatialView.js";
11
+ function MessagesView() {
12
+ const [messages, setMessages] = useState([]);
13
+ const [systemStatus, setSystemStatus] = useState(null);
14
+ const [selectedThreadId, setSelectedThreadId] = useState(null);
15
+ const [composeAddress, setComposeAddress] = useState("");
16
+ const [composeBody, setComposeBody] = useState("");
17
+ const [loading, setLoading] = useState(false);
18
+ const [sending, setSending] = useState(false);
19
+ const [error, setError] = useState(null);
20
+ const refresh = useCallback(async () => {
21
+ setLoading(true);
22
+ setError(null);
23
+ try {
24
+ const statusResult = await System.getStatus().catch(() => null);
25
+ setSystemStatus(statusResult);
26
+ const perm = await Messages.requestPermissions().catch(() => null);
27
+ if (perm && perm.sms !== "granted") {
28
+ setMessages([]);
29
+ setError(
30
+ "SMS access is needed to read and send messages. Grant it in your device settings, then retry."
31
+ );
32
+ return;
33
+ }
34
+ const messageResult = await Messages.listMessages({ limit: 200 });
35
+ setMessages(messageResult.messages);
36
+ } catch (err) {
37
+ setError(err instanceof Error ? err.message : String(err));
38
+ setMessages([]);
39
+ } finally {
40
+ setLoading(false);
41
+ }
42
+ }, []);
43
+ const autoLoadedRef = useRef(false);
44
+ useEffect(() => {
45
+ if (!autoLoadedRef.current) {
46
+ autoLoadedRef.current = true;
47
+ void refresh();
48
+ }
49
+ const interval = setInterval(() => void refresh(), 2e4);
50
+ return () => clearInterval(interval);
51
+ }, [refresh]);
52
+ useEffect(() => {
53
+ const pending = consumePendingMessageRecipient();
54
+ if (pending) {
55
+ setSelectedThreadId(null);
56
+ setComposeAddress(pending);
57
+ setComposeBody("");
58
+ setError(null);
59
+ }
60
+ }, []);
61
+ const threads = useMemo(() => buildThreads(messages), [messages]);
62
+ const currentSmsRole = smsRole(systemStatus);
63
+ const ownsSmsRole = currentSmsRole?.held === true;
64
+ const smsRoleHolder = currentSmsRole?.holders[0] ?? null;
65
+ const openThread = useCallback(
66
+ (threadId) => {
67
+ const thread = threads.find((t) => t.id === threadId);
68
+ if (!thread) return;
69
+ setSelectedThreadId(thread.id);
70
+ setComposeAddress(thread.address);
71
+ setError(null);
72
+ },
73
+ [threads]
74
+ );
75
+ const requestSmsRole = useCallback(async () => {
76
+ setError(null);
77
+ try {
78
+ await System.requestRole({ role: "sms" });
79
+ const next = await System.getStatus();
80
+ setSystemStatus(next);
81
+ } catch (err) {
82
+ setError(err instanceof Error ? err.message : String(err));
83
+ }
84
+ }, []);
85
+ const send = useCallback(async () => {
86
+ const address = composeAddress.trim();
87
+ const body = composeBody.trim();
88
+ if (!address || !body || sending) return;
89
+ setSending(true);
90
+ setError(null);
91
+ try {
92
+ await Messages.sendSms({ address, body });
93
+ setComposeBody("");
94
+ await refresh();
95
+ } catch (err) {
96
+ setError(err instanceof Error ? err.message : String(err));
97
+ } finally {
98
+ setSending(false);
99
+ }
100
+ }, [composeAddress, composeBody, refresh, sending]);
101
+ const onAction = useCallback(
102
+ (action) => {
103
+ if (action.startsWith("open-thread:")) {
104
+ openThread(action.slice("open-thread:".length));
105
+ return;
106
+ }
107
+ if (action.startsWith("compose-address:")) {
108
+ setComposeAddress(action.slice("compose-address:".length));
109
+ return;
110
+ }
111
+ if (action.startsWith("compose-body:")) {
112
+ setComposeBody(action.slice("compose-body:".length));
113
+ return;
114
+ }
115
+ switch (action) {
116
+ case "send":
117
+ void send();
118
+ return;
119
+ case "request-sms-role":
120
+ void requestSmsRole();
121
+ return;
122
+ case "refresh":
123
+ void refresh();
124
+ return;
125
+ }
126
+ },
127
+ [openThread, refresh, requestSmsRole, send]
128
+ );
129
+ const snapshot = {
130
+ threads,
131
+ selectedThreadId,
132
+ composeAddress,
133
+ composeBody,
134
+ ownsSmsRole,
135
+ smsRoleHolder,
136
+ loading,
137
+ error
138
+ };
139
+ return /* @__PURE__ */ jsx(SpatialSurface, { children: /* @__PURE__ */ jsx(MessagesSpatialView, { snapshot, onAction }) });
140
+ }
141
+ export {
142
+ MessagesView
143
+ };
144
+ //# sourceMappingURL=MessagesView.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/MessagesView.tsx"],"sourcesContent":["/**\n * MessagesView — the single GUI/XR data wrapper for the Messages surface.\n *\n * It owns the live Android SMS data (inbox fetch, default-SMS role status,\n * compose state, pending-recipient handoff, send / request-role actions) and\n * renders the one presentational {@link MessagesSpatialView} inside a\n * {@link SpatialSurface}. Omitting the `modality` prop lets `SpatialSurface`\n * auto-detect GUI vs XR via `window.__elizaXRContext`, so the SAME component\n * serves both surfaces. The TUI surface renders the same `MessagesSpatialView`\n * through the terminal registry (see `register-terminal-view.tsx`).\n */\n\nimport type { SmsMessageSummary } from \"@elizaos/capacitor-messages\";\nimport { Messages } from \"@elizaos/capacitor-messages\";\nimport { System, type SystemStatus } from \"@elizaos/capacitor-system\";\nimport { consumePendingMessageRecipient } from \"@elizaos/ui/app-navigate-view\";\nimport { SpatialSurface } from \"@elizaos/ui/spatial\";\nimport { useCallback, useEffect, useMemo, useRef, useState } from \"react\";\nimport { buildThreads, smsRole } from \"./MessagesAppView.helpers.js\";\nimport {\n type MessagesSnapshot,\n MessagesSpatialView,\n} from \"./MessagesSpatialView.js\";\n\nexport function MessagesView() {\n const [messages, setMessages] = useState<SmsMessageSummary[]>([]);\n const [systemStatus, setSystemStatus] = useState<SystemStatus | null>(null);\n const [selectedThreadId, setSelectedThreadId] = useState<string | null>(null);\n const [composeAddress, setComposeAddress] = useState(\"\");\n const [composeBody, setComposeBody] = useState(\"\");\n const [loading, setLoading] = useState(false);\n const [sending, setSending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n\n const refresh = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const statusResult = await System.getStatus().catch(() => null);\n setSystemStatus(statusResult);\n const perm = await Messages.requestPermissions().catch(() => null);\n if (perm && perm.sms !== \"granted\") {\n setMessages([]);\n setError(\n \"SMS access is needed to read and send messages. Grant it in your device settings, then retry.\",\n );\n return;\n }\n const messageResult = await Messages.listMessages({ limit: 200 });\n setMessages(messageResult.messages);\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n setMessages([]);\n } finally {\n setLoading(false);\n }\n }, []);\n\n // Load on mount, then quietly poll so newly received SMS surface without a\n // manual control. The bridge has no push channel, so a 20s interval keeps the\n // thread list fresh; it is cleared on unmount.\n const autoLoadedRef = useRef(false);\n useEffect(() => {\n if (!autoLoadedRef.current) {\n autoLoadedRef.current = true;\n void refresh();\n }\n const interval = setInterval(() => void refresh(), 20_000);\n return () => clearInterval(interval);\n }, [refresh]);\n\n // Seed the composer from a cross-view handoff (e.g. a Contacts \"Message\"\n // control that navigated here with a number). Single-shot: the recipient is\n // consumed so a later plain navigation does not re-seed a stale \"To\" field.\n useEffect(() => {\n const pending = consumePendingMessageRecipient();\n if (pending) {\n setSelectedThreadId(null);\n setComposeAddress(pending);\n setComposeBody(\"\");\n setError(null);\n }\n }, []);\n\n const threads = useMemo(() => buildThreads(messages), [messages]);\n const currentSmsRole = smsRole(systemStatus);\n const ownsSmsRole = currentSmsRole?.held === true;\n const smsRoleHolder = currentSmsRole?.holders[0] ?? null;\n\n const openThread = useCallback(\n (threadId: string) => {\n const thread = threads.find((t) => t.id === threadId);\n if (!thread) return;\n setSelectedThreadId(thread.id);\n setComposeAddress(thread.address);\n setError(null);\n },\n [threads],\n );\n\n const requestSmsRole = useCallback(async () => {\n setError(null);\n try {\n await System.requestRole({ role: \"sms\" });\n const next = await System.getStatus();\n setSystemStatus(next);\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n }\n }, []);\n\n const send = useCallback(async () => {\n const address = composeAddress.trim();\n const body = composeBody.trim();\n if (!address || !body || sending) return;\n setSending(true);\n setError(null);\n try {\n await Messages.sendSms({ address, body });\n setComposeBody(\"\");\n await refresh();\n } catch (err) {\n setError(err instanceof Error ? err.message : String(err));\n } finally {\n setSending(false);\n }\n }, [composeAddress, composeBody, refresh, sending]);\n\n const onAction = useCallback(\n (action: string) => {\n if (action.startsWith(\"open-thread:\")) {\n openThread(action.slice(\"open-thread:\".length));\n return;\n }\n if (action.startsWith(\"compose-address:\")) {\n setComposeAddress(action.slice(\"compose-address:\".length));\n return;\n }\n if (action.startsWith(\"compose-body:\")) {\n setComposeBody(action.slice(\"compose-body:\".length));\n return;\n }\n switch (action) {\n case \"send\":\n void send();\n return;\n case \"request-sms-role\":\n void requestSmsRole();\n return;\n case \"refresh\":\n void refresh();\n return;\n }\n },\n [openThread, refresh, requestSmsRole, send],\n );\n\n const snapshot: MessagesSnapshot = {\n threads,\n selectedThreadId,\n composeAddress,\n composeBody,\n ownsSmsRole,\n smsRoleHolder,\n loading,\n error,\n };\n\n return (\n <SpatialSurface>\n <MessagesSpatialView snapshot={snapshot} onAction={onAction} />\n </SpatialSurface>\n );\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,5 @@
1
+ import { type OverlayApp } from "@elizaos/ui";
2
+ export declare const MESSAGES_APP_NAME = "@elizaos/plugin-messages";
3
+ export declare const messagesApp: OverlayApp;
4
+ export declare function registerMessagesApp(): void;
5
+ //# sourceMappingURL=messages-app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages-app.d.ts","sourceRoot":"","sources":["../../src/components/messages-app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,UAAU,EAAsB,MAAM,aAAa,CAAC;AAElE,eAAO,MAAM,iBAAiB,6BAA6B,CAAC;AAE5D,eAAO,MAAM,WAAW,EAAE,UASzB,CAAC;AAEF,wBAAgB,mBAAmB,IAAI,IAAI,CAE1C"}
@@ -0,0 +1,20 @@
1
+ import { registerOverlayApp } from "@elizaos/ui";
2
+ const MESSAGES_APP_NAME = "@elizaos/plugin-messages";
3
+ const messagesApp = {
4
+ name: MESSAGES_APP_NAME,
5
+ displayName: "Messages",
6
+ description: "SMS inbox, threads, and compose for Android",
7
+ category: "system",
8
+ icon: null,
9
+ androidOnly: true,
10
+ loader: () => import("./MessagesAppView.js").then((m) => ({ default: m.MessagesAppView }))
11
+ };
12
+ function registerMessagesApp() {
13
+ registerOverlayApp(messagesApp);
14
+ }
15
+ export {
16
+ MESSAGES_APP_NAME,
17
+ messagesApp,
18
+ registerMessagesApp
19
+ };
20
+ //# sourceMappingURL=messages-app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/messages-app.ts"],"sourcesContent":["import { type OverlayApp, registerOverlayApp } from \"@elizaos/ui\";\n\nexport const MESSAGES_APP_NAME = \"@elizaos/plugin-messages\";\n\nexport const messagesApp: OverlayApp = {\n name: MESSAGES_APP_NAME,\n displayName: \"Messages\",\n description: \"SMS inbox, threads, and compose for Android\",\n category: \"system\",\n icon: null,\n androidOnly: true,\n loader: () =>\n import(\"./MessagesAppView.js\").then((m) => ({ default: m.MessagesAppView })),\n};\n\nexport function registerMessagesApp(): void {\n registerOverlayApp(messagesApp);\n}\n"],"mappings":"AAAA,SAA0B,0BAA0B;AAE7C,MAAM,oBAAoB;AAE1B,MAAM,cAA0B;AAAA,EACrC,MAAM;AAAA,EACN,aAAa;AAAA,EACb,aAAa;AAAA,EACb,UAAU;AAAA,EACV,MAAM;AAAA,EACN,aAAa;AAAA,EACb,QAAQ,MACN,OAAO,sBAAsB,EAAE,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,gBAAgB,EAAE;AAC/E;AAEO,SAAS,sBAA4B;AAC1C,qBAAmB,WAAW;AAChC;","names":[]}
@@ -0,0 +1,3 @@
1
+ export { interact } from "./MessagesAppView.interact.ts";
2
+ export { MessagesView } from "./MessagesView.tsx";
3
+ //# sourceMappingURL=messages-view-bundle.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"messages-view-bundle.d.ts","sourceRoot":"","sources":["../../src/components/messages-view-bundle.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,QAAQ,EAAE,MAAM,+BAA+B,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,7 @@
1
+ import { interact } from "./MessagesAppView.interact.js";
2
+ import { MessagesView } from "./MessagesView.js";
3
+ export {
4
+ MessagesView,
5
+ interact
6
+ };
7
+ //# sourceMappingURL=messages-view-bundle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/components/messages-view-bundle.ts"],"sourcesContent":["// Vite view-bundle entry. Re-exports the unified spatial view component plus\n// the `interact` capability handler so the built bundle (dist/views/bundle.js)\n// exposes the named exports the view loader reads (`MessagesView`, `interact`).\n// Kept separate from MessagesView.tsx so that file exports only React components\n// and stays Fast-Refresh-compatible in dev.\n\nexport { interact } from \"./MessagesAppView.interact.js\";\nexport { MessagesView } from \"./MessagesView.js\";\n"],"mappings":"AAMA,SAAS,gBAAgB;AACzB,SAAS,oBAAoB;","names":[]}
@@ -0,0 +1,7 @@
1
+ export { MessagesAppView } from "./components/MessagesAppView";
2
+ export { MessagesView } from "./components/MessagesView";
3
+ export { MESSAGES_APP_NAME, messagesApp, registerMessagesApp, } from "./components/messages-app";
4
+ export { appMessagesPlugin, default } from "./plugin";
5
+ export * from "./register";
6
+ export * from "./ui";
7
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,8BAA8B,CAAC;AAC/D,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,EACL,iBAAiB,EACjB,WAAW,EACX,mBAAmB,GACpB,MAAM,2BAA2B,CAAC;AACnC,OAAO,EAAE,iBAAiB,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACtD,cAAc,YAAY,CAAC;AAC3B,cAAc,MAAM,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ import { MessagesAppView } from "./components/MessagesAppView.js";
2
+ import { MessagesView } from "./components/MessagesView.js";
3
+ import {
4
+ MESSAGES_APP_NAME,
5
+ messagesApp,
6
+ registerMessagesApp
7
+ } from "./components/messages-app.js";
8
+ import { appMessagesPlugin, default as default2 } from "./plugin.js";
9
+ export * from "./register.js";
10
+ export * from "./ui.js";
11
+ export {
12
+ MESSAGES_APP_NAME,
13
+ MessagesAppView,
14
+ MessagesView,
15
+ appMessagesPlugin,
16
+ default2 as default,
17
+ messagesApp,
18
+ registerMessagesApp
19
+ };
20
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export { MessagesAppView } from \"./components/MessagesAppView.js\";\nexport { MessagesView } from \"./components/MessagesView.js\";\nexport {\n MESSAGES_APP_NAME,\n messagesApp,\n registerMessagesApp,\n} from \"./components/messages-app.js\";\nexport { appMessagesPlugin, default } from \"./plugin.js\";\nexport * from \"./register.js\";\nexport * from \"./ui.js\";\n"],"mappings":"AAAA,SAAS,uBAAuB;AAChC,SAAS,oBAAoB;AAC7B;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,OACK;AACP,SAAS,mBAAmB,WAAAA,gBAAe;AAC3C,cAAc;AACd,cAAc;","names":["default"]}
@@ -0,0 +1,4 @@
1
+ import type { Plugin } from "@elizaos/core";
2
+ export declare const appMessagesPlugin: Plugin;
3
+ export default appMessagesPlugin;
4
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAE5C,eAAO,MAAM,iBAAiB,EAAE,MAuB/B,CAAC;AAEF,eAAe,iBAAiB,CAAC"}