@ai-matrx/chat 0.6.6 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (249) hide show
  1. package/CHANGELOG.md +42 -0
  2. package/README.md +1 -1
  3. package/dist/agents/components/agent-widgets/AgentChatHistorySidebar.js +16 -55
  4. package/dist/agents/components/chat/chat-door.d.ts +67 -0
  5. package/dist/agents/components/chat/chat-door.js +100 -0
  6. package/dist/agents/components/conversation-history/AgentConversationList.d.ts +83 -0
  7. package/dist/agents/components/conversation-history/AgentConversationList.js +252 -0
  8. package/dist/agents/components/conversation-history/ConversationArchiveSection.d.ts +5 -1
  9. package/dist/agents/components/conversation-history/ConversationArchiveSection.js +22 -4
  10. package/dist/agents/components/conversation-history/ConversationHistorySidebar.js +20 -65
  11. package/dist/agents/components/inputs/input-components/TextareaInput.js +2 -0
  12. package/dist/agents/components/inputs/input-components/VariableInputComponent.js +7 -6
  13. package/dist/agents/components/inputs/smart-input/ConversationCompanionPills.d.ts +3 -0
  14. package/dist/agents/components/inputs/smart-input/ConversationCompanionPills.js +40 -0
  15. package/dist/agents/components/inputs/smart-input/ConversationContextRail.js +6 -29
  16. package/dist/agents/components/inputs/smart-input/DeviceToolsModeRow.d.ts +3 -0
  17. package/dist/agents/components/inputs/smart-input/DeviceToolsModeRow.js +35 -0
  18. package/dist/agents/components/inputs/smart-input/InboxQueueStrip.js +0 -8
  19. package/dist/agents/components/inputs/smart-input/RunToolPicker.js +15 -11
  20. package/dist/agents/components/inputs/smart-input/composer/ComposerPlusMenu.js +22 -0
  21. package/dist/agents/components/inputs/smart-input/rail-pill.d.ts +35 -0
  22. package/dist/agents/components/inputs/smart-input/rail-pill.js +30 -0
  23. package/dist/agents/components/inputs/variable-input-variations/AgentVariableForm.js +52 -48
  24. package/dist/agents/components/run-controls/panels/shared.d.ts +1 -1
  25. package/dist/agents/components/shared/AgentModeController.js +3 -2
  26. package/dist/agents/components/shell/AgentRunSidebarMenu.js +15 -54
  27. package/dist/agents/components/working-document/documents-workspace/DocumentsListRail.js +1 -2
  28. package/dist/agents/engine.d.ts +15 -0
  29. package/dist/agents/engine.js +17 -0
  30. package/dist/agents/hooks/useModelControls.js +3 -175
  31. package/dist/agents/redux/agent-definition/selectors.d.ts +237 -237
  32. package/dist/agents/redux/agent-shortcut-categories/selectors.d.ts +50 -50
  33. package/dist/agents/redux/agent-shortcuts/selectors.d.ts +147 -147
  34. package/dist/agents/redux/conversation-history/thunks.js +6 -18
  35. package/dist/agents/redux/conversation-list/conversation-list.selectors.d.ts +1 -73
  36. package/dist/agents/redux/conversation-list/conversation-list.selectors.js +2 -36
  37. package/dist/agents/redux/conversation-list/conversation-list.slice.d.ts +7 -22
  38. package/dist/agents/redux/conversation-list/conversation-list.slice.js +7 -62
  39. package/dist/agents/redux/conversation-list/conversation-list.thunks.d.ts +0 -25
  40. package/dist/agents/redux/conversation-list/conversation-list.thunks.js +2 -58
  41. package/dist/agents/redux/conversation-list/conversation-list.types.d.ts +0 -5
  42. package/dist/agents/redux/conversation-list/conversation-rows.d.ts +16 -0
  43. package/dist/agents/redux/conversation-list/conversation-rows.js +24 -0
  44. package/dist/agents/redux/conversation-list/conversation-trash.thunks.d.ts +1 -0
  45. package/dist/agents/redux/conversation-list/conversation-trash.thunks.js +13 -18
  46. package/dist/agents/redux/execution-system/active-requests/active-requests.selectors.d.ts +54 -54
  47. package/dist/agents/redux/execution-system/context-state/context-state.selectors.d.ts +14 -14
  48. package/dist/agents/redux/execution-system/conversations/conversations.selectors.d.ts +5 -5
  49. package/dist/agents/redux/execution-system/instance-context/instance-context.selectors.d.ts +3 -3
  50. package/dist/agents/redux/execution-system/instance-resources/instance-resources.selectors.d.ts +8 -8
  51. package/dist/agents/redux/execution-system/instance-ui-state/instance-ui-state.selectors.d.ts +5 -5
  52. package/dist/agents/redux/execution-system/instance-variable-values/instance-variable-values.selectors.d.ts +7 -7
  53. package/dist/agents/redux/execution-system/messages/messages.selectors.d.ts +1 -1
  54. package/dist/agents/redux/execution-system/observability/observability.selectors.d.ts +7 -7
  55. package/dist/agents/redux/execution-system/observational-memory/observational-memory.selectors.d.ts +1 -1
  56. package/dist/agents/redux/execution-system/selectors/aggregate.selectors.d.ts +14 -14
  57. package/dist/agents/redux/execution-system/thunks/create-instance.thunk.d.ts +10 -1
  58. package/dist/agents/redux/execution-system/thunks/create-instance.thunk.js +114 -265
  59. package/dist/agents/redux/execution-system/thunks/dispatch-matrx-extend-tool.thunk.js +18 -2
  60. package/dist/agents/redux/execution-system/thunks/dispatch-surface-client-tool.thunk.js +10 -28
  61. package/dist/agents/redux/execution-system/thunks/dispatch-surface-write.thunk.js +10 -25
  62. package/dist/agents/redux/execution-system/thunks/execute-instance.thunk.js +20 -0
  63. package/dist/agents/redux/execution-system/thunks/execute-manual-instance.thunk.js +6 -1
  64. package/dist/agents/redux/execution-system/thunks/notify-and-play-dispatcher.d.ts +58 -0
  65. package/dist/agents/redux/execution-system/thunks/notify-and-play-dispatcher.js +62 -0
  66. package/dist/agents/redux/execution-system/thunks/post-delegated-tool-outcome.d.ts +23 -0
  67. package/dist/agents/redux/execution-system/thunks/post-delegated-tool-outcome.js +37 -0
  68. package/dist/agents/redux/execution-system/thunks/resume-instance.thunk.js +10 -2
  69. package/dist/agents/redux/execution-system/utils/build-tool-injection.d.ts +7 -0
  70. package/dist/agents/redux/execution-system/utils/build-tool-injection.js +18 -6
  71. package/dist/agents/redux/execution-system/utils/host-turn.d.ts +45 -0
  72. package/dist/agents/redux/execution-system/utils/host-turn.js +119 -0
  73. package/dist/agents/redux/proposed-directives/proposedDirectivesSlice.d.ts +1 -1
  74. package/dist/agents/scribe-tools/dispatcher/dispatch-scribe-tool.thunk.d.ts +50 -20
  75. package/dist/agents/scribe-tools/dispatcher/dispatch-scribe-tool.thunk.js +9 -101
  76. package/dist/agents/types/tool-injection.types.d.ts +18 -2
  77. package/dist/agents/ui-first-tools/redux/agent-lists.selectors.d.ts +2 -2
  78. package/dist/agents/ui-first-tools/redux/pending-asks.slice.d.ts +2 -1
  79. package/dist/agents/ui-first-tools/redux/resolve-asks-with-input.thunk.js +1 -1
  80. package/dist/agents/ui-first-tools/tools/ask-response.d.ts +29 -0
  81. package/dist/agents/ui-first-tools/tools/ask-response.js +15 -0
  82. package/dist/agents/ui-first-tools/tools/schemas.d.ts +3 -25
  83. package/dist/agents/ui-first-tools/tools/schemas.js +1 -12
  84. package/dist/agents/ui-first-tools/ui/ApprovalCard.js +1 -1
  85. package/dist/agents/ui-first-tools/ui/AskWizard.d.ts +1 -1
  86. package/dist/agents/ui-first-tools/ui/AskWizard.js +1 -1
  87. package/dist/cx-chat/_legacy-stubs.d.ts +5 -5
  88. package/dist/cx-chat/components/SsrSidebarChats.js +13 -15
  89. package/dist/cx-chat/components/messages/UserMessage.d.ts +2 -2
  90. package/dist/cx-chat/types/conversation.d.ts +6 -6
  91. package/dist/cx-chat/utils/buildContentBlocksForSave.d.ts +1 -1
  92. package/dist/cx-chat/utils/cx-content-converter.d.ts +1 -1
  93. package/dist/host/bare-host-chrome.js +9 -3
  94. package/dist/host/composer-extensions.d.ts +75 -0
  95. package/dist/host/composer-extensions.js +92 -0
  96. package/dist/host/configure.js +1 -0
  97. package/dist/host/contract.d.ts +105 -13
  98. package/dist/host/defaults/confirm.d.ts +15 -0
  99. package/dist/host/defaults/confirm.js +25 -0
  100. package/dist/host/defaults/files-ui.d.ts +85 -0
  101. package/dist/host/defaults/files-ui.js +158 -0
  102. package/dist/host/defaults/gates.d.ts +27 -0
  103. package/dist/host/defaults/gates.js +72 -0
  104. package/dist/host/defaults/media-client.d.ts +15 -0
  105. package/dist/host/defaults/media-client.js +40 -0
  106. package/dist/host/defaults/voice.d.ts +81 -0
  107. package/dist/host/defaults/voice.js +239 -0
  108. package/dist/host/device-tool-handoff.d.ts +26 -0
  109. package/dist/host/device-tool-handoff.js +45 -0
  110. package/dist/host/index.d.ts +1 -0
  111. package/dist/host/index.js +4 -1
  112. package/dist/host/microphone.d.ts +11 -0
  113. package/dist/host/microphone.js +17 -0
  114. package/dist/host/ui-slots.d.ts +29 -74
  115. package/dist/host/ui-slots.js +40 -42
  116. package/dist/store/create-chat-store.d.ts +2 -2
  117. package/dist/store/middlewares.d.ts +5 -5
  118. package/dist/store/slices.d.ts +1 -1
  119. package/dist/surfaces/redux/selectors.d.ts +2 -2
  120. package/dist/tool-call-visualization/components/ToolCallVisualization.js +3 -2
  121. package/dist/tool-call-visualization/configured-rows/ConfiguredRowRenderer.d.ts +15 -0
  122. package/dist/tool-call-visualization/configured-rows/ConfiguredRowRenderer.js +36 -0
  123. package/dist/tool-call-visualization/configured-rows/ConfiguredToolRow.d.ts +40 -0
  124. package/dist/tool-call-visualization/configured-rows/ConfiguredToolRow.js +296 -0
  125. package/dist/tool-call-visualization/configured-rows/CopyToolButton.d.ts +23 -0
  126. package/dist/tool-call-visualization/configured-rows/CopyToolButton.js +62 -0
  127. package/dist/tool-call-visualization/configured-rows/ToolProgressView.d.ts +23 -0
  128. package/dist/tool-call-visualization/configured-rows/ToolProgressView.js +147 -0
  129. package/dist/tool-call-visualization/configured-rows/field-components.d.ts +15 -0
  130. package/dist/tool-call-visualization/configured-rows/field-components.js +244 -0
  131. package/dist/tool-call-visualization/configured-rows/helpers.d.ts +39 -0
  132. package/dist/tool-call-visualization/configured-rows/helpers.js +110 -0
  133. package/dist/tool-call-visualization/configured-rows/register.d.ts +25 -0
  134. package/dist/tool-call-visualization/configured-rows/register.js +51 -0
  135. package/dist/tool-call-visualization/configured-rows/registries.d.ts +27 -0
  136. package/dist/tool-call-visualization/configured-rows/registries.js +14 -0
  137. package/dist/tool-call-visualization/configured-rows/to-row-entry.d.ts +8 -0
  138. package/dist/tool-call-visualization/configured-rows/to-row-entry.js +38 -0
  139. package/dist/tool-call-visualization/configured-rows/transforms.d.ts +14 -0
  140. package/dist/tool-call-visualization/configured-rows/transforms.js +30 -0
  141. package/dist/tool-call-visualization/configured-rows/types.d.ts +140 -0
  142. package/dist/tool-call-visualization/redux/selectors.d.ts +1 -1
  143. package/dist/tool-call-visualization/registry/registry.d.ts +1 -1
  144. package/dist/tool-call-visualization/registry/registry.js +8 -1
  145. package/dist/tool-call-visualization/types.d.ts +1 -1
  146. package/dist/ui/chat-protocol-types.d.ts +0 -1
  147. package/dist/ui/ownership.d.ts +25 -0
  148. package/dist/ui/ownership.js +18 -0
  149. package/dist/voice-agent/audio/audioCapture.js +11 -6
  150. package/dist/window-panels/windows/agents/AgentRunHistoryWindow.d.ts +1 -2
  151. package/dist/window-panels/windows/agents/AgentRunHistoryWindow.js +21 -181
  152. package/dist/window-panels/windows/agents/AgentRunWindow.js +26 -13
  153. package/package.json +4627 -125
  154. package/dist/agents/components/agent-widgets/chat-assistant/AssistantMessageCard.d.ts +0 -7
  155. package/dist/agents/components/agent-widgets/chat-assistant/AssistantMessageCard.js +0 -33
  156. package/dist/agents/components/agent-widgets/chat-assistant/StatusCard.d.ts +0 -6
  157. package/dist/agents/components/agent-widgets/chat-assistant/StatusCard.js +0 -21
  158. package/dist/agents/components/agent-widgets/chat-assistant/UserMessageCard.d.ts +0 -6
  159. package/dist/agents/components/agent-widgets/chat-assistant/UserMessageCard.js +0 -11
  160. package/dist/agents/components/agent-widgets/chat-assistant/VariableInputCard.d.ts +0 -12
  161. package/dist/agents/components/agent-widgets/chat-assistant/VariableInputCard.js +0 -7
  162. package/dist/agents/components/agent-widgets/execution-gates/PreExecutionCard.d.ts +0 -4
  163. package/dist/agents/components/agent-widgets/execution-gates/PreExecutionCard.js +0 -81
  164. package/dist/agents/components/run/run-sidebar/SidebarHeader.d.ts +0 -21
  165. package/dist/agents/components/run/run-sidebar/SidebarHeader.js +0 -50
  166. package/dist/agents/components/run-controls/AdvancedRunSettings/index.d.ts +0 -6
  167. package/dist/agents/components/run-controls/AdvancedRunSettings/index.js +0 -32
  168. package/dist/agents/components/run-controls/RunSettingsModal.d.ts +0 -5
  169. package/dist/agents/components/run-controls/RunSettingsModal.js +0 -22
  170. package/dist/agents/components/shell/AgentBuildSidebarExtras.d.ts +0 -6
  171. package/dist/agents/components/shell/AgentBuildSidebarExtras.js +0 -30
  172. package/dist/agents/constants/agent-system-competitors.d.ts +0 -9
  173. package/dist/agents/constants/agent-system-competitors.js +0 -125
  174. package/dist/agents/hooks/message-crud/useMessageActions.d.ts +0 -29
  175. package/dist/agents/hooks/message-crud/useMessageActions.js +0 -119
  176. package/dist/agents/hooks/useConversationPendingCalls.d.ts +0 -30
  177. package/dist/agents/hooks/useConversationPendingCalls.js +0 -42
  178. package/dist/agents/model-registry/model-classes.d.ts +0 -1
  179. package/dist/agents/model-registry/model-classes.js +0 -1
  180. package/dist/agents/redux/conversation-history/index.d.ts +0 -18
  181. package/dist/agents/redux/conversation-history/index.js +0 -4
  182. package/dist/agents/redux/execution-system/instance-variable-values/instance-variable-values.thunks.d.ts +0 -38
  183. package/dist/agents/redux/execution-system/instance-variable-values/instance-variable-values.thunks.js +0 -42
  184. package/dist/agents/redux/execution-system/message-crud/server/compact-turns.thunk.d.ts +0 -58
  185. package/dist/agents/redux/execution-system/message-crud/server/compact-turns.thunk.js +0 -50
  186. package/dist/agents/redux/execution-system/message-crud/server/fork-and-run-server.thunk.d.ts +0 -76
  187. package/dist/agents/redux/execution-system/message-crud/server/fork-and-run-server.thunk.js +0 -101
  188. package/dist/agents/redux/execution-system/thunks/attach-workflow-run.d.ts +0 -38
  189. package/dist/agents/redux/execution-system/thunks/attach-workflow-run.js +0 -25
  190. package/dist/agents/redux/execution-system/utils/source-slice-helpers.d.ts +0 -45
  191. package/dist/agents/redux/execution-system/utils/source-slice-helpers.js +0 -50
  192. package/dist/agents/runtime/realtime/launchRealtimeSession.thunk.d.ts +0 -25
  193. package/dist/agents/runtime/realtime/launchRealtimeSession.thunk.js +0 -15
  194. package/dist/agents/services/agentService.d.ts +0 -12
  195. package/dist/agents/services/agentService.js +0 -92
  196. package/dist/agents/utils/escape-code-fences.d.ts +0 -15
  197. package/dist/agents/utils/escape-code-fences.js +0 -9
  198. package/dist/agents/utils/normalize-managed-options.d.ts +0 -30
  199. package/dist/agents/utils/normalize-managed-options.js +0 -29
  200. package/dist/agents/utils/processedDocumentContext.d.ts +0 -51
  201. package/dist/agents/utils/processedDocumentContext.js +0 -51
  202. package/dist/agents/utils/variable-validation.d.ts +0 -28
  203. package/dist/agents/utils/variable-validation.js +0 -54
  204. package/dist/cx-chat/components/ChatConversationSkeleton.d.ts +0 -9
  205. package/dist/cx-chat/components/ChatConversationSkeleton.js +0 -45
  206. package/dist/cx-chat/components/ChatPanelBackButton.d.ts +0 -1
  207. package/dist/cx-chat/components/ChatPanelBackButton.js +0 -26
  208. package/dist/cx-chat/components/messages/MessageErrorBoundary.d.ts +0 -20
  209. package/dist/cx-chat/components/messages/MessageErrorBoundary.js +0 -40
  210. package/dist/cx-chat/components/variable-inputs/CheckboxGroupInput.d.ts +0 -14
  211. package/dist/cx-chat/components/variable-inputs/CheckboxGroupInput.js +0 -160
  212. package/dist/cx-chat/components/variable-inputs/NumberInput.d.ts +0 -15
  213. package/dist/cx-chat/components/variable-inputs/NumberInput.js +0 -79
  214. package/dist/cx-chat/components/variable-inputs/RadioGroupInput.d.ts +0 -14
  215. package/dist/cx-chat/components/variable-inputs/RadioGroupInput.js +0 -125
  216. package/dist/cx-chat/components/variable-inputs/SelectInput.d.ts +0 -14
  217. package/dist/cx-chat/components/variable-inputs/SelectInput.js +0 -92
  218. package/dist/cx-chat/components/variable-inputs/TextareaInput.d.ts +0 -14
  219. package/dist/cx-chat/components/variable-inputs/TextareaInput.js +0 -46
  220. package/dist/cx-chat/components/variable-inputs/ToggleInput.d.ts +0 -14
  221. package/dist/cx-chat/components/variable-inputs/ToggleInput.js +0 -46
  222. package/dist/cx-chat/hooks/useChatPersistence.d.ts +0 -28
  223. package/dist/cx-chat/hooks/useChatPersistence.js +0 -180
  224. package/dist/cx-chat/hooks/useInstanceBootstrap.d.ts +0 -1
  225. package/dist/cx-chat/hooks/useInstanceBootstrap.js +0 -115
  226. package/dist/cx-chat/types/content.d.ts +0 -184
  227. package/dist/cx-chat/types/content.js +0 -91
  228. package/dist/cx-chat/types/cx-tables.d.ts +0 -176
  229. package/dist/cx-chat/utils/settings-diff.d.ts +0 -13
  230. package/dist/cx-chat/utils/settings-diff.js +0 -25
  231. package/dist/public-chat/components/AgentSelector.d.ts +0 -22
  232. package/dist/public-chat/components/AgentSelector.js +0 -80
  233. package/dist/public-chat/components/GuidedVariableInputs.d.ts +0 -25
  234. package/dist/public-chat/components/GuidedVariableInputs.js +0 -623
  235. package/dist/public-chat/components/PublicVariableInputs.d.ts +0 -29
  236. package/dist/public-chat/components/PublicVariableInputs.js +0 -172
  237. package/dist/public-chat/types/content.d.ts +0 -184
  238. package/dist/public-chat/types/content.js +0 -91
  239. package/dist/tool-call-visualization/renderers/_shared-entity/EntityOpenActions.d.ts +0 -15
  240. package/dist/tool-call-visualization/renderers/_shared-entity/EntityOpenActions.js +0 -31
  241. package/dist/tool-call-visualization/utils/toolCallBlockToLifecycleEntry.d.ts +0 -24
  242. package/dist/tool-call-visualization/utils/toolCallBlockToLifecycleEntry.js +0 -26
  243. package/dist/ui/AudioOutputBlockSkeleton.d.ts +0 -17
  244. package/dist/ui/AudioOutputBlockSkeleton.js +0 -78
  245. package/dist/ui/lazy/PublicChatVariableInputs.d.ts +0 -2
  246. package/dist/ui/lazy/PublicChatVariableInputs.js +0 -16
  247. package/dist/utils/xml/readXmlTag.d.ts +0 -12
  248. package/dist/utils/xml/readXmlTag.js +0 -66
  249. /package/dist/{cx-chat/types/cx-tables.js → tool-call-visualization/configured-rows/types.js} +0 -0
@@ -0,0 +1,72 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useState, useSyncExternalStore } from "react";
3
+ import { NO_ACCESS, resolveResourceAccess } from "../../ui/access-core.js";
4
+ import { toDbPermissionLevel } from "../../ui/permission-levels.js";
5
+ import { getChatHost, isChatHostConfigured } from "../configure.js";
6
+ const noSubscribe = () => () => void 0;
7
+ function useDefaultAccess(resourceType, resourceId) {
8
+ const [state, setState] = useState({ key: "", access: null, failed: false });
9
+ const [tick, setTick] = useState(0);
10
+ const key = resourceType && resourceId ? `${resourceType}:${resourceId}` : "";
11
+ useEffect(() => {
12
+ if (!key || !isChatHostConfigured()) return void 0;
13
+ let live = true;
14
+ resolveResourceAccess(getChatHost().db, resourceType, resourceId, { strict: true }).then(
15
+ (access) => live && setState({ key, access, failed: false }),
16
+ () => live && setState({ key, access: null, failed: true })
17
+ );
18
+ return () => {
19
+ live = false;
20
+ };
21
+ }, [key, resourceType, resourceId, tick]);
22
+ const answered = state.key === key ? state : null;
23
+ const loading = Boolean(key) && isChatHostConfigured() && !answered;
24
+ const refresh = useCallback(async () => setTick((t) => t + 1), []);
25
+ return { ...answered?.access ?? NO_ACCESS, loading, refresh };
26
+ }
27
+ function useDefaultOrganizationRequired() {
28
+ const host = isChatHostConfigured() ? getChatHost() : null;
29
+ const org = useSyncExternalStore(host?.org.subscribe ?? noSubscribe, () => host?.org.active() ?? null, () => null);
30
+ const identity = useSyncExternalStore(host?.identity.subscribe ?? noSubscribe, () => host?.identity.current() ?? null, () => null);
31
+ const organizationId = org?.id ?? null;
32
+ const canLoad = organizationId != null;
33
+ const signedOut = !canLoad && host != null && identity != null && !identity.userId;
34
+ const organizationState = canLoad ? "ready" : !host ? "unavailable" : signedOut ? "signed_out" : "required";
35
+ const retry = useCallback(() => {
36
+ host?.org.require("work here", { interactive: true }).catch(() => void 0);
37
+ }, [host]);
38
+ return {
39
+ organizationId,
40
+ canLoad,
41
+ organizationRequired: organizationState === "required",
42
+ resolving: false,
43
+ organizationState,
44
+ unavailableReason: host ? null : "no chat host",
45
+ retry
46
+ };
47
+ }
48
+ async function defaultEnsureOrgAvailability(options) {
49
+ if (!isChatHostConfigured()) return { success: false, error: "No chat host to grant access through." };
50
+ const db = getChatHost().db;
51
+ try {
52
+ const { data, error } = await db.rpc("grant_org_availability", {
53
+ p_resource_type: options.resourceType,
54
+ p_resource_id: options.resourceId,
55
+ p_target_org_id: options.organizationId,
56
+ p_permission_level: options.permissionLevel ? toDbPermissionLevel(options.permissionLevel) : void 0
57
+ });
58
+ if (error) return { success: false, error: error.message };
59
+ const o = data && typeof data === "object" ? data : {};
60
+ if (o.success === true) return { success: true, message: typeof o.message === "string" ? o.message : "Available to everyone in the organization" };
61
+ const err = typeof o.error === "string" ? o.error : "Could not make this available to the organization";
62
+ if (err === "Organization already has access") return { success: true, message: err };
63
+ return { success: false, error: err };
64
+ } catch (cause) {
65
+ return { success: false, error: cause instanceof Error ? cause.message : "Could not make this available to the organization" };
66
+ }
67
+ }
68
+ export {
69
+ defaultEnsureOrgAvailability,
70
+ useDefaultAccess,
71
+ useDefaultOrganizationRequired
72
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The media client a bare host's file cards resolve through (gap 6, AUDIT-C): tool-result files and
3
+ * images, attachment thumbnails and media bodies all read `@ai-matrx/media`'s `MediaProvider`. A
4
+ * host with only a db had none, so every one of them threw on mount. `BareHostChrome` mounts this
5
+ * one — `@ai-matrx/data/files` over the files service, carrying the host's bearer and active
6
+ * organization — unless a `MediaProvider` is already above the chat (the host's own wins).
7
+ */
8
+ import { type ReactNode } from "react";
9
+ import type { MediaClient } from "@ai-matrx/media";
10
+ import type { ResolvedChatHost } from "../contract.js";
11
+ export declare function createBareMediaClient(host: ResolvedChatHost): MediaClient;
12
+ export declare function BareMediaProvider({ host, children }: {
13
+ host: ResolvedChatHost;
14
+ children?: ReactNode;
15
+ }): import("react").JSX.Element;
@@ -0,0 +1,40 @@
1
+ "use client";
2
+ import { Fragment, jsx } from "react/jsx-runtime";
3
+ import { useState } from "react";
4
+ import { createMatrxFilesClient } from "@ai-matrx/data/files";
5
+ import { MediaProvider, useMediaClient } from "@ai-matrx/media/core";
6
+ import { DEFAULT_FILES_SERVICE_URL } from "./files-host.js";
7
+ function createBareMediaClient(host) {
8
+ return createMatrxFilesClient({
9
+ filesBaseUrl: DEFAULT_FILES_SERVICE_URL,
10
+ credentials: {
11
+ async get() {
12
+ const accessToken = await host.identity.getAccessToken();
13
+ return accessToken ? { kind: "user", accessToken } : null;
14
+ }
15
+ },
16
+ async fetchImpl(input, init) {
17
+ const headers = new Headers(init?.headers ?? (input instanceof Request ? input.headers : {}));
18
+ const organizationId = host.org.active()?.id;
19
+ if (headers.has("Authorization") && organizationId && !headers.has("X-Organization-Id")) headers.set("X-Organization-Id", organizationId);
20
+ return fetch(input, { ...init, headers });
21
+ }
22
+ });
23
+ }
24
+ function useHasMediaClient() {
25
+ try {
26
+ useMediaClient();
27
+ return true;
28
+ } catch {
29
+ return false;
30
+ }
31
+ }
32
+ function BareMediaProvider({ host, children }) {
33
+ const hasClient = useHasMediaClient();
34
+ const [client] = useState(() => hasClient ? null : createBareMediaClient(host));
35
+ return client ? /* @__PURE__ */ jsx(MediaProvider, { client, children }) : /* @__PURE__ */ jsx(Fragment, { children });
36
+ }
37
+ export {
38
+ BareMediaProvider,
39
+ createBareMediaClient
40
+ };
@@ -0,0 +1,81 @@
1
+ import { BrowserAudioKernel } from "@ai-matrx/browser-audio/core";
2
+ import { ReadAloudButton } from "@ai-matrx/media/react";
3
+ import type { ComponentProps } from "react";
4
+ import type { ResolvedChatHost } from "../contract.js";
5
+ /** THE page's audio kernel over the configured chat host (created on first use). */
6
+ export declare function getChatAudioKernel(): BrowserAudioKernel;
7
+ /** Test seam: drop the page's kernel. */
8
+ export declare function resetChatAudioKernelForTests(): void;
9
+ /** Same return shape as the website's `useRecordAndTranscribe` (the members chat reads). */
10
+ export declare function useDefaultRecordAndTranscribe(props?: {
11
+ onTranscriptionComplete?: (result: {
12
+ success: boolean;
13
+ text: string;
14
+ error?: string;
15
+ }) => void;
16
+ onChunkTranscribed?: (chunkText: string, accumulatedText: string) => void;
17
+ onError?: (error: string, errorCode?: string) => void;
18
+ autoTranscribe?: boolean;
19
+ }): {
20
+ isRecording: boolean;
21
+ isPaused: boolean;
22
+ duration: number;
23
+ audioLevel: number;
24
+ isTranscribing: boolean;
25
+ liveTranscript: string;
26
+ failedChunkCount: number;
27
+ isProcessing: boolean;
28
+ startRecording: () => Promise<void>;
29
+ stopRecording: () => void | undefined;
30
+ pauseRecording: () => void | undefined;
31
+ resumeRecording: () => Promise<void> | undefined;
32
+ reset: () => void | undefined;
33
+ };
34
+ export interface DefaultMicrophoneIconButtonProps {
35
+ id?: string;
36
+ onTranscriptionComplete: (text: string) => void;
37
+ onTranscriptOnlyComplete?: (text: string) => void;
38
+ onLiveTranscript?: (text: string) => void;
39
+ onRecordingStateChange?: (state: {
40
+ isRecording: boolean;
41
+ isTranscribing: boolean;
42
+ }) => void;
43
+ onError?: (error: string, code?: string) => void;
44
+ className?: string;
45
+ iconClassName?: string;
46
+ disabled?: boolean;
47
+ label?: string;
48
+ }
49
+ /** The composer's mic: one press records, the next stops; the transcript arrives through the props. */
50
+ export declare const DefaultMicrophoneIconButton: import("react").ForwardRefExoticComponent<DefaultMicrophoneIconButtonProps & import("react").RefAttributes<{
51
+ stopForTranscriptOnly: () => void;
52
+ }>>;
53
+ /** "Transcribing (0:12)…" while a recording turns into text. */
54
+ export declare function DefaultTranscriptionLoader({ message, duration, className, }: {
55
+ message?: string;
56
+ duration?: number;
57
+ className?: string;
58
+ size?: "sm" | "md" | "lg";
59
+ }): import("react").JSX.Element;
60
+ /** The mic's device caret: lists the microphones and keeps the choice for every recording. */
61
+ export declare function DefaultMicDeviceMenu({ className, disabled, ariaLabel }: {
62
+ className?: string;
63
+ disabled?: boolean;
64
+ ariaLabel?: string;
65
+ }): import("react").JSX.Element;
66
+ type ReadAloudVariant = ComponentProps<typeof ReadAloudButton>["variant"];
67
+ /** Read one assistant message aloud (THE one speech engine, footer size). */
68
+ export declare function DefaultSpeakerButton({ text: given, className, variant, disabled }: {
69
+ text?: string;
70
+ className?: string;
71
+ variant?: ReadAloudVariant;
72
+ processMarkdown?: boolean;
73
+ disabled?: boolean;
74
+ }): import("react").JSX.Element;
75
+ /**
76
+ * Give THE speech engine what it needs on a host that wired none of it: a Cartesia token from the
77
+ * platform's broker over this host's server, and the engine's own voice defaults. Ports a host
78
+ * already set are left alone (`isSpeechConfigured`).
79
+ */
80
+ export declare function installBareHostSpeech(host: ResolvedChatHost): void;
81
+ export {};
@@ -0,0 +1,239 @@
1
+ "use client";
2
+ import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { forwardRef, useCallback, useEffect, useId, useImperativeHandle, useRef, useState, useSyncExternalStore } from "react";
4
+ import { ChevronDown, Loader2, Mic, Square } from "lucide-react";
5
+ import { BrowserAudioKernel, getPreferredInputDeviceId, setPreferredInputDeviceId } from "@ai-matrx/browser-audio/core";
6
+ import { createMatrxAudioClient } from "@ai-matrx/browser-audio/matrx";
7
+ import { configureSpeech, isSpeechConfigured } from "@ai-matrx/media/speech";
8
+ import { ReadAloudButton, useReadAloud } from "@ai-matrx/media/react";
9
+ import { Button } from "@ai-matrx/design-system/controls";
10
+ import { Popover, PopoverContent, PopoverTrigger } from "@ai-matrx/design-system";
11
+ import { formatDurationSeconds } from "@ai-matrx/kit/format";
12
+ import { getChatHost, isChatHostConfigured } from "../configure.js";
13
+ let sharedKernel = null;
14
+ function getChatAudioKernel() {
15
+ if (sharedKernel) return sharedKernel;
16
+ if (!isChatHostConfigured()) throw new Error("@ai-matrx/chat: voice input needs a chat host (ChatProvider).");
17
+ const host = getChatHost();
18
+ const client = createMatrxAudioClient({
19
+ baseUrl: host.server.baseUrl(),
20
+ getAccessToken: () => host.identity.getAccessToken(),
21
+ getOrganizationId: () => host.org.active()?.id ?? null
22
+ });
23
+ sharedKernel = new BrowserAudioKernel({ transport: client, persistence: client });
24
+ return sharedKernel;
25
+ }
26
+ function resetChatAudioKernelForTests() {
27
+ sharedKernel?.dispose();
28
+ sharedKernel = null;
29
+ }
30
+ const IDLE_SNAPSHOT = Object.freeze({
31
+ status: "idle",
32
+ ownerId: null,
33
+ liveTranscript: "",
34
+ audioLevel: 0,
35
+ durationMs: 0,
36
+ error: null,
37
+ recoverableCount: 0
38
+ });
39
+ const noSubscribe = () => () => void 0;
40
+ function useKernelSnapshot() {
41
+ const kernel = isChatHostConfigured() ? getChatAudioKernel() : null;
42
+ const snapshot = useSyncExternalStore(
43
+ kernel?.subscribe ?? noSubscribe,
44
+ kernel?.getSnapshot ?? (() => IDLE_SNAPSHOT),
45
+ () => IDLE_SNAPSHOT
46
+ );
47
+ return { kernel, snapshot };
48
+ }
49
+ function useDefaultRecordAndTranscribe(props = {}) {
50
+ const ownerId = useId();
51
+ const { kernel, snapshot } = useKernelSnapshot();
52
+ const cb = useRef(props);
53
+ cb.current = props;
54
+ const owns = snapshot.ownerId === ownerId;
55
+ const isRecording = owns && (snapshot.status === "recording" || snapshot.status === "paused");
56
+ const isPaused = owns && snapshot.status === "paused";
57
+ const isTranscribing = owns && snapshot.status === "transcribing";
58
+ const liveTranscript = owns ? snapshot.liveTranscript : "";
59
+ const previous = useRef("");
60
+ useEffect(() => {
61
+ if (!liveTranscript || liveTranscript === previous.current) return;
62
+ const delta = liveTranscript.startsWith(previous.current) ? liveTranscript.slice(previous.current.length) : liveTranscript;
63
+ previous.current = liveTranscript;
64
+ cb.current.onChunkTranscribed?.(delta, liveTranscript);
65
+ }, [liveTranscript]);
66
+ const startRecording = useCallback(async () => {
67
+ if (!kernel) {
68
+ cb.current.onError?.("Voice input is not available here.", "NO_HOST");
69
+ return;
70
+ }
71
+ previous.current = "";
72
+ await kernel.start({
73
+ ownerId,
74
+ onFinalTranscript: (text) => {
75
+ if (cb.current.autoTranscribe === false) return;
76
+ cb.current.onTranscriptionComplete?.({ success: true, text });
77
+ },
78
+ onError: (error) => cb.current.onError?.(error.message, error.code)
79
+ });
80
+ }, [kernel, ownerId]);
81
+ return {
82
+ isRecording,
83
+ isPaused,
84
+ duration: owns ? Math.round(snapshot.durationMs / 1e3) : 0,
85
+ audioLevel: owns ? snapshot.audioLevel : 0,
86
+ isTranscribing,
87
+ liveTranscript,
88
+ failedChunkCount: 0,
89
+ isProcessing: isRecording || isTranscribing,
90
+ startRecording,
91
+ stopRecording: () => kernel?.stop(ownerId),
92
+ pauseRecording: () => kernel?.pause(ownerId),
93
+ resumeRecording: () => kernel?.resume(ownerId),
94
+ reset: () => kernel?.cancel(ownerId)
95
+ };
96
+ }
97
+ const DefaultMicrophoneIconButton = forwardRef(
98
+ function DefaultMicrophoneIconButton2(props, ref) {
99
+ const transcriptOnly = useRef(false);
100
+ const recorder = useDefaultRecordAndTranscribe({
101
+ onTranscriptionComplete: ({ text }) => {
102
+ if (transcriptOnly.current && props.onTranscriptOnlyComplete) props.onTranscriptOnlyComplete(text);
103
+ else props.onTranscriptionComplete(text);
104
+ transcriptOnly.current = false;
105
+ },
106
+ onChunkTranscribed: (_delta, all) => props.onLiveTranscript?.(all),
107
+ ...props.onError ? { onError: props.onError } : {}
108
+ });
109
+ const { isRecording, isTranscribing } = recorder;
110
+ const onState = useRef(props.onRecordingStateChange);
111
+ onState.current = props.onRecordingStateChange;
112
+ useEffect(() => {
113
+ onState.current?.({ isRecording, isTranscribing });
114
+ }, [isRecording, isTranscribing]);
115
+ useImperativeHandle(ref, () => ({
116
+ stopForTranscriptOnly: () => {
117
+ transcriptOnly.current = true;
118
+ recorder.stopRecording();
119
+ }
120
+ }));
121
+ const label = isRecording ? "Stop recording" : isTranscribing ? "Transcribing" : props.label ?? "Start recording";
122
+ const icon = isTranscribing ? /* @__PURE__ */ jsx(Loader2, { className: "animate-spin" }) : isRecording ? /* @__PURE__ */ jsx(Square, { className: "text-destructive" }) : /* @__PURE__ */ jsx(Mic, { className: props.iconClassName });
123
+ return /* @__PURE__ */ jsx(
124
+ Button,
125
+ {
126
+ id: props.id,
127
+ variant: "quiet",
128
+ icon,
129
+ "aria-label": label,
130
+ title: label,
131
+ "aria-pressed": isRecording,
132
+ disabled: props.disabled || isTranscribing,
133
+ className: props.className,
134
+ "data-chat-default": "microphone",
135
+ onClick: () => isRecording ? recorder.stopRecording() : void recorder.startRecording()
136
+ }
137
+ );
138
+ }
139
+ );
140
+ function DefaultTranscriptionLoader({
141
+ message = "Transcribing",
142
+ duration,
143
+ className
144
+ }) {
145
+ return /* @__PURE__ */ jsxs("div", { className: `flex items-center gap-2 text-sm text-muted-foreground ${className ?? ""}`, role: "status", "data-chat-default": "transcription-loader", children: [
146
+ /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }),
147
+ /* @__PURE__ */ jsxs("span", { children: [
148
+ message,
149
+ duration !== void 0 ? ` (${formatDurationSeconds(duration, { style: "clock" })})` : ""
150
+ ] })
151
+ ] });
152
+ }
153
+ function DefaultMicDeviceMenu({ className, disabled, ariaLabel = "Choose microphone" }) {
154
+ const [devices, setDevices] = useState([]);
155
+ const [chosen, setChosen] = useState(() => getPreferredInputDeviceId());
156
+ const load = () => {
157
+ void navigator.mediaDevices?.enumerateDevices().then((all) => setDevices(all.filter((d) => d.kind === "audioinput"))).catch(() => setDevices([]));
158
+ };
159
+ return /* @__PURE__ */ jsxs(Popover, { onOpenChange: (open) => open && load(), children: [
160
+ /* @__PURE__ */ jsx(PopoverTrigger, { asChild: true, children: /* @__PURE__ */ jsx(Button, { variant: "quiet", disabled, "aria-label": ariaLabel, icon: /* @__PURE__ */ jsx(ChevronDown, {}), className, "data-chat-default": "mic-device-menu" }) }),
161
+ /* @__PURE__ */ jsx(PopoverContent, { sizing: "content", align: "end", side: "bottom", sideOffset: 6, className: "p-1", onOpenAutoFocus: (e) => e.preventDefault(), children: /* @__PURE__ */ jsxs("div", { role: "listbox", "aria-label": ariaLabel, className: "flex min-w-48 flex-col", children: [
162
+ devices.length === 0 ? /* @__PURE__ */ jsx("span", { className: "px-2 py-1.5 text-sm text-muted-foreground", children: "No microphone found" }) : null,
163
+ devices.map((d, i) => {
164
+ const selected = (chosen ?? "default") === d.deviceId || !chosen && i === 0;
165
+ return /* @__PURE__ */ jsx(
166
+ "button",
167
+ {
168
+ type: "button",
169
+ role: "option",
170
+ "aria-selected": selected,
171
+ className: `rounded px-2 py-1.5 text-left text-sm hover:bg-accent ${selected ? "font-medium" : ""}`,
172
+ onClick: () => {
173
+ setPreferredInputDeviceId(d.deviceId || null);
174
+ setChosen(d.deviceId || null);
175
+ },
176
+ children: d.label || `Microphone ${i + 1}`
177
+ },
178
+ d.deviceId || i
179
+ );
180
+ })
181
+ ] }) })
182
+ ] });
183
+ }
184
+ function DefaultSpeakerButton({ text: given, className, variant, disabled }) {
185
+ const text = given ?? "";
186
+ const { status, error, onPress } = useReadAloud(text);
187
+ return /* @__PURE__ */ jsx(
188
+ ReadAloudButton,
189
+ {
190
+ status,
191
+ error,
192
+ onPress,
193
+ disabled: disabled || !text.trim(),
194
+ ...variant !== void 0 ? { variant } : { size: "compact" },
195
+ ...className !== void 0 && { className }
196
+ }
197
+ );
198
+ }
199
+ const CARTESIA_REQUEST = { audience: "cartesia_tts", tierPolicy: "none", scopes: ["tts"], ttlSeconds: 600 };
200
+ function installBareHostSpeech(host) {
201
+ let cached = null;
202
+ const ports = {};
203
+ if (!isSpeechConfigured("getCartesiaToken")) {
204
+ ports.getCartesiaToken = async (opts) => {
205
+ if (cached && !opts?.forceRefresh && Date.now() - cached.at < (CARTESIA_REQUEST.ttlSeconds - 60) * 1e3) return cached.token;
206
+ const api = host.server.api;
207
+ if (!api.mintCredential) throw new Error("Read-aloud needs a server that can mint a speech credential.");
208
+ const credential = await api.mintCredential(CARTESIA_REQUEST.audience, CARTESIA_REQUEST.tierPolicy, {
209
+ scopes: [...CARTESIA_REQUEST.scopes],
210
+ ttlSeconds: CARTESIA_REQUEST.ttlSeconds
211
+ });
212
+ if (!credential?.token) throw new Error("The credential service returned no speech token.");
213
+ cached = { token: credential.token, at: Date.now() };
214
+ return credential.token;
215
+ };
216
+ ports.invalidateCartesiaToken = () => {
217
+ cached = null;
218
+ };
219
+ }
220
+ if (!isSpeechConfigured("resolveVoiceSettings")) {
221
+ ports.resolveVoiceSettings = async () => ({
222
+ voice: null,
223
+ language: typeof navigator !== "undefined" && navigator.language?.split("-")[0] || "en",
224
+ speed: 1
225
+ });
226
+ }
227
+ if (!isSpeechConfigured("notifyError")) ports.notifyError = (title, description) => host.notify.error(`${title}: ${description}`);
228
+ if (Object.keys(ports).length > 0) configureSpeech(ports);
229
+ }
230
+ export {
231
+ DefaultMicDeviceMenu,
232
+ DefaultMicrophoneIconButton,
233
+ DefaultSpeakerButton,
234
+ DefaultTranscriptionLoader,
235
+ getChatAudioKernel,
236
+ installBareHostSpeech,
237
+ resetChatAudioKernelForTests,
238
+ useDefaultRecordAndTranscribe
239
+ };
@@ -0,0 +1,26 @@
1
+ /**
2
+ * host/device-tool-handoff — delegated device-tool calls that outlive the document.
3
+ *
4
+ * A host that IS the device (the Chrome side panel) runs a cloud agent's tools through
5
+ * `deviceTools.invoke`; the package submits each result from the same document. When that document
6
+ * goes away (the panel closes) mid-call, the promise dies with it and the result is lost — the call
7
+ * stays pending on the server and, on reopen, would run a SECOND time (cold resume).
8
+ *
9
+ * So every in-flight call is tracked here, and on `pagehide` the package hands the whole set to the
10
+ * host (`deviceTools.handOff`). From that moment the host owns each call's result: this document
11
+ * will not submit it (`handedOff()` is true), whether or not the document actually dies. A host
12
+ * without `handOff` is untouched — nothing is tracked on its behalf and nothing is skipped.
13
+ */
14
+ import type { ChatDeviceToolHandOff } from "./contract.js";
15
+ /** Hand every in-flight call to the host now. Returns what was handed (empty when the host has no `handOff`). */
16
+ export declare function handOffInFlightDeviceCalls(): readonly ChatDeviceToolHandOff[];
17
+ export interface TrackedDeviceCall {
18
+ /** True once the host was handed this call; the caller must not submit a result for it. */
19
+ handedOff(): boolean;
20
+ /** The call settled (or failed) in this document. */
21
+ done(): void;
22
+ }
23
+ /** Track a call from `invoke` start to its settle. Cheap and inert for a host with no `handOff`. */
24
+ export declare function trackInFlightDeviceCall(call: ChatDeviceToolHandOff): TrackedDeviceCall;
25
+ /** Tests only. */
26
+ export declare function resetDeviceToolHandoffForTests(): void;
@@ -0,0 +1,45 @@
1
+ import { getChatHost, isChatHostConfigured } from "./configure.js";
2
+ const inFlight = /* @__PURE__ */ new Map();
3
+ let bound = false;
4
+ function handOffInFlightDeviceCalls() {
5
+ if (inFlight.size === 0 || !isChatHostConfigured()) return [];
6
+ const port = getChatHost().deviceTools;
7
+ if (!port?.handOff) return [];
8
+ const pending = [...inFlight.values()].filter((e) => !e.handedOff);
9
+ if (pending.length === 0) return [];
10
+ const calls = pending.map((e) => e.call);
11
+ for (const e of pending) e.handedOff = true;
12
+ try {
13
+ port.handOff(calls);
14
+ } catch (error) {
15
+ console.error("[Matrx] deviceTools.handOff threw; these calls are not owned by anyone:", calls, error);
16
+ }
17
+ return calls;
18
+ }
19
+ function bind() {
20
+ if (bound || typeof window === "undefined") return;
21
+ bound = true;
22
+ window.addEventListener("pagehide", (event) => {
23
+ if (event.persisted) return;
24
+ handOffInFlightDeviceCalls();
25
+ });
26
+ }
27
+ function trackInFlightDeviceCall(call) {
28
+ const entry = { call, handedOff: false };
29
+ inFlight.set(call.callId, entry);
30
+ bind();
31
+ return {
32
+ handedOff: () => entry.handedOff,
33
+ done: () => {
34
+ if (inFlight.get(call.callId) === entry) inFlight.delete(call.callId);
35
+ }
36
+ };
37
+ }
38
+ function resetDeviceToolHandoffForTests() {
39
+ inFlight.clear();
40
+ }
41
+ export {
42
+ handOffInFlightDeviceCalls,
43
+ resetDeviceToolHandoffForTests,
44
+ trackInFlightDeviceCall
45
+ };
@@ -3,6 +3,7 @@
3
3
  */
4
4
  export * from "./contract.js";
5
5
  export { configureChat, DEFAULT_CHAT_APP, getChatHost, isChatHostConfigured, resolveChatHost, _resetChatHostForTests, } from "./configure.js";
6
+ export { isMicrophonePermissionRefusal, withMicrophoneGrant } from "./microphone.js";
6
7
  export { attributionForSend, currentChatApp, type WireAttribution } from "./app-identity.js";
7
8
  export { ChatHostInvalidError, ChatHostNotConfiguredError, ChatOrganizationRequiredError, } from "./errors.js";
8
9
  export { createLogClientErrorDiagnostics, isChatSourceApp, CHAT_SOURCE_FEATURE, DIAGNOSTICS_FLUSH_DELAY_MS, DIAGNOSTICS_MAX_PER_FLUSH, } from "./defaults/diagnostics.js";
@@ -7,6 +7,7 @@ import {
7
7
  resolveChatHost,
8
8
  _resetChatHostForTests
9
9
  } from "./configure.js";
10
+ import { isMicrophonePermissionRefusal, withMicrophoneGrant } from "./microphone.js";
10
11
  import { attributionForSend, currentChatApp } from "./app-identity.js";
11
12
  import {
12
13
  ChatHostInvalidError,
@@ -107,8 +108,10 @@ export {
107
108
  isChatNoDatabaseError,
108
109
  isChatSourceApp,
109
110
  isChatWindowId,
111
+ isMicrophonePermissionRefusal,
110
112
  isNoDatabase,
111
113
  resolveChatHost,
112
114
  schedulerDb,
113
- supabase
115
+ supabase,
116
+ withMicrophoneGrant
114
117
  };
@@ -0,0 +1,11 @@
1
+ /**
2
+ * The microphone grant door. A browser that cannot show its permission prompt where the chat lives
3
+ * (a Chrome side panel) gives the microphone no way to be allowed there; the HOST knows where the
4
+ * prompt CAN appear (a small popup window) and says so through `ChatHost.microphone`. Package code
5
+ * that opens the microphone wraps its `getUserMedia` in `withMicrophoneGrant`: on a refusal it asks
6
+ * the host once, and only if the host reports the person allowed it tries again — once.
7
+ */
8
+ import type { ChatMicrophonePort } from "./contract.js";
9
+ /** True for the browser's permission verdict (`NotAllowedError`), not a missing or busy device. */
10
+ export declare function isMicrophonePermissionRefusal(error: unknown): boolean;
11
+ export declare function withMicrophoneGrant<T>(acquire: () => Promise<T>, port: ChatMicrophonePort | null | undefined): Promise<T>;
@@ -0,0 +1,17 @@
1
+ function isMicrophonePermissionRefusal(error) {
2
+ return error?.name === "NotAllowedError";
3
+ }
4
+ async function withMicrophoneGrant(acquire, port) {
5
+ try {
6
+ return await acquire();
7
+ } catch (error) {
8
+ if (!port || !isMicrophonePermissionRefusal(error)) throw error;
9
+ const granted = await port.requestPermission().catch(() => false);
10
+ if (!granted) throw error;
11
+ return acquire();
12
+ }
13
+ }
14
+ export {
15
+ isMicrophonePermissionRefusal,
16
+ withMicrophoneGrant
17
+ };