@athenaintel/react 0.42.1 → 0.42.2

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 (96) hide show
  1. package/README.md +36 -7
  2. package/dist/agent-runtime/src/index.d.ts +2 -2
  3. package/dist/agent-runtime/src/messages.cjs +38 -7
  4. package/dist/agent-runtime/src/messages.cjs.map +1 -1
  5. package/dist/agent-runtime/src/messages.d.ts +26 -8
  6. package/dist/agent-runtime/src/messages.js +38 -7
  7. package/dist/agent-runtime/src/messages.js.map +1 -1
  8. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs +10 -0
  9. package/dist/agent-runtime/src/quota-safe-local-storage-session.cjs.map +1 -1
  10. package/dist/agent-runtime/src/quota-safe-local-storage-session.d.ts +7 -0
  11. package/dist/agent-runtime/src/quota-safe-local-storage-session.js +10 -0
  12. package/dist/agent-runtime/src/quota-safe-local-storage-session.js.map +1 -1
  13. package/dist/agent-runtime/src/session-lane-scope.cjs +25 -0
  14. package/dist/agent-runtime/src/session-lane-scope.cjs.map +1 -0
  15. package/dist/agent-runtime/src/session-lane-scope.d.ts +59 -0
  16. package/dist/agent-runtime/src/session-lane-scope.js +25 -0
  17. package/dist/agent-runtime/src/session-lane-scope.js.map +1 -0
  18. package/dist/agent-runtime/src/subagent-transcripts.cjs +2 -2
  19. package/dist/agent-runtime/src/subagent-transcripts.cjs.map +1 -1
  20. package/dist/agent-runtime/src/subagent-transcripts.d.ts +2 -2
  21. package/dist/agent-runtime/src/subagent-transcripts.js +3 -3
  22. package/dist/agent-runtime/src/subagent-transcripts.js.map +1 -1
  23. package/dist/agent-runtime/src/tool-names.cjs +4 -0
  24. package/dist/agent-runtime/src/tool-names.cjs.map +1 -1
  25. package/dist/agent-runtime/src/tool-names.d.ts +7 -0
  26. package/dist/agent-runtime/src/tool-names.js +4 -0
  27. package/dist/agent-runtime/src/tool-names.js.map +1 -1
  28. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +18 -1
  29. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  30. package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +19 -10
  31. package/dist/agent-runtime/src/use-deep-agent-runtime.js +19 -2
  32. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  33. package/dist/agent-runtime/src/use-deep-agent-thread.cjs +28 -10
  34. package/dist/agent-runtime/src/use-deep-agent-thread.cjs.map +1 -1
  35. package/dist/agent-runtime/src/use-deep-agent-thread.d.ts +35 -3
  36. package/dist/agent-runtime/src/use-deep-agent-thread.js +28 -10
  37. package/dist/agent-runtime/src/use-deep-agent-thread.js.map +1 -1
  38. package/dist/agent-runtime/src/use-deep-agent-voice.cjs +20 -0
  39. package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
  40. package/dist/agent-runtime/src/use-deep-agent-voice.js +20 -0
  41. package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
  42. package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
  43. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  44. package/dist/agent-runtime/src/voice-presentation.js +2 -0
  45. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  46. package/dist/catalog/runtime-catalog.js +1 -1
  47. package/dist/chat/AthenaChat.cjs +125 -103
  48. package/dist/chat/AthenaChat.cjs.map +1 -1
  49. package/dist/chat/AthenaChat.d.ts +9 -0
  50. package/dist/chat/AthenaChat.js +126 -104
  51. package/dist/chat/AthenaChat.js.map +1 -1
  52. package/dist/chat/chat-voice-controls.cjs +128 -0
  53. package/dist/chat/chat-voice-controls.cjs.map +1 -0
  54. package/dist/chat/chat-voice-controls.d.ts +19 -0
  55. package/dist/chat/chat-voice-controls.js +128 -0
  56. package/dist/chat/chat-voice-controls.js.map +1 -0
  57. package/dist/chat/use-statewire-approval-request.js +1 -1
  58. package/dist/chat-ui/composer/tiptap-composer.cjs +42 -22
  59. package/dist/chat-ui/composer/tiptap-composer.cjs.map +1 -1
  60. package/dist/chat-ui/composer/tiptap-composer.d.ts +1 -0
  61. package/dist/chat-ui/composer/tiptap-composer.js +43 -23
  62. package/dist/chat-ui/composer/tiptap-composer.js.map +1 -1
  63. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs +2 -1
  64. package/dist/chat-ui/mentions/suggestions/data/api-client.cjs.map +1 -1
  65. package/dist/chat-ui/mentions/suggestions/data/api-client.d.ts +3 -1
  66. package/dist/chat-ui/mentions/suggestions/data/api-client.js +2 -1
  67. package/dist/chat-ui/mentions/suggestions/data/api-client.js.map +1 -1
  68. package/dist/chat-ui/mentions/use-account-workspace-id.cjs +29 -51
  69. package/dist/chat-ui/mentions/use-account-workspace-id.cjs.map +1 -1
  70. package/dist/chat-ui/mentions/use-account-workspace-id.d.ts +2 -2
  71. package/dist/chat-ui/mentions/use-account-workspace-id.js +30 -52
  72. package/dist/chat-ui/mentions/use-account-workspace-id.js.map +1 -1
  73. package/dist/chat-ui/thread/composer-send-or-stop.cjs +2 -1
  74. package/dist/chat-ui/thread/composer-send-or-stop.cjs.map +1 -1
  75. package/dist/chat-ui/thread/composer-send-or-stop.d.ts +2 -0
  76. package/dist/chat-ui/thread/composer-send-or-stop.js +2 -1
  77. package/dist/chat-ui/thread/composer-send-or-stop.js.map +1 -1
  78. package/dist/diagnostics/athena-diagnostics.cjs.map +1 -1
  79. package/dist/diagnostics/athena-diagnostics.d.ts +1 -1
  80. package/dist/diagnostics/athena-diagnostics.js.map +1 -1
  81. package/dist/index.d.ts +1 -1
  82. package/dist/react/package.json.cjs +1 -1
  83. package/dist/react/package.json.js +1 -1
  84. package/dist/runtime/statewire-session-key.cjs.map +1 -1
  85. package/dist/runtime/statewire-session-key.d.ts +8 -5
  86. package/dist/runtime/statewire-session-key.js.map +1 -1
  87. package/dist/runtime/useAthenaStatewireRuntime.cjs +5 -2
  88. package/dist/runtime/useAthenaStatewireRuntime.cjs.map +1 -1
  89. package/dist/runtime/useAthenaStatewireRuntime.js +5 -2
  90. package/dist/runtime/useAthenaStatewireRuntime.js.map +1 -1
  91. package/dist/voice/use-chat-voice-enabled.cjs +43 -0
  92. package/dist/voice/use-chat-voice-enabled.cjs.map +1 -0
  93. package/dist/voice/use-chat-voice-enabled.d.ts +9 -0
  94. package/dist/voice/use-chat-voice-enabled.js +43 -0
  95. package/dist/voice/use-chat-voice-enabled.js.map +1 -0
  96. package/package.json +4 -8
@@ -1 +1 @@
1
- {"version":3,"file":"voice-presentation.js","sources":["../../../../agent-runtime/src/voice-presentation.ts"],"sourcesContent":["/**\n * How voice-mode transcript entries present, shared by every chat surface\n * (Olympus, the Chrome side panel, the mobile app) so a spoken turn, a\n * delegation the live voice model handed off, and an interim spoken update\n * read the same everywhere. The wire contract is the deep-agent host's\n * `additional_kwargs.metadata` (exposed as `message.metadata.custom`):\n *\n * - `source: \"voice\"` — a spoken turn; `voice_live: true` while it is still\n * being heard or spoken (the ephemeral entry the durable row replaces);\n * - `source: \"text\"` — a message typed into the live conversation;\n * - `source: \"voice_delegation\"` (id prefix `voicetask_`) — the unit of work\n * the live voice model delegated to the thread.\n */\nimport { getExternalStoreMessages, type ThreadMessage } from '@assistant-ui/core';\n\n/** `metadata.custom.source` of a spoken turn. */\nexport const VOICE_SOURCE_SPOKEN = 'voice';\n/** `metadata.custom.source` of a message typed into the live conversation. */\nexport const VOICE_SOURCE_TYPED = 'text';\n/** `metadata.custom.source` of a voice delegation's anchor message. */\nexport const VOICE_SOURCE_DELEGATION = 'voice_delegation';\n/** Id prefix of a voice delegation's anchor message (the working signal on\n * hosts that drop the command's metadata). */\nexport const VOICE_DELEGATION_ID_PREFIX = 'voicetask_';\n/** `metadata.custom` flag on a spoken turn still in flight. */\nexport const VOICE_LIVE_KEY = 'voice_live';\n/** `metadata.custom` list of the wire ids a grouped spoken bubble stands for\n * (see `groupSpokenUserTurns`), so id-keyed lookups such as checkpoint markers\n * can resolve any member to the bubble. */\nexport const VOICE_TURN_IDS_KEY = 'voice_turn_ids';\n\n/** The delegated run's interim-update tool (agora `send_voice_update`). */\nexport const VOICE_UPDATE_TOOL_NAME = 'send_voice_update';\n\n/** Trailing hint on the live-session strip, worded once for every surface. */\nexport const VOICE_LIVE_TYPED_HINT = 'typed messages go to the voice conversation';\n\nexport interface VoiceMessageMarker {\n /** The thread message id; a host may not have assigned one yet. */\n messageId: string | undefined;\n /** The thread message's `metadata.custom`, when any. */\n custom: Record<string, unknown> | undefined;\n}\n\n/** Whether a thread message is a voice delegation's anchor (the ask the live\n * voice model handed to the thread), by source or by id prefix. */\nexport function isVoiceDelegationMessage({ messageId, custom }: VoiceMessageMarker): boolean {\n return (\n custom?.source === VOICE_SOURCE_DELEGATION ||\n (typeof messageId === 'string' && messageId.startsWith(VOICE_DELEGATION_ID_PREFIX))\n );\n}\n\nexport interface VoiceProvenance {\n /** Chip copy: \"Listening\", \"Speaking\", \"Spoken\", or \"Delegated from voice\". */\n readonly label: string;\n /** True while the turn is still being heard or spoken. */\n readonly live: boolean;\n}\n\n// These values are read directly by useAuiState/useSyncExternalStore selectors.\n// Equal presentation must retain its identity, even when a transport replaces\n// the message metadata. Fresh objects here cause React's update-depth crash.\nconst SPOKEN_PROVENANCE: VoiceProvenance = Object.freeze({\n label: 'Spoken',\n live: false,\n});\nconst LISTENING_PROVENANCE: VoiceProvenance = Object.freeze({\n label: 'Listening',\n live: true,\n});\nconst SPEAKING_PROVENANCE: VoiceProvenance = Object.freeze({\n label: 'Speaking',\n live: true,\n});\nconst DELEGATED_PROVENANCE: VoiceProvenance = Object.freeze({\n label: 'Delegated from voice',\n live: false,\n});\n\n/**\n * The provenance chip for a message, or `null` for ordinary chat messages so\n * the chip is free to mount on every row. A spoken turn still in flight reads\n * as what is happening (\"Listening\" for the user, \"Speaking\" for the\n * assistant) rather than as a finished \"Spoken\" turn; a typed-in-voice turn\n * is a normal message and gets no chip. Results are immutable and stable for\n * equal presentation, so all surfaces can use this function as a selector.\n */\nexport function voiceProvenanceLabel({\n messageId,\n custom,\n role,\n}: VoiceMessageMarker & { role: 'user' | 'assistant' | string }): VoiceProvenance | null {\n const source = custom?.source;\n if (source === VOICE_SOURCE_SPOKEN) {\n if (custom?.[VOICE_LIVE_KEY] === true) {\n return role === 'user' ? LISTENING_PROVENANCE : SPEAKING_PROVENANCE;\n }\n return SPOKEN_PROVENANCE;\n }\n if (isVoiceDelegationMessage({ messageId, custom })) {\n return DELEGATED_PROVENANCE;\n }\n return null;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Whether a part of an assistant message holds the words of a spoken\n * assistant turn.\n *\n * The converter joins consecutive assistant entries into one message. The\n * host appends a live turn while it is heard, and a delegated run keeps\n * appending its own steps after it, so a turn spoken during a run becomes a\n * text part inside the run's message, before the run's later tool calls.\n * Grouped views put the text before the last tool call in the details panel\n * as the run's narration; they use this to keep the spoken words visible.\n *\n * The joined message's `metadata.custom` merges every entry in it, so this\n * reads the part's own entry, which the converter binds to the part.\n */\nexport function isSpokenTurnTextPart(part: { readonly type: string }): boolean {\n if (part.type !== 'text') return false;\n const entries: readonly unknown[] = getExternalStoreMessages<unknown>(\n part as ThreadMessage['content'][number]\n );\n return entries.some((entry) => {\n if (!isRecord(entry) || entry.role !== 'assistant' || !isRecord(entry.metadata)) return false;\n const custom = entry.metadata.custom;\n return isRecord(custom) && custom.source === VOICE_SOURCE_SPOKEN;\n });\n}\n\nexport interface VoiceUpdateToolCall {\n /** `mode: \"speak\"` — read aloud to the user; otherwise a silent progress note. */\n spoken: boolean;\n /** The update's words. */\n text: string;\n /** Whether the live session took it; `null` while the call is still running. */\n delivered: boolean | null;\n}\n\n/**\n * What a `send_voice_update` tool call did, from its arguments and the tool's\n * teaching-text result (agora returns prose, not JSON, so the run can read it).\n */\nexport function describeVoiceUpdateToolCall({\n args,\n result,\n}: {\n args: Record<string, unknown> | undefined;\n result: unknown;\n}): VoiceUpdateToolCall {\n const text = typeof args?.text === 'string' ? args.text.trim() : '';\n const spoken = args?.mode === 'speak';\n if (typeof result !== 'string' || result.trim() === '') {\n return { spoken, text, delivered: null };\n }\n const outcome = result.toLowerCase();\n const failed =\n outcome.includes('could not be delivered') ||\n outcome.includes('was not delivered') ||\n outcome.includes('rejected the update') ||\n outcome.includes('has ended') ||\n outcome.includes('not configured') ||\n outcome.includes('no voice session');\n return { spoken, text, delivered: !failed };\n}\n\n/** The card's status line for a voice update in a given tool-call state. */\nexport function voiceUpdateStatusLabel(\n call: VoiceUpdateToolCall,\n status: 'running' | 'complete' | 'incomplete' | 'requires-action' | undefined\n): string {\n if (status === 'running' || (call.delivered === null && status !== 'incomplete')) {\n return call.spoken ? 'Saying to you…' : 'Noting progress…';\n }\n if (call.delivered === false || status === 'incomplete') {\n return call.spoken ? 'Could not say this to you' : 'Progress note not delivered';\n }\n return call.spoken ? 'Said aloud' : 'Progress noted silently';\n}\n"],"names":[],"mappings":";AAgBO,MAAM,sBAAsB;AAI5B,MAAM,0BAA0B;AAGhC,MAAM,6BAA6B;AAEnC,MAAM,iBAAiB;AAIvB,MAAM,qBAAqB;AAG3B,MAAM,yBAAyB;AAc/B,SAAS,yBAAyB,EAAE,WAAW,UAAuC;AAC3F,UACE,iCAAQ,YAAW,2BAClB,OAAO,cAAc,YAAY,UAAU,WAAW,0BAA0B;AAErF;AAuDA,MAAM,WAAW,CAAC,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAgB9D,SAAS,qBAAqB,MAA0C;AAC7E,MAAI,KAAK,SAAS,OAAQ,QAAO;AACjC,QAAM,UAA8B;AAAA,IAClC;AAAA,EAAA;AAEF,SAAO,QAAQ,KAAK,CAAC,UAAU;AAC7B,QAAI,CAAC,SAAS,KAAK,KAAK,MAAM,SAAS,eAAe,CAAC,SAAS,MAAM,QAAQ,EAAG,QAAO;AACxF,UAAM,SAAS,MAAM,SAAS;AAC9B,WAAO,SAAS,MAAM,KAAK,OAAO,WAAW;AAAA,EAC/C,CAAC;AACH;AAeO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AACF,GAGwB;AACtB,QAAM,OAAO,QAAO,6BAAM,UAAS,WAAW,KAAK,KAAK,SAAS;AACjE,QAAM,UAAS,6BAAM,UAAS;AAC9B,MAAI,OAAO,WAAW,YAAY,OAAO,KAAA,MAAW,IAAI;AACtD,WAAO,EAAE,QAAQ,MAAM,WAAW,KAAA;AAAA,EACpC;AACA,QAAM,UAAU,OAAO,YAAA;AACvB,QAAM,SACJ,QAAQ,SAAS,wBAAwB,KACzC,QAAQ,SAAS,mBAAmB,KACpC,QAAQ,SAAS,qBAAqB,KACtC,QAAQ,SAAS,WAAW,KAC5B,QAAQ,SAAS,gBAAgB,KACjC,QAAQ,SAAS,kBAAkB;AACrC,SAAO,EAAE,QAAQ,MAAM,WAAW,CAAC,OAAA;AACrC;AAGO,SAAS,uBACd,MACA,QACQ;AACR,MAAI,WAAW,aAAc,KAAK,cAAc,QAAQ,WAAW,cAAe;AAChF,WAAO,KAAK,SAAS,mBAAmB;AAAA,EAC1C;AACA,MAAI,KAAK,cAAc,SAAS,WAAW,cAAc;AACvD,WAAO,KAAK,SAAS,8BAA8B;AAAA,EACrD;AACA,SAAO,KAAK,SAAS,eAAe;AACtC;"}
1
+ {"version":3,"file":"voice-presentation.js","sources":["../../../../agent-runtime/src/voice-presentation.ts"],"sourcesContent":["/**\n * How voice-mode transcript entries present, shared by every chat surface\n * (Olympus, the Chrome side panel, the mobile app) so a spoken turn, a\n * delegation the live voice model handed off, and an interim spoken update\n * read the same everywhere. The wire contract is the deep-agent host's\n * `additional_kwargs.metadata` (exposed as `message.metadata.custom`):\n *\n * - `source: \"voice\"` — a spoken turn; `voice_live: true` while it is still\n * being heard or spoken (the ephemeral entry the durable row replaces);\n * - `source: \"text\"` — a message typed into the live conversation;\n * - `source: \"voice_delegation\"` (id prefix `voicetask_`) — the unit of work\n * the live voice model delegated to the thread.\n */\nimport { getExternalStoreMessages, type ThreadMessage } from '@assistant-ui/core';\n\n/** `metadata.custom.source` of a spoken turn. */\nexport const VOICE_SOURCE_SPOKEN = 'voice';\n/** `metadata.custom.source` of a message typed into the live conversation. */\nexport const VOICE_SOURCE_TYPED = 'text';\n/** `metadata.custom.source` of a voice delegation's anchor message. */\nexport const VOICE_SOURCE_DELEGATION = 'voice_delegation';\n/** Id prefix of a voice delegation's anchor message (the working signal on\n * hosts that drop the command's metadata). */\nexport const VOICE_DELEGATION_ID_PREFIX = 'voicetask_';\n/** `metadata.custom` flag on a spoken turn still in flight. */\nexport const VOICE_LIVE_KEY = 'voice_live';\n/** `metadata.custom` list of the wire ids a grouped spoken bubble stands for\n * (see `groupSpokenUserTurns`), so id-keyed lookups such as checkpoint markers\n * can resolve any member to the bubble. */\nexport const VOICE_TURN_IDS_KEY = 'voice_turn_ids';\n\n/** The delegated run's interim-update tool (agora `send_voice_update`). */\nexport const VOICE_UPDATE_TOOL_NAME = 'send_voice_update';\n\n/** Trailing hint on the live-session strip, worded once for every surface. */\nexport const VOICE_LIVE_TYPED_HINT = 'typed messages go to the voice conversation';\n\nexport interface VoiceMessageMarker {\n /** The thread message id; a host may not have assigned one yet. */\n messageId: string | undefined;\n /** The thread message's `metadata.custom`, when any. */\n custom: Record<string, unknown> | undefined;\n}\n\n/** Whether a thread message is a voice delegation's anchor (the ask the live\n * voice model handed to the thread), by source or by id prefix. */\nexport function isVoiceDelegationMessage({ messageId, custom }: VoiceMessageMarker): boolean {\n return (\n custom?.source === VOICE_SOURCE_DELEGATION ||\n (typeof messageId === 'string' && messageId.startsWith(VOICE_DELEGATION_ID_PREFIX))\n );\n}\n\nexport interface VoiceProvenance {\n /** Chip copy: \"Listening\", \"Speaking\", \"Spoken\", or \"Delegated from voice\". */\n readonly label: string;\n /** True while the turn is still being heard or spoken. */\n readonly live: boolean;\n}\n\n// These values are read directly by useAuiState/useSyncExternalStore selectors.\n// Equal presentation must retain its identity, even when a transport replaces\n// the message metadata. Fresh objects here cause React's update-depth crash.\nconst SPOKEN_PROVENANCE: VoiceProvenance = Object.freeze({\n label: 'Spoken',\n live: false,\n});\nconst LISTENING_PROVENANCE: VoiceProvenance = Object.freeze({\n label: 'Listening',\n live: true,\n});\nconst SPEAKING_PROVENANCE: VoiceProvenance = Object.freeze({\n label: 'Speaking',\n live: true,\n});\nconst DELEGATED_PROVENANCE: VoiceProvenance = Object.freeze({\n label: 'Delegated from voice',\n live: false,\n});\n\n/**\n * The provenance chip for a message, or `null` for ordinary chat messages so\n * the chip is free to mount on every row. A spoken turn still in flight reads\n * as what is happening (\"Listening\" for the user, \"Speaking\" for the\n * assistant) rather than as a finished \"Spoken\" turn; a typed-in-voice turn\n * is a normal message and gets no chip. Results are immutable and stable for\n * equal presentation, so all surfaces can use this function as a selector.\n */\nexport function voiceProvenanceLabel({\n messageId,\n custom,\n role,\n}: VoiceMessageMarker & { role: 'user' | 'assistant' | string }): VoiceProvenance | null {\n const source = custom?.source;\n if (source === VOICE_SOURCE_SPOKEN) {\n if (custom?.[VOICE_LIVE_KEY] === true) {\n return role === 'user' ? LISTENING_PROVENANCE : SPEAKING_PROVENANCE;\n }\n return SPOKEN_PROVENANCE;\n }\n if (isVoiceDelegationMessage({ messageId, custom })) {\n return DELEGATED_PROVENANCE;\n }\n return null;\n}\n\nconst isRecord = (value: unknown): value is Record<string, unknown> =>\n typeof value === 'object' && value !== null && !Array.isArray(value);\n\n/**\n * Whether a part of an assistant message holds the words of a spoken\n * assistant turn.\n *\n * The converter joins consecutive assistant entries into one message. The\n * host appends a live turn while it is heard, and a delegated run keeps\n * appending its own steps after it, so a turn spoken during a run becomes a\n * text part inside the run's message, before the run's later tool calls.\n * Grouped views put the text before the last tool call in the details panel\n * as the run's narration; they use this to keep the spoken words visible.\n *\n * The joined message's `metadata.custom` merges every entry in it, so this\n * reads the part's own entry, which the converter binds to the part.\n */\nexport function isSpokenTurnTextPart(part: { readonly type: string }): boolean {\n if (part.type !== 'text') return false;\n const entries: readonly unknown[] = getExternalStoreMessages<unknown>(\n part as ThreadMessage['content'][number]\n );\n return entries.some((entry) => {\n if (!isRecord(entry) || entry.role !== 'assistant' || !isRecord(entry.metadata)) return false;\n const custom = entry.metadata.custom;\n return isRecord(custom) && custom.source === VOICE_SOURCE_SPOKEN;\n });\n}\n\nexport interface VoiceUpdateToolCall {\n /** `mode: \"speak\"` — read aloud to the user; otherwise a silent progress note. */\n spoken: boolean;\n /** The update's words. */\n text: string;\n /** Whether the live session took it; `null` while the call is still running. */\n delivered: boolean | null;\n}\n\n/**\n * What a `send_voice_update` tool call did, from its arguments and the tool's\n * teaching-text result (agora returns prose, not JSON, so the run can read it).\n */\nexport function describeVoiceUpdateToolCall({\n args,\n result,\n}: {\n args: Record<string, unknown> | undefined;\n result: unknown;\n}): VoiceUpdateToolCall {\n const text = typeof args?.text === 'string' ? args.text.trim() : '';\n const spoken = args?.mode === 'speak';\n if (typeof result !== 'string' || result.trim() === '') {\n return { spoken, text, delivered: null };\n }\n const outcome = result.toLowerCase();\n const failed =\n outcome.includes('could not be delivered') ||\n outcome.includes('was not delivered') ||\n outcome.includes('rejected the update') ||\n outcome.includes('has ended') ||\n outcome.includes('not configured') ||\n outcome.includes('no voice session');\n return { spoken, text, delivered: !failed };\n}\n\n/** The card's status line for a voice update in a given tool-call state. */\nexport function voiceUpdateStatusLabel(\n call: VoiceUpdateToolCall,\n status: 'running' | 'complete' | 'incomplete' | 'requires-action' | undefined\n): string {\n if (status === 'running' || (call.delivered === null && status !== 'incomplete')) {\n return call.spoken ? 'Saying to you…' : 'Noting progress…';\n }\n if (call.delivered === false || status === 'incomplete') {\n return call.spoken ? 'Could not say this to you' : 'Progress note not delivered';\n }\n return call.spoken ? 'Said aloud' : 'Progress noted silently';\n}\n"],"names":[],"mappings":";AAgBO,MAAM,sBAAsB;AAI5B,MAAM,0BAA0B;AAGhC,MAAM,6BAA6B;AAEnC,MAAM,iBAAiB;AAIvB,MAAM,qBAAqB;AAG3B,MAAM,yBAAyB;AAG/B,MAAM,wBAAwB;AAW9B,SAAS,yBAAyB,EAAE,WAAW,UAAuC;AAC3F,UACE,iCAAQ,YAAW,2BAClB,OAAO,cAAc,YAAY,UAAU,WAAW,0BAA0B;AAErF;AAuDA,MAAM,WAAW,CAAC,UAChB,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,KAAK;AAgB9D,SAAS,qBAAqB,MAA0C;AAC7E,MAAI,KAAK,SAAS,OAAQ,QAAO;AACjC,QAAM,UAA8B;AAAA,IAClC;AAAA,EAAA;AAEF,SAAO,QAAQ,KAAK,CAAC,UAAU;AAC7B,QAAI,CAAC,SAAS,KAAK,KAAK,MAAM,SAAS,eAAe,CAAC,SAAS,MAAM,QAAQ,EAAG,QAAO;AACxF,UAAM,SAAS,MAAM,SAAS;AAC9B,WAAO,SAAS,MAAM,KAAK,OAAO,WAAW;AAAA,EAC/C,CAAC;AACH;AAeO,SAAS,4BAA4B;AAAA,EAC1C;AAAA,EACA;AACF,GAGwB;AACtB,QAAM,OAAO,QAAO,6BAAM,UAAS,WAAW,KAAK,KAAK,SAAS;AACjE,QAAM,UAAS,6BAAM,UAAS;AAC9B,MAAI,OAAO,WAAW,YAAY,OAAO,KAAA,MAAW,IAAI;AACtD,WAAO,EAAE,QAAQ,MAAM,WAAW,KAAA;AAAA,EACpC;AACA,QAAM,UAAU,OAAO,YAAA;AACvB,QAAM,SACJ,QAAQ,SAAS,wBAAwB,KACzC,QAAQ,SAAS,mBAAmB,KACpC,QAAQ,SAAS,qBAAqB,KACtC,QAAQ,SAAS,WAAW,KAC5B,QAAQ,SAAS,gBAAgB,KACjC,QAAQ,SAAS,kBAAkB;AACrC,SAAO,EAAE,QAAQ,MAAM,WAAW,CAAC,OAAA;AACrC;AAGO,SAAS,uBACd,MACA,QACQ;AACR,MAAI,WAAW,aAAc,KAAK,cAAc,QAAQ,WAAW,cAAe;AAChF,WAAO,KAAK,SAAS,mBAAmB;AAAA,EAC1C;AACA,MAAI,KAAK,cAAc,SAAS,WAAW,cAAc;AACvD,WAAO,KAAK,SAAS,8BAA8B;AAAA,EACrD;AACA,SAAO,KAAK,SAAS,eAAe;AACtC;"}
@@ -1,5 +1,5 @@
1
1
  import { useQuery, QueryClientContext, QueryClient } from "@tanstack/react-query";
2
- import { useMemo, useContext, useState, useCallback } from "react";
2
+ import { useContext, useState, useMemo, useCallback } from "react";
3
3
  import { AthenaAuthContext } from "../auth/AthenaAuthContext.js";
4
4
  import { fetchGraphQL, AthenaGraphQLError } from "../chat-ui/mentions/suggestions/data/api-client.js";
5
5
  import { useAthenaConfig } from "../provider/AthenaContext.js";
@@ -70,6 +70,10 @@ const index = require("../toolkits/index.cjs");
70
70
  const runActivity = require("./run-activity.cjs");
71
71
  const messageTime = require("./message-time.cjs");
72
72
  const threadScrollToBottom = require("./thread-scroll-to-bottom.cjs");
73
+ const reactQuery = require("@tanstack/react-query");
74
+ const runtimeCatalog = require("../catalog/runtime-catalog.cjs");
75
+ const statewireLifecycle = require("../runtime/statewire-lifecycle.cjs");
76
+ const chatVoiceControls = require("./chat-voice-controls.cjs");
73
77
  const toolNames = require("../agent-runtime/src/tool-names.cjs");
74
78
  const subagents = require("../agent-runtime/src/subagents.cjs");
75
79
  const githubWakeEvent = require("../agent-runtime/src/github-wake-event.cjs");
@@ -146,7 +150,11 @@ const withNestedPtcMessages = (ToolUI) => {
146
150
  };
147
151
  return ToolUIWithNestedPtcMessages;
148
152
  };
149
- const AthenaChat = ({
153
+ const AthenaChat = (props) => {
154
+ const queryClient = runtimeCatalog.useCatalogQueryClient();
155
+ return /* @__PURE__ */ jsxRuntime.jsx(reactQuery.QueryClientProvider, { client: queryClient, children: /* @__PURE__ */ jsxRuntime.jsx(AthenaChatContent, { ...props }) });
156
+ };
157
+ const AthenaChatContent = ({
150
158
  className,
151
159
  welcomeMessage = "Hello there!",
152
160
  welcomeSubtext = "What can I help you with?",
@@ -159,7 +167,8 @@ const AthenaChat = ({
159
167
  components,
160
168
  groupToolCalls = true,
161
169
  composer: composerSlots,
162
- dictation = true
170
+ dictation = true,
171
+ voice = true
163
172
  }) => {
164
173
  var _a, _b, _c, _d, _e, _f;
165
174
  const athenaConfig = AthenaContext.useAthenaConfig();
@@ -301,6 +310,7 @@ const AthenaChat = ({
301
310
  tools,
302
311
  slots: composerSlots,
303
312
  dictation,
313
+ voice: readOnly ? false : voice,
304
314
  welcomeSuggestionCount: welcomeSuggestions.length
305
315
  }
306
316
  )
@@ -359,7 +369,7 @@ const ATHENA_CHAT_MENTION_CATEGORIES = [
359
369
  "projects",
360
370
  "folders"
361
371
  ];
362
- const AthenaChatComposer = ({ tools, slots, dictation, welcomeSuggestionCount }) => {
372
+ const AthenaChatComposer = ({ tools, slots, dictation, voice: voiceOptions, welcomeSuggestionCount }) => {
363
373
  const aui = react.useAui();
364
374
  const threadId = threadIdContext.useAthenaStatewireThreadId();
365
375
  const { appUrl } = AthenaContext.useAthenaConfig();
@@ -384,6 +394,7 @@ const AthenaChatComposer = ({ tools, slots, dictation, welcomeSuggestionCount })
384
394
  return ((_a = state.optional.thread) == null ? void 0 : _a.isRunning) ?? false;
385
395
  });
386
396
  const connection = threadStatus.useDeepAgentConnection();
397
+ const { voice, active: voiceActive, enabled: voiceEnabled, audioRef } = chatVoiceControls.useComposerChatVoice({ options: voiceOptions });
387
398
  const runLive = react.useAuiState((state) => {
388
399
  const thread = state.optional.thread;
389
400
  return thread ? threadStatus.readRunLive(thread, connection) : false;
@@ -420,7 +431,7 @@ const AthenaChatComposer = ({ tools, slots, dictation, welcomeSuggestionCount })
420
431
  onAutoEnd: appendSpokenText
421
432
  });
422
433
  const recording = dictationEnabled && dictationCtl.isActive;
423
- const showMic = dictationEnabled && dictationCtl.isSupported;
434
+ const showMic = dictationEnabled && dictationCtl.isSupported && !voiceActive;
424
435
  const { isActive: dictationActive, cancel: cancelDictation } = dictationCtl;
425
436
  react$1.useEffect(() => {
426
437
  if (!dictationEnabled && dictationActive) cancelDictation();
@@ -470,8 +481,13 @@ const AthenaChatComposer = ({ tools, slots, dictation, welcomeSuggestionCount })
470
481
  [appUrl, aui, clearAttachments, clearQuote, threadId]
471
482
  );
472
483
  const interceptSubmit = react$1.useCallback(
473
- (text, draftMarkdown) => attachmentsRef.current.length > 0 || quoteRef.current !== null ? sendComposed({ text, draftMarkdown }) : false,
474
- [sendComposed]
484
+ async (text, draftMarkdown) => {
485
+ if (attachmentsRef.current.length > 0 || quoteRef.current !== null) {
486
+ return sendComposed({ text, draftMarkdown });
487
+ }
488
+ return voice.phase === "live" && text.length > 0 ? voice.sendUserText(text) : false;
489
+ },
490
+ [sendComposed, voice.phase, voice.sendUserText]
475
491
  );
476
492
  const hasPendingExtras = react$1.useCallback(
477
493
  () => attachmentsRef.current.length > 0 || quoteRef.current !== null,
@@ -543,104 +559,110 @@ const AthenaChatComposer = ({ tools, slots, dictation, welcomeSuggestionCount })
543
559
  aui.composer.setText(send.text);
544
560
  aui.composer.send({ steer: false });
545
561
  }, [appendSpokenText, aui, dictationCtl, isRunning, sendComposed, sessionLane, slots, threadId]);
546
- return /* @__PURE__ */ jsxRuntime.jsx(ComposerDropZone.ComposerDropZone, { onDragActiveChange: setDragActive, children: /* @__PURE__ */ jsxRuntime.jsx(
547
- "div",
548
- {
549
- className: cnfast.cn("aui-composer-entrance", entrance.className),
550
- style: entrance.style,
551
- onFocus: keyboardHint.onFocus,
552
- children: /* @__PURE__ */ jsxRuntime.jsx(
553
- chatComposerDock.ChatComposerDock,
554
- {
555
- appearance: "paper",
556
- dragActive,
557
- aboveSurface: /* @__PURE__ */ jsxRuntime.jsx(threadScrollToBottom.ThreadScrollToBottom, {}),
558
- beforeEditor: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
559
- /* @__PURE__ */ jsxRuntime.jsx(ComposerQuotePreview.ComposerQuotePreview, {}),
560
- /* @__PURE__ */ jsxRuntime.jsx(ComposerAttachmentPreview.ComposerAttachmentPreview, {})
561
- ] }),
562
- editor: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
563
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: cnfast.cn(recording && "hidden"), children: /* @__PURE__ */ jsxRuntime.jsx(
564
- tiptapComposer.TiptapComposer,
565
- {
566
- ref: composerRef,
567
- threadId,
568
- appearance: "paper",
569
- placeholder: (runLive ? slots == null ? void 0 : slots.runningPlaceholder : void 0) ?? (slots == null ? void 0 : slots.placeholder) ?? DEFAULT_COMPOSER_PLACEHOLDER,
570
- tools,
571
- rootCategories,
572
- onDraftStateChange,
573
- waitForHostUploads,
574
- interceptSubmit,
575
- hasPendingExtras,
576
- holdPendingExtras,
577
- onAttachmentError: setComposerNotice,
578
- fileIntake,
579
- onSubmitted: keyboardHint.onSubmitted
580
- }
581
- ) }),
582
- notice && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "aui-composer-notice px-1.5 pb-1 text-destructive text-xs", role: "alert", children: notice }),
583
- recording && /* @__PURE__ */ jsxRuntime.jsx(
584
- DictationStrip.DictationStrip,
585
- {
586
- status: dictationCtl.status,
587
- transcript: dictationCtl.transcript,
588
- stream: dictationCtl.stream,
589
- stoppingLabel: dictationCtl.engine.stoppingLabel
590
- }
591
- ),
592
- dictationEnabled && dictationCtl.status === dictationState.DICTATION_STATUSES.error && dictationCtl.error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "aui-dictation-error px-1.5 pb-1 text-destructive text-xs", role: "alert", children: dictationCtl.error.message })
593
- ] }),
594
- toolbarStart: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: recording ? /* @__PURE__ */ jsxRuntime.jsx(dictationControls.DictationCancelButton, { onClick: dictationCtl.cancel }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
595
- /* @__PURE__ */ jsxRuntime.jsx(FileUploadButton.FileUploadButton, { className: "aui-composer-attach size-8 rounded-full p-0 disabled:opacity-30" }),
596
- /* @__PURE__ */ jsxRuntime.jsx(
597
- athenaComposerControls.AthenaComposerMentionButton,
598
- {
599
- onClick: () => {
600
- var _a, _b;
601
- return (_b = (_a = composerRef.current) == null ? void 0 : _a.openMentionMenu) == null ? void 0 : _b.call(_a);
562
+ return /* @__PURE__ */ jsxRuntime.jsxs(ComposerDropZone.ComposerDropZone, { onDragActiveChange: setDragActive, children: [
563
+ /* @__PURE__ */ jsxRuntime.jsx("audio", { ref: audioRef, autoPlay: true, playsInline: true }),
564
+ /* @__PURE__ */ jsxRuntime.jsx(
565
+ "div",
566
+ {
567
+ className: cnfast.cn("aui-composer-entrance", entrance.className),
568
+ style: entrance.style,
569
+ onFocus: keyboardHint.onFocus,
570
+ children: /* @__PURE__ */ jsxRuntime.jsx(
571
+ chatComposerDock.ChatComposerDock,
572
+ {
573
+ appearance: "paper",
574
+ dragActive,
575
+ aboveSurface: /* @__PURE__ */ jsxRuntime.jsx(threadScrollToBottom.ThreadScrollToBottom, {}),
576
+ beforeEditor: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
577
+ /* @__PURE__ */ jsxRuntime.jsx(chatVoiceControls.ChatVoiceStatus, { voice }),
578
+ /* @__PURE__ */ jsxRuntime.jsx(ComposerQuotePreview.ComposerQuotePreview, {}),
579
+ /* @__PURE__ */ jsxRuntime.jsx(ComposerAttachmentPreview.ComposerAttachmentPreview, {})
580
+ ] }),
581
+ editor: /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
582
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: cnfast.cn(recording && "hidden"), children: /* @__PURE__ */ jsxRuntime.jsx(
583
+ tiptapComposer.TiptapComposer,
584
+ {
585
+ ref: composerRef,
586
+ threadId,
587
+ appearance: "paper",
588
+ placeholder: (runLive ? slots == null ? void 0 : slots.runningPlaceholder : void 0) ?? (slots == null ? void 0 : slots.placeholder) ?? DEFAULT_COMPOSER_PLACEHOLDER,
589
+ tools,
590
+ rootCategories,
591
+ onDraftStateChange,
592
+ waitForHostUploads,
593
+ interceptSubmit,
594
+ hasPendingExtras,
595
+ holdPendingExtras,
596
+ onAttachmentError: setComposerNotice,
597
+ fileIntake,
598
+ onSubmitted: keyboardHint.onSubmitted
602
599
  }
603
- }
604
- ),
605
- /* @__PURE__ */ jsxRuntime.jsx(chatPickers.AthenaChatModeMenu, {}),
606
- slots == null ? void 0 : slots.toolbarStart
607
- ] }) }),
608
- toolbarEnd: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: recording ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
609
- /* @__PURE__ */ jsxRuntime.jsx(
610
- dictationControls.DictationStopButton,
611
- {
612
- onClick: () => void stopDictation(),
613
- disabled: dictationCtl.status === dictationState.DICTATION_STATUSES.stopping
614
- }
615
- ),
616
- /* @__PURE__ */ jsxRuntime.jsx(
617
- athenaComposerControls.AthenaComposerSendButton,
618
- {
619
- action: isRunning ? "queue" : "send",
620
- onSend: () => void sendDictation(),
621
- disabled: dictationCtl.status !== dictationState.DICTATION_STATUSES.listening || isUploading,
622
- title: isUploading ? "Upload in progress..." : void 0
623
- }
624
- )
625
- ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
626
- slots == null ? void 0 : slots.toolbarEnd,
627
- showMic && /* @__PURE__ */ jsxRuntime.jsx(dictationControls.DictationMicButton, { onClick: dictationCtl.start, disabled: isUploading }),
628
- /* @__PURE__ */ jsxRuntime.jsx(
629
- composerSendOrStop.ComposerSendOrStop,
630
- {
631
- composerRef,
632
- hasDraft: hasText || hasExtras,
633
- sendBlockedReason: isUploading ? "Upload in progress..." : null,
634
- onBeforeSend: slots == null ? void 0 : slots.onBeforeSend,
635
- components: athenaComposerControls.ATHENA_COMPOSER_COMPONENTS
636
- }
637
- )
638
- ] }) }),
639
- belowSurface: /* @__PURE__ */ jsxRuntime.jsx(composerKeyboardHint.ComposerKeyboardHint, { visible: keyboardHint.visible })
640
- }
641
- )
642
- }
643
- ) });
600
+ ) }),
601
+ notice && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "aui-composer-notice px-1.5 pb-1 text-destructive text-xs", role: "alert", children: notice }),
602
+ recording && /* @__PURE__ */ jsxRuntime.jsx(
603
+ DictationStrip.DictationStrip,
604
+ {
605
+ status: dictationCtl.status,
606
+ transcript: dictationCtl.transcript,
607
+ stream: dictationCtl.stream,
608
+ stoppingLabel: dictationCtl.engine.stoppingLabel
609
+ }
610
+ ),
611
+ dictationEnabled && dictationCtl.status === dictationState.DICTATION_STATUSES.error && dictationCtl.error && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "aui-dictation-error px-1.5 pb-1 text-destructive text-xs", role: "alert", children: dictationCtl.error.message })
612
+ ] }),
613
+ toolbarStart: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: recording ? /* @__PURE__ */ jsxRuntime.jsx(dictationControls.DictationCancelButton, { onClick: dictationCtl.cancel }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
614
+ /* @__PURE__ */ jsxRuntime.jsx(FileUploadButton.FileUploadButton, { className: "aui-composer-attach size-8 rounded-full p-0 disabled:opacity-30" }),
615
+ /* @__PURE__ */ jsxRuntime.jsx(
616
+ athenaComposerControls.AthenaComposerMentionButton,
617
+ {
618
+ onClick: () => {
619
+ var _a, _b;
620
+ return (_b = (_a = composerRef.current) == null ? void 0 : _a.openMentionMenu) == null ? void 0 : _b.call(_a);
621
+ }
622
+ }
623
+ ),
624
+ /* @__PURE__ */ jsxRuntime.jsx(chatPickers.AthenaChatModeMenu, {}),
625
+ slots == null ? void 0 : slots.toolbarStart
626
+ ] }) }),
627
+ toolbarEnd: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center gap-1", children: recording ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
628
+ /* @__PURE__ */ jsxRuntime.jsx(
629
+ dictationControls.DictationStopButton,
630
+ {
631
+ onClick: () => void stopDictation(),
632
+ disabled: dictationCtl.status === dictationState.DICTATION_STATUSES.stopping
633
+ }
634
+ ),
635
+ /* @__PURE__ */ jsxRuntime.jsx(
636
+ athenaComposerControls.AthenaComposerSendButton,
637
+ {
638
+ action: isRunning ? "queue" : "send",
639
+ onSend: () => void sendDictation(),
640
+ disabled: dictationCtl.status !== dictationState.DICTATION_STATUSES.listening || isUploading,
641
+ title: isUploading ? "Upload in progress..." : void 0
642
+ }
643
+ )
644
+ ] }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
645
+ slots == null ? void 0 : slots.toolbarEnd,
646
+ showMic && /* @__PURE__ */ jsxRuntime.jsx(dictationControls.DictationMicButton, { onClick: dictationCtl.start, disabled: isUploading }),
647
+ voiceEnabled && /* @__PURE__ */ jsxRuntime.jsx(chatVoiceControls.ChatVoiceButton, { voice, disabled: isUploading || dictationCtl.isActive || !statewireLifecycle.isStatewireChannelLive(connection) }),
648
+ /* @__PURE__ */ jsxRuntime.jsx(
649
+ composerSendOrStop.ComposerSendOrStop,
650
+ {
651
+ composerRef,
652
+ hasDraft: hasText || hasExtras,
653
+ liveVoice: voice.phase === "live" && !hasExtras,
654
+ sendBlockedReason: isUploading ? "Upload in progress..." : null,
655
+ onBeforeSend: slots == null ? void 0 : slots.onBeforeSend,
656
+ components: athenaComposerControls.ATHENA_COMPOSER_COMPONENTS
657
+ }
658
+ )
659
+ ] }) }),
660
+ belowSurface: /* @__PURE__ */ jsxRuntime.jsx(composerKeyboardHint.ComposerKeyboardHint, { visible: keyboardHint.visible })
661
+ }
662
+ )
663
+ }
664
+ )
665
+ ] });
644
666
  };
645
667
  const MessageError = () => /* @__PURE__ */ jsxRuntime.jsx(react.MessagePrimitive.Error, { children: /* @__PURE__ */ jsxRuntime.jsxs(react.ErrorPrimitive.Root, { className: "aui-message-error-root mt-2 flex items-start gap-2 rounded-md border border-destructive bg-destructive/10 p-3 text-destructive text-sm dark:bg-destructive/5 dark:text-red-200", children: [
646
668
  /* @__PURE__ */ jsxRuntime.jsx(lucideReact.AlertCircleIcon, { className: "mt-0.5 size-4 shrink-0" }),