@athenaintel/react 0.60.1 → 0.61.0

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 (105) hide show
  1. package/README.md +157 -5
  2. package/dist/agent-ui/AgentTarget.cjs +11 -0
  3. package/dist/agent-ui/AgentTarget.cjs.map +1 -0
  4. package/dist/agent-ui/AgentTarget.d.ts +17 -0
  5. package/dist/agent-ui/AgentTarget.js +11 -0
  6. package/dist/agent-ui/AgentTarget.js.map +1 -0
  7. package/dist/agent-ui/AgentUiLayer.cjs +387 -0
  8. package/dist/agent-ui/AgentUiLayer.cjs.map +1 -0
  9. package/dist/agent-ui/AgentUiLayer.d.ts +40 -0
  10. package/dist/agent-ui/AgentUiLayer.js +387 -0
  11. package/dist/agent-ui/AgentUiLayer.js.map +1 -0
  12. package/dist/agent-ui/contract.cjs +104 -0
  13. package/dist/agent-ui/contract.cjs.map +1 -0
  14. package/dist/agent-ui/contract.d.ts +113 -0
  15. package/dist/agent-ui/contract.js +104 -0
  16. package/dist/agent-ui/contract.js.map +1 -0
  17. package/dist/agent-ui/driver.cjs +335 -0
  18. package/dist/agent-ui/driver.cjs.map +1 -0
  19. package/dist/agent-ui/driver.d.ts +66 -0
  20. package/dist/agent-ui/driver.js +335 -0
  21. package/dist/agent-ui/driver.js.map +1 -0
  22. package/dist/agent-ui/index.cjs +33 -0
  23. package/dist/agent-ui/index.cjs.map +1 -0
  24. package/dist/agent-ui/index.d.ts +19 -0
  25. package/dist/agent-ui/index.js +33 -0
  26. package/dist/agent-ui/index.js.map +1 -0
  27. package/dist/agent-ui/pace.cjs +121 -0
  28. package/dist/agent-ui/pace.cjs.map +1 -0
  29. package/dist/agent-ui/pace.d.ts +66 -0
  30. package/dist/agent-ui/pace.js +121 -0
  31. package/dist/agent-ui/pace.js.map +1 -0
  32. package/dist/agent-ui/screen.cjs +471 -0
  33. package/dist/agent-ui/screen.cjs.map +1 -0
  34. package/dist/agent-ui/screen.d.ts +97 -0
  35. package/dist/agent-ui/screen.js +471 -0
  36. package/dist/agent-ui/screen.js.map +1 -0
  37. package/dist/agent-ui/secret-fields.cjs +50 -0
  38. package/dist/agent-ui/secret-fields.cjs.map +1 -0
  39. package/dist/agent-ui/secret-fields.d.ts +15 -0
  40. package/dist/agent-ui/secret-fields.js +50 -0
  41. package/dist/agent-ui/secret-fields.js.map +1 -0
  42. package/dist/agent-ui/stage.cjs +196 -0
  43. package/dist/agent-ui/stage.cjs.map +1 -0
  44. package/dist/agent-ui/stage.d.ts +110 -0
  45. package/dist/agent-ui/stage.js +196 -0
  46. package/dist/agent-ui/stage.js.map +1 -0
  47. package/dist/agent-ui/tools.cjs +448 -0
  48. package/dist/agent-ui/tools.cjs.map +1 -0
  49. package/dist/agent-ui/tools.d.ts +103 -0
  50. package/dist/agent-ui/tools.js +448 -0
  51. package/dist/agent-ui/tools.js.map +1 -0
  52. package/dist/agent-ui/use-agent-target.cjs +63 -0
  53. package/dist/agent-ui/use-agent-target.cjs.map +1 -0
  54. package/dist/agent-ui/use-agent-target.d.ts +27 -0
  55. package/dist/agent-ui/use-agent-target.js +63 -0
  56. package/dist/agent-ui/use-agent-target.js.map +1 -0
  57. package/dist/agent-ui/use-agent-ui.cjs +15 -0
  58. package/dist/agent-ui/use-agent-ui.cjs.map +1 -0
  59. package/dist/agent-ui/use-agent-ui.d.ts +24 -0
  60. package/dist/agent-ui/use-agent-ui.js +15 -0
  61. package/dist/agent-ui/use-agent-ui.js.map +1 -0
  62. package/dist/chat/AthenaChat.cjs +17 -4
  63. package/dist/chat/AthenaChat.cjs.map +1 -1
  64. package/dist/chat/AthenaChat.d.ts +13 -0
  65. package/dist/chat/AthenaChat.js +17 -4
  66. package/dist/chat/AthenaChat.js.map +1 -1
  67. package/dist/chat/StatewireClientToolBridge.cjs +17 -12
  68. package/dist/chat/StatewireClientToolBridge.cjs.map +1 -1
  69. package/dist/chat/StatewireClientToolBridge.d.ts +4 -2
  70. package/dist/chat/StatewireClientToolBridge.js +17 -12
  71. package/dist/chat/StatewireClientToolBridge.js.map +1 -1
  72. package/dist/chat/StatewireClientToolConsentCard.cjs +3 -2
  73. package/dist/chat/StatewireClientToolConsentCard.cjs.map +1 -1
  74. package/dist/chat/StatewireClientToolConsentCard.d.ts +2 -1
  75. package/dist/chat/StatewireClientToolConsentCard.js +3 -2
  76. package/dist/chat/StatewireClientToolConsentCard.js.map +1 -1
  77. package/dist/chat/chat-voice-controls.cjs +5 -2
  78. package/dist/chat/chat-voice-controls.cjs.map +1 -1
  79. package/dist/chat/chat-voice-controls.d.ts +6 -1
  80. package/dist/chat/chat-voice-controls.js +6 -3
  81. package/dist/chat/chat-voice-controls.js.map +1 -1
  82. package/dist/chat/client-tool-write-consent.cjs +16 -5
  83. package/dist/chat/client-tool-write-consent.cjs.map +1 -1
  84. package/dist/chat/client-tool-write-consent.d.ts +12 -4
  85. package/dist/chat/client-tool-write-consent.js +16 -5
  86. package/dist/chat/client-tool-write-consent.js.map +1 -1
  87. package/dist/chat-ui/messages/message-components.cjs +1 -1
  88. package/dist/chat-ui/messages/message-components.cjs.map +1 -1
  89. package/dist/chat-ui/messages/message-components.d.ts +2 -2
  90. package/dist/chat-ui/messages/message-components.js +1 -1
  91. package/dist/chat-ui/messages/message-components.js.map +1 -1
  92. package/dist/index.cjs +7 -0
  93. package/dist/index.cjs.map +1 -1
  94. package/dist/index.d.ts +6 -0
  95. package/dist/index.js +7 -0
  96. package/dist/index.js.map +1 -1
  97. package/dist/provider/AthenaProvider.cjs +37 -8
  98. package/dist/provider/AthenaProvider.cjs.map +1 -1
  99. package/dist/provider/AthenaProvider.d.ts +18 -1
  100. package/dist/provider/AthenaProvider.js +33 -4
  101. package/dist/provider/AthenaProvider.js.map +1 -1
  102. package/dist/react/package.json.cjs +1 -1
  103. package/dist/react/package.json.js +1 -1
  104. package/dist/styles.css +1 -1
  105. package/package.json +6 -1
@@ -1 +1 @@
1
- {"version":3,"file":"chat-voice-controls.js","sources":["../../src/chat/chat-voice-controls.tsx"],"sourcesContent":["import { useAui } from '@assistant-ui/react';\nimport {\n describeVoiceLiveElsewhere,\n playVoiceMoveEarcon,\n useVoiceElapsed,\n useVoiceMovedNotice,\n VOICE_CONTINUE_BY_VOICE_LABEL,\n VOICE_LIVE_TYPED_HINT,\n VOICE_MOVE_CALL_HERE_LABEL,\n VOICE_MOVE_HERE_ACTION,\n} from '@athenaintel/agent-runtime';\nimport { HeadphonesIcon, Loader2Icon, MicIcon, MicOffIcon, PhoneIcon, XIcon } from 'lucide-react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { createDeepAgentVoiceLiveKitAdapter } from '../voice/livekit';\nimport {\n useAthenaChatVoice,\n type UseAthenaChatVoiceOptions,\n type UseAthenaChatVoiceReturn,\n} from '../voice/chat';\nimport { useChatVoiceEnabled } from '../voice/use-chat-voice-enabled';\nimport { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport { TooltipIconButton } from './tooltip-icon-button';\nimport { useMentionAuth } from '../chat-ui/mentions/suggestions/data/api-client';\nimport { credentialFingerprint, useCatalogPrincipal } from '../catalog/runtime-catalog';\n\n/**\n * `onCallMoved` replaces the move earcon a call plays when it moves to or\n * from this device (pass a no-op to keep it silent); by default the shared\n * one plays.\n */\nexport interface AthenaChatVoiceOptions\n extends Pick<UseAthenaChatVoiceOptions, 'model' | 'livekit' | 'onDiagnostic' | 'onCallMoved'> {}\n\n/** One controller for this composer's controls, text sends and audio sink. */\nexport function useComposerChatVoice({ options }: { options: boolean | AthenaChatVoiceOptions }) {\n const aui = useAui();\n const auth = useMentionAuth();\n const principal = useCatalogPrincipal();\n const enabled = useChatVoiceEnabled({ enabled: options !== false });\n const audioRef = useRef<HTMLAudioElement | null>(null);\n const media = useMemo(\n () => createDeepAgentVoiceLiveKitAdapter({ audioElement: () => audioRef.current }),\n []\n );\n const config = typeof options === 'object' ? options : undefined;\n const voice = useAthenaChatVoice({\n transport: 'livekit',\n livekit: config?.livekit ?? media,\n model: config?.model,\n onDiagnostic:\n config?.onDiagnostic ??\n ((entry) => athenaDiagnostics.emit('voice.chat', { data: { ...entry } })),\n onCallMoved: config?.onCallMoved ?? playVoiceMoveEarcon,\n });\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n const { end, syncProfile } = voice;\n const identity = JSON.stringify([\n auth.backendUrl,\n principal,\n auth.apiKey ? credentialFingerprint({ credential: auth.apiKey }) : null,\n ]);\n const previousScope = useRef({ identity, workspaceId: auth.workspaceId });\n useEffect(() => {\n const previous = previousScope.current;\n const changed =\n previous.identity !== identity ||\n Boolean(\n previous.workspaceId && auth.workspaceId && previous.workspaceId !== auth.workspaceId\n );\n previousScope.current = {\n identity,\n workspaceId:\n previous.identity === identity\n ? auth.workspaceId ?? previous.workspaceId\n : auth.workspaceId,\n };\n if (active && (changed || !enabled)) end();\n }, [identity, auth.workspaceId, active, enabled, end]);\n\n // Model/agent/toolkit/mode picks live in the ambient model context. Read it\n // through the controller at send time, with the same debounce as other hosts.\n const live = voice.phase === 'live';\n useEffect(() => {\n if (!live) return;\n let timer: ReturnType<typeof setTimeout>;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(syncProfile, 800);\n };\n schedule();\n const unsubscribe = aui.modelContext.subscribe?.(schedule);\n return () => {\n clearTimeout(timer);\n unsubscribe?.();\n };\n }, [aui, live, syncProfile]);\n return { voice, enabled, active, audioRef };\n}\n\nexport function ChatVoiceButton({\n voice,\n disabled,\n disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n disabled: boolean;\n /**\n * Why a voice session can't start now (Athena can't be reached): shown as the tooltip of a\n * control that stays focusable and starts nothing. A session already connecting or live keeps its\n * Cancel and Mute.\n */\n disabledReason?: string | null;\n}) {\n const live = voice.phase === 'live';\n const connecting = voice.phase === 'connecting';\n const unavailable = Boolean(disabledReason) && !live && !connecting;\n // The conversation's call is live on another of the person's devices.\n const moveHere = !live && !connecting && voice.liveElsewhere !== null;\n const label = connecting\n ? 'Cancel voice connection'\n : live\n ? voice.micMuted\n ? 'Unmute microphone'\n : 'Mute microphone'\n : moveHere\n ? VOICE_MOVE_CALL_HERE_LABEL\n : 'Start voice session';\n return (\n <TooltipIconButton\n type=\"button\"\n tooltip={unavailable && disabledReason ? disabledReason : label}\n aria-label={label}\n aria-pressed={live && !voice.micMuted}\n aria-disabled={unavailable || undefined}\n data-unavailable={unavailable || undefined}\n className={cn(\n 'aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30',\n unavailable && 'cursor-not-allowed opacity-30 hover:bg-transparent',\n )}\n disabled={!unavailable && !live && !connecting && disabled}\n onClick={() => {\n if (unavailable) return;\n if (connecting) voice.end();\n else if (live) voice.setMicMuted(!voice.micMuted);\n else if (moveHere) void voice.transferHere();\n else void voice.start();\n }}\n >\n {connecting ? (\n <Loader2Icon className=\"size-4 animate-spin\" />\n ) : live ? (\n voice.micMuted ? (\n <MicOffIcon className=\"size-4\" />\n ) : (\n <MicIcon className=\"size-4\" />\n )\n ) : moveHere ? (\n <PhoneIcon className=\"size-4\" />\n ) : (\n <HeadphonesIcon className=\"size-4\" />\n )}\n </TooltipIconButton>\n );\n}\n\n/**\n * A host's \"Continue by voice\" offer for the open conversation: a handover the\n * person sent in voice mode landed here. `onDone` runs once they answer it —\n * took it, dismissed it, or typed instead — so the host clears the offer.\n */\nexport interface AthenaChatContinueByVoice {\n onDone: () => void;\n}\n\n/**\n * The one-tap \"Continue by voice\" above the composer: it starts a call whose\n * model opens by saying where things stand. Shown while voice is idle here and\n * no call is live on another of the person's devices (\"Move here\" covers that).\n * Nothing starts the microphone without the tap.\n */\nexport function ChatVoiceContinueOffer({\n voice,\n offer,\n disabled = false,\n disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n offer: AthenaChatContinueByVoice | null;\n /** Starting would capture the microphone now (dictation, an upload, no channel). */\n disabled?: boolean;\n /** Why voice can't start now (Athena can't be reached), as the action's tooltip. */\n disabledReason?: string | null;\n}) {\n if (!offer || voice.phase !== 'idle' || voice.liveElsewhere !== null) return null;\n return (\n <div className=\"aui-chat-voice-continue flex items-center gap-1 px-2 pb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"xs\"\n title={disabledReason ?? undefined}\n disabled={disabled || Boolean(disabledReason)}\n onClick={() => {\n offer.onDone();\n void voice.start({ opening: 'recap' });\n }}\n >\n <MicIcon aria-hidden=\"true\" />\n {VOICE_CONTINUE_BY_VOICE_LABEL}\n </Button>\n <TooltipIconButton\n type=\"button\"\n tooltip=\"Not now\"\n aria-label=\"Not now\"\n className=\"size-6 rounded-md p-0\"\n onClick={offer.onDone}\n >\n <XIcon className=\"size-3\" />\n </TooltipIconButton>\n </div>\n );\n}\n\nexport function ChatVoiceStatus({\n voice,\n offerMove = false,\n moveDisabled = false,\n moveDisabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n /** Whether this composer may take a call from another device (its voice is enabled). */\n offerMove?: boolean;\n /** Moving the call here would capture the microphone now (dictation, an upload, no channel). */\n moveDisabled?: boolean;\n /** Why the call can't move here now (Athena can't be reached), as the action's tooltip. */\n moveDisabledReason?: string | null;\n}) {\n const elapsed = useVoiceElapsed(voice.liveSince);\n const movedNotice = useVoiceMovedNotice(voice.movedTo);\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n // \"Live call on your phone · Move here\", or briefly after this composer's call moved away,\n // \"Call moved to your phone\".\n const elsewhere = offerMove && !active && movedNotice === null ? voice.liveElsewhere : null;\n if (!active && !voice.error && !voice.handoffTask && movedNotice === null && elsewhere === null)\n return null;\n const moveUnavailable = moveDisabled || Boolean(moveDisabledReason);\n return (\n <div className=\"aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground\">\n {/* No end control of its own: the composer's Stop ends the session\n (and any run), so there is one way to stop everything. */}\n {active && (\n <span role=\"status\">\n {voice.phase === 'connecting'\n ? 'Connecting voice…'\n : `Voice ${voice.micMuted ? 'muted' : 'live'}${elapsed ? ` · ${elapsed}` : ''}`}\n </span>\n )}\n {voice.phase === 'live' && <span>{VOICE_LIVE_TYPED_HINT}</span>}\n {voice.activeTask && <span>Athena is working: {voice.activeTask.text}</span>}\n {voice.handoffTask && (\n <span>Voice ended. Athena is still working: {voice.handoffTask.text}</span>\n )}\n {voice.notice && <span role=\"status\">{voice.notice}</span>}\n {voice.error && (\n <span role=\"alert\" className=\"text-destructive\">\n {voice.error}\n </span>\n )}\n {movedNotice !== null && <span role=\"status\">{movedNotice}</span>}\n {elsewhere !== null && (\n <span className=\"aui-chat-voice-elsewhere flex items-center gap-1\">\n <span className=\"min-w-0 truncate\">{describeVoiceLiveElsewhere(elsewhere.surface)}</span>\n <span aria-hidden=\"true\">·</span>\n <button\n type=\"button\"\n title={moveDisabledReason ?? undefined}\n disabled={moveUnavailable}\n onClick={() => void voice.transferHere()}\n className=\"shrink-0 font-medium text-foreground underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {VOICE_MOVE_HERE_ACTION}\n </button>\n </span>\n )}\n </div>\n );\n}\n"],"names":["useAthenaChatVoice"],"mappings":"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"}
1
+ {"version":3,"file":"chat-voice-controls.js","sources":["../../src/chat/chat-voice-controls.tsx"],"sourcesContent":["import { useAui } from '@assistant-ui/react';\nimport {\n describeVoiceLiveElsewhere,\n playVoiceMoveEarcon,\n useVoiceElapsed,\n useVoiceMovedNotice,\n VOICE_CONTINUE_BY_VOICE_LABEL,\n VOICE_LIVE_TYPED_HINT,\n VOICE_MOVE_CALL_HERE_LABEL,\n VOICE_MOVE_HERE_ACTION,\n} from '@athenaintel/agent-runtime';\nimport {\n AudioLinesIcon,\n HeadphonesIcon,\n Loader2Icon,\n MicIcon,\n MicOffIcon,\n PhoneIcon,\n XIcon,\n} from 'lucide-react';\nimport { useEffect, useMemo, useRef } from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { createDeepAgentVoiceLiveKitAdapter } from '../voice/livekit';\nimport {\n useAthenaChatVoice,\n type UseAthenaChatVoiceOptions,\n type UseAthenaChatVoiceReturn,\n} from '../voice/chat';\nimport { useChatVoiceEnabled } from '../voice/use-chat-voice-enabled';\nimport { cn } from '../lib/utils';\nimport { Button } from '../ui/button';\nimport { TooltipIconButton } from './tooltip-icon-button';\nimport { useMentionAuth } from '../chat-ui/mentions/suggestions/data/api-client';\nimport { credentialFingerprint, useCatalogPrincipal } from '../catalog/runtime-catalog';\n\n/**\n * `onCallMoved` replaces the move earcon a call plays when it moves to or\n * from this device (pass a no-op to keep it silent); by default the shared\n * one plays.\n */\nexport interface AthenaChatVoiceOptions\n extends Pick<UseAthenaChatVoiceOptions, 'model' | 'livekit' | 'onDiagnostic' | 'onCallMoved'> {}\n\n/** One controller for this composer's controls, text sends and audio sink. */\nexport function useComposerChatVoice({ options }: { options: boolean | AthenaChatVoiceOptions }) {\n const aui = useAui();\n const auth = useMentionAuth();\n const principal = useCatalogPrincipal();\n const enabled = useChatVoiceEnabled({ enabled: options !== false });\n const audioRef = useRef<HTMLAudioElement | null>(null);\n const media = useMemo(\n () => createDeepAgentVoiceLiveKitAdapter({ audioElement: () => audioRef.current }),\n []\n );\n const config = typeof options === 'object' ? options : undefined;\n const voice = useAthenaChatVoice({\n transport: 'livekit',\n livekit: config?.livekit ?? media,\n model: config?.model,\n onDiagnostic:\n config?.onDiagnostic ??\n ((entry) => athenaDiagnostics.emit('voice.chat', { data: { ...entry } })),\n onCallMoved: config?.onCallMoved ?? playVoiceMoveEarcon,\n });\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n const { end, syncProfile } = voice;\n const identity = JSON.stringify([\n auth.backendUrl,\n principal,\n auth.apiKey ? credentialFingerprint({ credential: auth.apiKey }) : null,\n ]);\n const previousScope = useRef({ identity, workspaceId: auth.workspaceId });\n useEffect(() => {\n const previous = previousScope.current;\n const changed =\n previous.identity !== identity ||\n Boolean(\n previous.workspaceId && auth.workspaceId && previous.workspaceId !== auth.workspaceId\n );\n previousScope.current = {\n identity,\n workspaceId:\n previous.identity === identity\n ? auth.workspaceId ?? previous.workspaceId\n : auth.workspaceId,\n };\n if (active && (changed || !enabled)) end();\n }, [identity, auth.workspaceId, active, enabled, end]);\n\n // Model/agent/toolkit/mode picks live in the ambient model context. Read it\n // through the controller at send time, with the same debounce as other hosts.\n const live = voice.phase === 'live';\n useEffect(() => {\n if (!live) return;\n let timer: ReturnType<typeof setTimeout>;\n const schedule = () => {\n clearTimeout(timer);\n timer = setTimeout(syncProfile, 800);\n };\n schedule();\n const unsubscribe = aui.modelContext.subscribe?.(schedule);\n return () => {\n clearTimeout(timer);\n unsubscribe?.();\n };\n }, [aui, live, syncProfile]);\n return { voice, enabled, active, audioRef };\n}\n\nexport function ChatVoiceButton({\n voice,\n disabled,\n disabledReason = null,\n appearance = 'icon',\n}: {\n voice: UseAthenaChatVoiceReturn;\n disabled: boolean;\n /**\n * `send`: drawn as the composer's Send (an ink circle with a waveform), where it stands in for Send\n * while the draft is empty (`AthenaChatComposerSlots.voiceInSend`). `icon` (default): a plain icon.\n */\n appearance?: 'icon' | 'send';\n /**\n * Why a voice session can't start now (Athena can't be reached): shown as the tooltip of a\n * control that stays focusable and starts nothing. A session already connecting or live keeps its\n * Cancel and Mute.\n */\n disabledReason?: string | null;\n}) {\n const live = voice.phase === 'live';\n const connecting = voice.phase === 'connecting';\n const unavailable = Boolean(disabledReason) && !live && !connecting;\n // The conversation's call is live on another of the person's devices.\n const moveHere = !live && !connecting && voice.liveElsewhere !== null;\n const label = connecting\n ? 'Cancel voice connection'\n : live\n ? voice.micMuted\n ? 'Unmute microphone'\n : 'Mute microphone'\n : moveHere\n ? VOICE_MOVE_CALL_HERE_LABEL\n : 'Start voice session';\n return (\n <TooltipIconButton\n type=\"button\"\n tooltip={unavailable && disabledReason ? disabledReason : label}\n aria-label={label}\n aria-pressed={live && !voice.micMuted}\n aria-disabled={unavailable || undefined}\n data-unavailable={unavailable || undefined}\n {...(appearance === 'send' ? { variant: 'default' as const } : {})}\n className={cn(\n 'aui-composer-voice size-8 rounded-full p-0 disabled:opacity-30',\n appearance === 'send' && 'aui-composer-voice-send',\n unavailable && 'cursor-not-allowed opacity-30 hover:bg-transparent',\n )}\n disabled={!unavailable && !live && !connecting && disabled}\n onClick={() => {\n if (unavailable) return;\n if (connecting) voice.end();\n else if (live) voice.setMicMuted(!voice.micMuted);\n else if (moveHere) void voice.transferHere();\n else void voice.start();\n }}\n >\n {connecting ? (\n <Loader2Icon className=\"size-4 animate-spin\" />\n ) : live ? (\n voice.micMuted ? (\n <MicOffIcon className=\"size-4\" />\n ) : (\n <MicIcon className=\"size-4\" />\n )\n ) : moveHere ? (\n <PhoneIcon className=\"size-4\" />\n ) : appearance === 'send' ? (\n <AudioLinesIcon className=\"size-4\" />\n ) : (\n <HeadphonesIcon className=\"size-4\" />\n )}\n </TooltipIconButton>\n );\n}\n\n/**\n * A host's \"Continue by voice\" offer for the open conversation: a handover the\n * person sent in voice mode landed here. `onDone` runs once they answer it —\n * took it, dismissed it, or typed instead — so the host clears the offer.\n */\nexport interface AthenaChatContinueByVoice {\n onDone: () => void;\n}\n\n/**\n * The one-tap \"Continue by voice\" above the composer: it starts a call whose\n * model opens by saying where things stand. Shown while voice is idle here and\n * no call is live on another of the person's devices (\"Move here\" covers that).\n * Nothing starts the microphone without the tap.\n */\nexport function ChatVoiceContinueOffer({\n voice,\n offer,\n disabled = false,\n disabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n offer: AthenaChatContinueByVoice | null;\n /** Starting would capture the microphone now (dictation, an upload, no channel). */\n disabled?: boolean;\n /** Why voice can't start now (Athena can't be reached), as the action's tooltip. */\n disabledReason?: string | null;\n}) {\n if (!offer || voice.phase !== 'idle' || voice.liveElsewhere !== null) return null;\n return (\n <div className=\"aui-chat-voice-continue flex items-center gap-1 px-2 pb-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"xs\"\n title={disabledReason ?? undefined}\n disabled={disabled || Boolean(disabledReason)}\n onClick={() => {\n offer.onDone();\n void voice.start({ opening: 'recap' });\n }}\n >\n <MicIcon aria-hidden=\"true\" />\n {VOICE_CONTINUE_BY_VOICE_LABEL}\n </Button>\n <TooltipIconButton\n type=\"button\"\n tooltip=\"Not now\"\n aria-label=\"Not now\"\n className=\"size-6 rounded-md p-0\"\n onClick={offer.onDone}\n >\n <XIcon className=\"size-3\" />\n </TooltipIconButton>\n </div>\n );\n}\n\nexport function ChatVoiceStatus({\n voice,\n offerMove = false,\n moveDisabled = false,\n moveDisabledReason = null,\n}: {\n voice: UseAthenaChatVoiceReturn;\n /** Whether this composer may take a call from another device (its voice is enabled). */\n offerMove?: boolean;\n /** Moving the call here would capture the microphone now (dictation, an upload, no channel). */\n moveDisabled?: boolean;\n /** Why the call can't move here now (Athena can't be reached), as the action's tooltip. */\n moveDisabledReason?: string | null;\n}) {\n const elapsed = useVoiceElapsed(voice.liveSince);\n const movedNotice = useVoiceMovedNotice(voice.movedTo);\n const active = voice.phase === 'connecting' || voice.phase === 'live';\n // \"Live call on your phone · Move here\", or briefly after this composer's call moved away,\n // \"Call moved to your phone\".\n const elsewhere = offerMove && !active && movedNotice === null ? voice.liveElsewhere : null;\n if (!active && !voice.error && !voice.handoffTask && movedNotice === null && elsewhere === null)\n return null;\n const moveUnavailable = moveDisabled || Boolean(moveDisabledReason);\n return (\n <div className=\"aui-chat-voice-status flex flex-col gap-1 px-2 pb-2 text-xs text-muted-foreground\">\n {/* No end control of its own: the composer's Stop ends the session\n (and any run), so there is one way to stop everything. */}\n {active && (\n <span role=\"status\">\n {voice.phase === 'connecting'\n ? 'Connecting voice…'\n : `Voice ${voice.micMuted ? 'muted' : 'live'}${elapsed ? ` · ${elapsed}` : ''}`}\n </span>\n )}\n {voice.phase === 'live' && <span>{VOICE_LIVE_TYPED_HINT}</span>}\n {voice.activeTask && <span>Athena is working: {voice.activeTask.text}</span>}\n {voice.handoffTask && (\n <span>Voice ended. Athena is still working: {voice.handoffTask.text}</span>\n )}\n {voice.notice && <span role=\"status\">{voice.notice}</span>}\n {voice.error && (\n <span role=\"alert\" className=\"text-destructive\">\n {voice.error}\n </span>\n )}\n {movedNotice !== null && <span role=\"status\">{movedNotice}</span>}\n {elsewhere !== null && (\n <span className=\"aui-chat-voice-elsewhere flex items-center gap-1\">\n <span className=\"min-w-0 truncate\">{describeVoiceLiveElsewhere(elsewhere.surface)}</span>\n <span aria-hidden=\"true\">·</span>\n <button\n type=\"button\"\n title={moveDisabledReason ?? undefined}\n disabled={moveUnavailable}\n onClick={() => void voice.transferHere()}\n className=\"shrink-0 font-medium text-foreground underline-offset-2 hover:underline disabled:cursor-not-allowed disabled:opacity-50\"\n >\n {VOICE_MOVE_HERE_ACTION}\n </button>\n </span>\n )}\n </div>\n );\n}\n"],"names":["useAthenaChatVoice"],"mappings":";;;;;;;;;;;;;;;AA4CO,SAAS,qBAAqB,EAAE,WAA0D;AAC/F,QAAM,MAAM,OAAA;AACZ,QAAM,OAAO,eAAA;AACb,QAAM,YAAY,oBAAA;AAClB,QAAM,UAAU,oBAAoB,EAAE,SAAS,YAAY,OAAO;AAClE,QAAM,WAAW,OAAgC,IAAI;AACrD,QAAM,QAAQ;AAAA,IACZ,MAAM,mCAAmC,EAAE,cAAc,MAAM,SAAS,SAAS;AAAA,IACjF,CAAA;AAAA,EAAC;AAEH,QAAM,SAAS,OAAO,YAAY,WAAW,UAAU;AACvD,QAAM,QAAQA,kBAAmB;AAAA,IAC/B,WAAW;AAAA,IACX,UAAS,iCAAQ,YAAW;AAAA,IAC5B,OAAO,iCAAQ;AAAA,IACf,eACE,iCAAQ,kBACP,CAAC,UAAU,kBAAkB,KAAK,cAAc,EAAE,MAAM,EAAE,GAAG,MAAA,GAAS;AAAA,IACzE,cAAa,iCAAQ,gBAAe;AAAA,EAAA,CACrC;AACD,QAAM,SAAS,MAAM,UAAU,gBAAgB,MAAM,UAAU;AAC/D,QAAM,EAAE,KAAK,YAAA,IAAgB;AAC7B,QAAM,WAAW,KAAK,UAAU;AAAA,IAC9B,KAAK;AAAA,IACL;AAAA,IACA,KAAK,SAAS,sBAAsB,EAAE,YAAY,KAAK,OAAA,CAAQ,IAAI;AAAA,EAAA,CACpE;AACD,QAAM,gBAAgB,OAAO,EAAE,UAAU,aAAa,KAAK,aAAa;AACxE,YAAU,MAAM;AACd,UAAM,WAAW,cAAc;AAC/B,UAAM,UACJ,SAAS,aAAa,YACtB;AAAA,MACE,SAAS,eAAe,KAAK,eAAe,SAAS,gBAAgB,KAAK;AAAA,IAAA;AAE9E,kBAAc,UAAU;AAAA,MACtB;AAAA,MACA,aACE,SAAS,aAAa,WAClB,KAAK,eAAe,SAAS,cAC7B,KAAK;AAAA,IAAA;AAEb,QAAI,WAAW,WAAW,CAAC,SAAU,KAAA;AAAA,EACvC,GAAG,CAAC,UAAU,KAAK,aAAa,QAAQ,SAAS,GAAG,CAAC;AAIrD,QAAM,OAAO,MAAM,UAAU;AAC7B,YAAU,MAAM;;AACd,QAAI,CAAC,KAAM;AACX,QAAI;AACJ,UAAM,WAAW,MAAM;AACrB,mBAAa,KAAK;AAClB,cAAQ,WAAW,aAAa,GAAG;AAAA,IACrC;AACA,aAAA;AACA,UAAM,eAAc,eAAI,cAAa,cAAjB,4BAA6B;AACjD,WAAO,MAAM;AACX,mBAAa,KAAK;AAClB;AAAA,IACF;AAAA,EACF,GAAG,CAAC,KAAK,MAAM,WAAW,CAAC;AAC3B,SAAO,EAAE,OAAO,SAAS,QAAQ,SAAA;AACnC;AAEO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA;AAAA,EACA,iBAAiB;AAAA,EACjB,aAAa;AACf,GAcG;AACD,QAAM,OAAO,MAAM,UAAU;AAC7B,QAAM,aAAa,MAAM,UAAU;AACnC,QAAM,cAAc,QAAQ,cAAc,KAAK,CAAC,QAAQ,CAAC;AAEzD,QAAM,WAAW,CAAC,QAAQ,CAAC,cAAc,MAAM,kBAAkB;AACjE,QAAM,QAAQ,aACV,4BACA,OACA,MAAM,WACJ,sBACA,oBACF,WACA,6BACA;AACJ,SACE;AAAA,IAAC;AAAA,IAAA;AAAA,MACC,MAAK;AAAA,MACL,SAAS,eAAe,iBAAiB,iBAAiB;AAAA,MAC1D,cAAY;AAAA,MACZ,gBAAc,QAAQ,CAAC,MAAM;AAAA,MAC7B,iBAAe,eAAe;AAAA,MAC9B,oBAAkB,eAAe;AAAA,MAChC,GAAI,eAAe,SAAS,EAAE,SAAS,UAAA,IAAuB,CAAA;AAAA,MAC/D,WAAW;AAAA,QACT;AAAA,QACA,eAAe,UAAU;AAAA,QACzB,eAAe;AAAA,MAAA;AAAA,MAEjB,UAAU,CAAC,eAAe,CAAC,QAAQ,CAAC,cAAc;AAAA,MAClD,SAAS,MAAM;AACb,YAAI,YAAa;AACjB,YAAI,kBAAkB,IAAA;AAAA,iBACb,KAAM,OAAM,YAAY,CAAC,MAAM,QAAQ;AAAA,iBACvC,SAAU,MAAK,MAAM,aAAA;AAAA,YACzB,MAAK,MAAM,MAAA;AAAA,MAClB;AAAA,MAEC,UAAA,aACC,oBAAC,aAAA,EAAY,WAAU,uBAAsB,IAC3C,OACF,MAAM,+BACH,YAAA,EAAW,WAAU,SAAA,CAAS,wBAE9B,SAAA,EAAQ,WAAU,SAAA,CAAS,IAE5B,WACF,oBAAC,WAAA,EAAU,WAAU,SAAA,CAAS,IAC5B,eAAe,SACjB,oBAAC,gBAAA,EAAe,WAAU,SAAA,CAAS,IAEnC,oBAAC,gBAAA,EAAe,WAAU,SAAA,CAAS;AAAA,IAAA;AAAA,EAAA;AAI3C;AAiBO,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,iBAAiB;AACnB,GAOG;AACD,MAAI,CAAC,SAAS,MAAM,UAAU,UAAU,MAAM,kBAAkB,KAAM,QAAO;AAC7E,SACE,qBAAC,OAAA,EAAI,WAAU,6DACb,UAAA;AAAA,IAAA;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,MAAK;AAAA,QACL,OAAO,kBAAkB;AAAA,QACzB,UAAU,YAAY,QAAQ,cAAc;AAAA,QAC5C,SAAS,MAAM;AACb,gBAAM,OAAA;AACN,eAAK,MAAM,MAAM,EAAE,SAAS,SAAS;AAAA,QACvC;AAAA,QAEA,UAAA;AAAA,UAAA,oBAAC,SAAA,EAAQ,eAAY,OAAA,CAAO;AAAA,UAC3B;AAAA,QAAA;AAAA,MAAA;AAAA,IAAA;AAAA,IAEH;AAAA,MAAC;AAAA,MAAA;AAAA,QACC,MAAK;AAAA,QACL,SAAQ;AAAA,QACR,cAAW;AAAA,QACX,WAAU;AAAA,QACV,SAAS,MAAM;AAAA,QAEf,UAAA,oBAAC,OAAA,EAAM,WAAU,SAAA,CAAS;AAAA,MAAA;AAAA,IAAA;AAAA,EAC5B,GACF;AAEJ;AAEO,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,YAAY;AAAA,EACZ,eAAe;AAAA,EACf,qBAAqB;AACvB,GAQG;AACD,QAAM,UAAU,gBAAgB,MAAM,SAAS;AAC/C,QAAM,cAAc,oBAAoB,MAAM,OAAO;AACrD,QAAM,SAAS,MAAM,UAAU,gBAAgB,MAAM,UAAU;AAG/D,QAAM,YAAY,aAAa,CAAC,UAAU,gBAAgB,OAAO,MAAM,gBAAgB;AACvF,MAAI,CAAC,UAAU,CAAC,MAAM,SAAS,CAAC,MAAM,eAAe,gBAAgB,QAAQ,cAAc;AACzF,WAAO;AACT,QAAM,kBAAkB,gBAAgB,QAAQ,kBAAkB;AAClE,SACE,qBAAC,OAAA,EAAI,WAAU,qFAGZ,UAAA;AAAA,IAAA,8BACE,QAAA,EAAK,MAAK,UACR,UAAA,MAAM,UAAU,eACb,sBACA,SAAS,MAAM,WAAW,UAAU,MAAM,GAAG,UAAU,MAAM,OAAO,KAAK,EAAE,GAAA,CACjF;AAAA,IAED,MAAM,UAAU,UAAU,oBAAC,UAAM,UAAA,uBAAsB;AAAA,IACvD,MAAM,cAAc,qBAAC,QAAA,EAAK,UAAA;AAAA,MAAA;AAAA,MAAoB,MAAM,WAAW;AAAA,IAAA,GAAK;AAAA,IACpE,MAAM,eACL,qBAAC,QAAA,EAAK,UAAA;AAAA,MAAA;AAAA,MAAuC,MAAM,YAAY;AAAA,IAAA,GAAK;AAAA,IAErE,MAAM,UAAU,oBAAC,UAAK,MAAK,UAAU,gBAAM,QAAO;AAAA,IAClD,MAAM,SACL,oBAAC,QAAA,EAAK,MAAK,SAAQ,WAAU,oBAC1B,UAAA,MAAM,MAAA,CACT;AAAA,IAED,gBAAgB,QAAQ,oBAAC,QAAA,EAAK,MAAK,UAAU,UAAA,aAAY;AAAA,IACzD,cAAc,QACb,qBAAC,QAAA,EAAK,WAAU,oDACd,UAAA;AAAA,MAAA,oBAAC,UAAK,WAAU,oBAAoB,UAAA,2BAA2B,UAAU,OAAO,GAAE;AAAA,MAClF,oBAAC,QAAA,EAAK,eAAY,QAAO,UAAA,KAAC;AAAA,MAC1B;AAAA,QAAC;AAAA,QAAA;AAAA,UACC,MAAK;AAAA,UACL,OAAO,sBAAsB;AAAA,UAC7B,UAAU;AAAA,UACV,SAAS,MAAM,KAAK,MAAM,aAAA;AAAA,UAC1B,WAAU;AAAA,UAET,UAAA;AAAA,QAAA;AAAA,MAAA;AAAA,IACH,EAAA,CACF;AAAA,EAAA,GAEJ;AAEJ;"}
@@ -7,6 +7,10 @@ function createClientToolWriteConsentStore() {
7
7
  let pending = null;
8
8
  let cards = 0;
9
9
  const granted = /* @__PURE__ */ new Set();
10
+ const grantKey = (threadId, toolName) => `${threadId}\0${toolName}`;
11
+ const grantAll = (threadId, toolNames) => {
12
+ for (const toolName of toolNames) granted.add(grantKey(threadId, toolName));
13
+ };
10
14
  const listeners = /* @__PURE__ */ new Set();
11
15
  const notify = () => {
12
16
  for (const listener of Array.from(listeners)) listener();
@@ -25,9 +29,11 @@ function createClientToolWriteConsentStore() {
25
29
  listeners.delete(listener);
26
30
  };
27
31
  },
28
- isGranted: (threadId) => granted.has(threadId),
32
+ isGranted: ({ threadId, toolName }) => granted.has(grantKey(threadId, toolName)),
29
33
  ask(request) {
30
- if (granted.has(request.threadId)) return Promise.resolve("proceed");
34
+ if (request.tools.every((tool) => granted.has(grantKey(request.threadId, tool.name)))) {
35
+ return Promise.resolve("proceed");
36
+ }
31
37
  if (pending) settle("abandoned");
32
38
  if (cards === 0) {
33
39
  athenaDiagnostics.athenaDiagnostics.emit("sdk.config.warning", {
@@ -48,15 +54,20 @@ function createClientToolWriteConsentStore() {
48
54
  },
49
55
  answer({ requestId, allow }) {
50
56
  if ((pending == null ? void 0 : pending.request.requestId) !== requestId) return;
51
- if (allow) granted.add(pending.request.threadId);
57
+ if (allow) {
58
+ grantAll(
59
+ pending.request.threadId,
60
+ pending.request.tools.map((tool) => tool.name)
61
+ );
62
+ }
52
63
  settle(allow ? "proceed" : "declined");
53
64
  },
54
65
  abandon(requestId) {
55
66
  if ((pending == null ? void 0 : pending.request.requestId) !== requestId) return;
56
67
  settle("abandoned");
57
68
  },
58
- grant(threadId) {
59
- granted.add(threadId);
69
+ grant({ threadId, toolNames }) {
70
+ grantAll(threadId, toolNames);
60
71
  },
61
72
  registerCard() {
62
73
  cards += 1;
@@ -1 +1 @@
1
- {"version":3,"file":"client-tool-write-consent.cjs","sources":["../../src/chat/client-tool-write-consent.ts"],"sourcesContent":["'use client';\n\nimport type { ClientToolWriteConsentDecision } from '@athenaintel/agent-runtime';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\n/*\n * Write consent for the app's own frontend tools. A tool marked with\n * `requiresWriteConsent` parks its first call in a conversation until the\n * person answers; this store holds that question between the client-tool\n * bridge (which asks) and whatever draws it (`StatewireClientToolConsentCard`\n * in `AthenaChat`, or a host's own card on `useClientToolWriteConsent`).\n *\n * Allowing covers the rest of the conversation, as Athena's own apps do, for\n * the person who allowed it: a store belongs to one signed-in person on one\n * Athena (`useClientToolWriteConsentStore`), so someone else signing in to the\n * same mounted chat is asked for themselves. Nothing is remembered across a\n * reload: the next call asks again.\n */\n\n/** One write tool of a batch waiting on the person. */\nexport interface ClientToolWriteConsentTool {\n /** The tool's name, as the model calls it. */\n name: string;\n /** What it does, when the tool said (`requiresWriteConsent(…, { summary })`). */\n summary?: string;\n}\n\n/** The question a parked batch of write calls puts to the person. */\nexport interface ClientToolWriteConsentRequest {\n /** The parked request the answer resumes. */\n requestId: string;\n /** The conversation the answer covers. */\n threadId: string;\n /** The batch's write tools, each once. */\n tools: readonly ClientToolWriteConsentTool[];\n}\n\n/**\n * A host's own consent UI (`AthenaProviderProps.confirmClientToolWrites`).\n * Resolve `true` to allow the conversation's writes, `false` to decline this\n * batch. `signal` aborts when the question no longer has a call to answer\n * (the run was stopped): close the dialog then; the value is ignored.\n */\nexport type ClientToolWriteConfirmer = (\n request: ClientToolWriteConsentRequest & { signal: AbortSignal },\n) => Promise<boolean>;\n\nexport interface ClientToolWriteConsentStore {\n /** The question on screen, or `null`. */\n getPending(): ClientToolWriteConsentRequest | null;\n subscribe(listener: () => void): () => void;\n /** Whether the person already allowed writes in this conversation. */\n isGranted(threadId: string): boolean;\n /** Put the question to the person; resolves with their answer, or `abandoned`. */\n ask(request: ClientToolWriteConsentRequest): Promise<ClientToolWriteConsentDecision>;\n /** The person's answer to the pending question. */\n answer(args: { requestId: string; allow: boolean }): void;\n /** The pending question lost its call (stop, another conversation): close it. */\n abandon(requestId: string): void;\n /** Record a consent the host's own UI collected. */\n grant(threadId: string): void;\n /** A mounted card registers itself, so a question nobody can see is reported. */\n registerCard(): () => void;\n /** The store's person is gone: close the pending question and forget every yes. */\n dispose(): void;\n}\n\ninterface PendingConsent {\n request: ClientToolWriteConsentRequest;\n resolve: (decision: ClientToolWriteConsentDecision) => void;\n}\n\nexport function createClientToolWriteConsentStore(): ClientToolWriteConsentStore {\n let pending: PendingConsent | null = null;\n let cards = 0;\n const granted = new Set<string>();\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of Array.from(listeners)) listener();\n };\n const settle = (decision: ClientToolWriteConsentDecision) => {\n const settled = pending;\n pending = null;\n settled?.resolve(decision);\n notify();\n };\n\n return {\n getPending: () => pending?.request ?? null,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n isGranted: (threadId) => granted.has(threadId),\n ask(request) {\n if (granted.has(request.threadId)) return Promise.resolve('proceed');\n // One question at a time: a newer batch replaces a question whose call\n // the bridge has already given up on.\n if (pending) settle('abandoned');\n if (cards === 0) {\n athenaDiagnostics.emit('sdk.config.warning', {\n level: 'warn',\n error: new AthenaSdkError({\n code: 'config_invalid',\n message:\n 'A frontend tool is waiting for the person to allow it, and nothing on screen asks them.',\n hint: 'Render <AthenaChat> (it draws the question), mount <StatewireClientToolConsentCard>, or pass confirmClientToolWrites on <AthenaProvider>. The run stays parked until the question is answered.',\n context: { tools: request.tools.map((tool) => tool.name) },\n }),\n data: { tools: request.tools.map((tool) => tool.name) },\n });\n }\n return new Promise<ClientToolWriteConsentDecision>((resolve) => {\n pending = { request, resolve };\n notify();\n });\n },\n answer({ requestId, allow }) {\n if (pending?.request.requestId !== requestId) return;\n if (allow) granted.add(pending.request.threadId);\n settle(allow ? 'proceed' : 'declined');\n },\n abandon(requestId) {\n if (pending?.request.requestId !== requestId) return;\n settle('abandoned');\n },\n grant(threadId) {\n granted.add(threadId);\n },\n registerCard() {\n cards += 1;\n return () => {\n cards -= 1;\n };\n },\n dispose() {\n granted.clear();\n if (pending) settle('abandoned');\n },\n };\n}\n\n/**\n * The consent store for one signed-in person on one Athena. `scope` names\n * both (the provider passes the session principal and the sync host): when it\n * changes, the previous person's store is disposed (its question closed, its\n * yeses forgotten) and the next person starts with none.\n */\nexport function useClientToolWriteConsentStore(scope: string): ClientToolWriteConsentStore {\n const [held, setHeld] = useState(() => ({ scope, store: createClientToolWriteConsentStore() }));\n let current = held;\n if (held.scope !== scope) {\n // Replaced during render, so no commit ever pairs the new person with the\n // previous person's store.\n current = { scope, store: createClientToolWriteConsentStore() };\n setHeld(current);\n }\n const { store } = current;\n useEffect(() => () => store.dispose(), [store]);\n return store;\n}\n\n/** Provided by `AthenaProvider`; `null` outside one. */\nexport const ClientToolWriteConsentContext = createContext<ClientToolWriteConsentStore | null>(\n null,\n);\n\nconst NO_SUBSCRIPTION = () => () => {};\nconst NO_PENDING = () => null;\n\nexport interface ClientToolWriteConsent {\n /** The question waiting on the person, or `null`. */\n request: ClientToolWriteConsentRequest | null;\n /** Allow the conversation's writes and run the waiting calls. */\n allow: () => void;\n /** Decline the waiting calls; the agent is told, and a later call asks again. */\n decline: () => void;\n}\n\n/**\n * The write-consent question for the chat the nearest `AthenaProvider` shows,\n * for a host that draws its own card. Calling it marks the question as shown.\n */\nexport function useClientToolWriteConsent(): ClientToolWriteConsent {\n const store = useContext(ClientToolWriteConsentContext);\n const request = useSyncExternalStore(\n store?.subscribe ?? NO_SUBSCRIPTION,\n store?.getPending ?? NO_PENDING,\n NO_PENDING,\n );\n useEffect(() => store?.registerCard(), [store]);\n const requestId = request?.requestId;\n const allow = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: true });\n }, [requestId, store]);\n const decline = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: false });\n }, [requestId, store]);\n return { request, allow, decline };\n}\n"],"names":["athenaDiagnostics","AthenaSdkError","useState","useEffect","createContext","useContext","useSyncExternalStore","useCallback"],"mappings":"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"}
1
+ {"version":3,"file":"client-tool-write-consent.cjs","sources":["../../src/chat/client-tool-write-consent.ts"],"sourcesContent":["'use client';\n\nimport type { ClientToolWriteConsentDecision } from '@athenaintel/agent-runtime';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\n/*\n * Write consent for the app's own frontend tools. A tool marked with\n * `requiresWriteConsent` parks its first call in a conversation until the\n * person answers; this store holds that question between the client-tool\n * bridge (which asks) and whatever draws it (`StatewireClientToolConsentCard`\n * in `AthenaChat`, or a host's own card on `useClientToolWriteConsent`).\n *\n * Allowing covers the tools the question named, for the rest of the\n * conversation, for the person who allowed it: a yes to one tool is never a\n * yes to another. A store belongs to one signed-in person on one\n * Athena (`useClientToolWriteConsentStore`), so someone else signing in to the\n * same mounted chat is asked for themselves. Nothing is remembered across a\n * reload: the next call asks again.\n */\n\n/** One write tool of a batch waiting on the person. */\nexport interface ClientToolWriteConsentTool {\n /** The tool's name, as the model calls it. */\n name: string;\n /** What it does, when the tool said (`requiresWriteConsent(…, { summary })`). */\n summary?: string;\n}\n\n/** The question a parked batch of write calls puts to the person. */\nexport interface ClientToolWriteConsentRequest {\n /** The parked request the answer resumes. */\n requestId: string;\n /** The conversation the answer covers. */\n threadId: string;\n /** The batch's write tools, each once. */\n tools: readonly ClientToolWriteConsentTool[];\n /** Who is asking, when the host named its agent (`agentUi.agentName`). */\n agentName?: string;\n}\n\n/**\n * A host's own consent UI (`AthenaProviderProps.confirmClientToolWrites`).\n * Resolve `true` to allow the conversation's writes, `false` to decline this\n * batch. `signal` aborts when the question no longer has a call to answer\n * (the run was stopped): close the dialog then; the value is ignored.\n */\nexport type ClientToolWriteConfirmer = (\n request: ClientToolWriteConsentRequest & { signal: AbortSignal },\n) => Promise<boolean>;\n\nexport interface ClientToolWriteConsentStore {\n /** The question on screen, or `null`. */\n getPending(): ClientToolWriteConsentRequest | null;\n subscribe(listener: () => void): () => void;\n /** Whether the person already allowed this tool in this conversation. */\n isGranted(args: { threadId: string; toolName: string }): boolean;\n /** Put the question to the person; resolves with their answer, or `abandoned`. */\n ask(request: ClientToolWriteConsentRequest): Promise<ClientToolWriteConsentDecision>;\n /** The person's answer to the pending question. */\n answer(args: { requestId: string; allow: boolean }): void;\n /** The pending question lost its call (stop, another conversation): close it. */\n abandon(requestId: string): void;\n /** Record a consent the host's own UI collected, for the tools it asked about. */\n grant(args: { threadId: string; toolNames: readonly string[] }): void;\n /** A mounted card registers itself, so a question nobody can see is reported. */\n registerCard(): () => void;\n /** The store's person is gone: close the pending question and forget every yes. */\n dispose(): void;\n}\n\ninterface PendingConsent {\n request: ClientToolWriteConsentRequest;\n resolve: (decision: ClientToolWriteConsentDecision) => void;\n}\n\nexport function createClientToolWriteConsentStore(): ClientToolWriteConsentStore {\n let pending: PendingConsent | null = null;\n let cards = 0;\n const granted = new Set<string>();\n const grantKey = (threadId: string, toolName: string): string => `${threadId}\\u0000${toolName}`;\n const grantAll = (threadId: string, toolNames: readonly string[]) => {\n for (const toolName of toolNames) granted.add(grantKey(threadId, toolName));\n };\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of Array.from(listeners)) listener();\n };\n const settle = (decision: ClientToolWriteConsentDecision) => {\n const settled = pending;\n pending = null;\n settled?.resolve(decision);\n notify();\n };\n\n return {\n getPending: () => pending?.request ?? null,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n isGranted: ({ threadId, toolName }) => granted.has(grantKey(threadId, toolName)),\n ask(request) {\n if (request.tools.every((tool) => granted.has(grantKey(request.threadId, tool.name)))) {\n return Promise.resolve('proceed');\n }\n // One question at a time: a newer batch replaces a question whose call\n // the bridge has already given up on.\n if (pending) settle('abandoned');\n if (cards === 0) {\n athenaDiagnostics.emit('sdk.config.warning', {\n level: 'warn',\n error: new AthenaSdkError({\n code: 'config_invalid',\n message:\n 'A frontend tool is waiting for the person to allow it, and nothing on screen asks them.',\n hint: 'Render <AthenaChat> (it draws the question), mount <StatewireClientToolConsentCard>, or pass confirmClientToolWrites on <AthenaProvider>. The run stays parked until the question is answered.',\n context: { tools: request.tools.map((tool) => tool.name) },\n }),\n data: { tools: request.tools.map((tool) => tool.name) },\n });\n }\n return new Promise<ClientToolWriteConsentDecision>((resolve) => {\n pending = { request, resolve };\n notify();\n });\n },\n answer({ requestId, allow }) {\n if (pending?.request.requestId !== requestId) return;\n if (allow) {\n grantAll(\n pending.request.threadId,\n pending.request.tools.map((tool) => tool.name),\n );\n }\n settle(allow ? 'proceed' : 'declined');\n },\n abandon(requestId) {\n if (pending?.request.requestId !== requestId) return;\n settle('abandoned');\n },\n grant({ threadId, toolNames }) {\n grantAll(threadId, toolNames);\n },\n registerCard() {\n cards += 1;\n return () => {\n cards -= 1;\n };\n },\n dispose() {\n granted.clear();\n if (pending) settle('abandoned');\n },\n };\n}\n\n/**\n * The consent store for one signed-in person on one Athena. `scope` names\n * both (the provider passes the session principal and the sync host): when it\n * changes, the previous person's store is disposed (its question closed, its\n * yeses forgotten) and the next person starts with none.\n */\nexport function useClientToolWriteConsentStore(scope: string): ClientToolWriteConsentStore {\n const [held, setHeld] = useState(() => ({ scope, store: createClientToolWriteConsentStore() }));\n let current = held;\n if (held.scope !== scope) {\n // Replaced during render, so no commit ever pairs the new person with the\n // previous person's store.\n current = { scope, store: createClientToolWriteConsentStore() };\n setHeld(current);\n }\n const { store } = current;\n useEffect(() => () => store.dispose(), [store]);\n return store;\n}\n\n/** Provided by `AthenaProvider`; `null` outside one. */\nexport const ClientToolWriteConsentContext = createContext<ClientToolWriteConsentStore | null>(\n null,\n);\n\nconst NO_SUBSCRIPTION = () => () => {};\nconst NO_PENDING = () => null;\n\nexport interface ClientToolWriteConsent {\n /** The question waiting on the person, or `null`. */\n request: ClientToolWriteConsentRequest | null;\n /** Allow the conversation's writes and run the waiting calls. */\n allow: () => void;\n /** Decline the waiting calls; the agent is told, and a later call asks again. */\n decline: () => void;\n}\n\n/**\n * The write-consent question for the chat the nearest `AthenaProvider` shows,\n * for a host that draws its own card. Calling it marks the question as shown.\n */\nexport function useClientToolWriteConsent(): ClientToolWriteConsent {\n const store = useContext(ClientToolWriteConsentContext);\n const request = useSyncExternalStore(\n store?.subscribe ?? NO_SUBSCRIPTION,\n store?.getPending ?? NO_PENDING,\n NO_PENDING,\n );\n useEffect(() => store?.registerCard(), [store]);\n const requestId = request?.requestId;\n const allow = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: true });\n }, [requestId, store]);\n const decline = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: false });\n }, [requestId, store]);\n return { request, allow, decline };\n}\n"],"names":["athenaDiagnostics","AthenaSdkError","useState","useEffect","createContext","useContext","useSyncExternalStore","useCallback"],"mappings":"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"}
@@ -14,6 +14,8 @@ export interface ClientToolWriteConsentRequest {
14
14
  threadId: string;
15
15
  /** The batch's write tools, each once. */
16
16
  tools: readonly ClientToolWriteConsentTool[];
17
+ /** Who is asking, when the host named its agent (`agentUi.agentName`). */
18
+ agentName?: string;
17
19
  }
18
20
  /**
19
21
  * A host's own consent UI (`AthenaProviderProps.confirmClientToolWrites`).
@@ -28,8 +30,11 @@ export interface ClientToolWriteConsentStore {
28
30
  /** The question on screen, or `null`. */
29
31
  getPending(): ClientToolWriteConsentRequest | null;
30
32
  subscribe(listener: () => void): () => void;
31
- /** Whether the person already allowed writes in this conversation. */
32
- isGranted(threadId: string): boolean;
33
+ /** Whether the person already allowed this tool in this conversation. */
34
+ isGranted(args: {
35
+ threadId: string;
36
+ toolName: string;
37
+ }): boolean;
33
38
  /** Put the question to the person; resolves with their answer, or `abandoned`. */
34
39
  ask(request: ClientToolWriteConsentRequest): Promise<ClientToolWriteConsentDecision>;
35
40
  /** The person's answer to the pending question. */
@@ -39,8 +44,11 @@ export interface ClientToolWriteConsentStore {
39
44
  }): void;
40
45
  /** The pending question lost its call (stop, another conversation): close it. */
41
46
  abandon(requestId: string): void;
42
- /** Record a consent the host's own UI collected. */
43
- grant(threadId: string): void;
47
+ /** Record a consent the host's own UI collected, for the tools it asked about. */
48
+ grant(args: {
49
+ threadId: string;
50
+ toolNames: readonly string[];
51
+ }): void;
44
52
  /** A mounted card registers itself, so a question nobody can see is reported. */
45
53
  registerCard(): () => void;
46
54
  /** The store's person is gone: close the pending question and forget every yes. */
@@ -5,6 +5,10 @@ function createClientToolWriteConsentStore() {
5
5
  let pending = null;
6
6
  let cards = 0;
7
7
  const granted = /* @__PURE__ */ new Set();
8
+ const grantKey = (threadId, toolName) => `${threadId}\0${toolName}`;
9
+ const grantAll = (threadId, toolNames) => {
10
+ for (const toolName of toolNames) granted.add(grantKey(threadId, toolName));
11
+ };
8
12
  const listeners = /* @__PURE__ */ new Set();
9
13
  const notify = () => {
10
14
  for (const listener of Array.from(listeners)) listener();
@@ -23,9 +27,11 @@ function createClientToolWriteConsentStore() {
23
27
  listeners.delete(listener);
24
28
  };
25
29
  },
26
- isGranted: (threadId) => granted.has(threadId),
30
+ isGranted: ({ threadId, toolName }) => granted.has(grantKey(threadId, toolName)),
27
31
  ask(request) {
28
- if (granted.has(request.threadId)) return Promise.resolve("proceed");
32
+ if (request.tools.every((tool) => granted.has(grantKey(request.threadId, tool.name)))) {
33
+ return Promise.resolve("proceed");
34
+ }
29
35
  if (pending) settle("abandoned");
30
36
  if (cards === 0) {
31
37
  athenaDiagnostics.emit("sdk.config.warning", {
@@ -46,15 +52,20 @@ function createClientToolWriteConsentStore() {
46
52
  },
47
53
  answer({ requestId, allow }) {
48
54
  if ((pending == null ? void 0 : pending.request.requestId) !== requestId) return;
49
- if (allow) granted.add(pending.request.threadId);
55
+ if (allow) {
56
+ grantAll(
57
+ pending.request.threadId,
58
+ pending.request.tools.map((tool) => tool.name)
59
+ );
60
+ }
50
61
  settle(allow ? "proceed" : "declined");
51
62
  },
52
63
  abandon(requestId) {
53
64
  if ((pending == null ? void 0 : pending.request.requestId) !== requestId) return;
54
65
  settle("abandoned");
55
66
  },
56
- grant(threadId) {
57
- granted.add(threadId);
67
+ grant({ threadId, toolNames }) {
68
+ grantAll(threadId, toolNames);
58
69
  },
59
70
  registerCard() {
60
71
  cards += 1;
@@ -1 +1 @@
1
- {"version":3,"file":"client-tool-write-consent.js","sources":["../../src/chat/client-tool-write-consent.ts"],"sourcesContent":["'use client';\n\nimport type { ClientToolWriteConsentDecision } from '@athenaintel/agent-runtime';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\n/*\n * Write consent for the app's own frontend tools. A tool marked with\n * `requiresWriteConsent` parks its first call in a conversation until the\n * person answers; this store holds that question between the client-tool\n * bridge (which asks) and whatever draws it (`StatewireClientToolConsentCard`\n * in `AthenaChat`, or a host's own card on `useClientToolWriteConsent`).\n *\n * Allowing covers the rest of the conversation, as Athena's own apps do, for\n * the person who allowed it: a store belongs to one signed-in person on one\n * Athena (`useClientToolWriteConsentStore`), so someone else signing in to the\n * same mounted chat is asked for themselves. Nothing is remembered across a\n * reload: the next call asks again.\n */\n\n/** One write tool of a batch waiting on the person. */\nexport interface ClientToolWriteConsentTool {\n /** The tool's name, as the model calls it. */\n name: string;\n /** What it does, when the tool said (`requiresWriteConsent(…, { summary })`). */\n summary?: string;\n}\n\n/** The question a parked batch of write calls puts to the person. */\nexport interface ClientToolWriteConsentRequest {\n /** The parked request the answer resumes. */\n requestId: string;\n /** The conversation the answer covers. */\n threadId: string;\n /** The batch's write tools, each once. */\n tools: readonly ClientToolWriteConsentTool[];\n}\n\n/**\n * A host's own consent UI (`AthenaProviderProps.confirmClientToolWrites`).\n * Resolve `true` to allow the conversation's writes, `false` to decline this\n * batch. `signal` aborts when the question no longer has a call to answer\n * (the run was stopped): close the dialog then; the value is ignored.\n */\nexport type ClientToolWriteConfirmer = (\n request: ClientToolWriteConsentRequest & { signal: AbortSignal },\n) => Promise<boolean>;\n\nexport interface ClientToolWriteConsentStore {\n /** The question on screen, or `null`. */\n getPending(): ClientToolWriteConsentRequest | null;\n subscribe(listener: () => void): () => void;\n /** Whether the person already allowed writes in this conversation. */\n isGranted(threadId: string): boolean;\n /** Put the question to the person; resolves with their answer, or `abandoned`. */\n ask(request: ClientToolWriteConsentRequest): Promise<ClientToolWriteConsentDecision>;\n /** The person's answer to the pending question. */\n answer(args: { requestId: string; allow: boolean }): void;\n /** The pending question lost its call (stop, another conversation): close it. */\n abandon(requestId: string): void;\n /** Record a consent the host's own UI collected. */\n grant(threadId: string): void;\n /** A mounted card registers itself, so a question nobody can see is reported. */\n registerCard(): () => void;\n /** The store's person is gone: close the pending question and forget every yes. */\n dispose(): void;\n}\n\ninterface PendingConsent {\n request: ClientToolWriteConsentRequest;\n resolve: (decision: ClientToolWriteConsentDecision) => void;\n}\n\nexport function createClientToolWriteConsentStore(): ClientToolWriteConsentStore {\n let pending: PendingConsent | null = null;\n let cards = 0;\n const granted = new Set<string>();\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of Array.from(listeners)) listener();\n };\n const settle = (decision: ClientToolWriteConsentDecision) => {\n const settled = pending;\n pending = null;\n settled?.resolve(decision);\n notify();\n };\n\n return {\n getPending: () => pending?.request ?? null,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n isGranted: (threadId) => granted.has(threadId),\n ask(request) {\n if (granted.has(request.threadId)) return Promise.resolve('proceed');\n // One question at a time: a newer batch replaces a question whose call\n // the bridge has already given up on.\n if (pending) settle('abandoned');\n if (cards === 0) {\n athenaDiagnostics.emit('sdk.config.warning', {\n level: 'warn',\n error: new AthenaSdkError({\n code: 'config_invalid',\n message:\n 'A frontend tool is waiting for the person to allow it, and nothing on screen asks them.',\n hint: 'Render <AthenaChat> (it draws the question), mount <StatewireClientToolConsentCard>, or pass confirmClientToolWrites on <AthenaProvider>. The run stays parked until the question is answered.',\n context: { tools: request.tools.map((tool) => tool.name) },\n }),\n data: { tools: request.tools.map((tool) => tool.name) },\n });\n }\n return new Promise<ClientToolWriteConsentDecision>((resolve) => {\n pending = { request, resolve };\n notify();\n });\n },\n answer({ requestId, allow }) {\n if (pending?.request.requestId !== requestId) return;\n if (allow) granted.add(pending.request.threadId);\n settle(allow ? 'proceed' : 'declined');\n },\n abandon(requestId) {\n if (pending?.request.requestId !== requestId) return;\n settle('abandoned');\n },\n grant(threadId) {\n granted.add(threadId);\n },\n registerCard() {\n cards += 1;\n return () => {\n cards -= 1;\n };\n },\n dispose() {\n granted.clear();\n if (pending) settle('abandoned');\n },\n };\n}\n\n/**\n * The consent store for one signed-in person on one Athena. `scope` names\n * both (the provider passes the session principal and the sync host): when it\n * changes, the previous person's store is disposed (its question closed, its\n * yeses forgotten) and the next person starts with none.\n */\nexport function useClientToolWriteConsentStore(scope: string): ClientToolWriteConsentStore {\n const [held, setHeld] = useState(() => ({ scope, store: createClientToolWriteConsentStore() }));\n let current = held;\n if (held.scope !== scope) {\n // Replaced during render, so no commit ever pairs the new person with the\n // previous person's store.\n current = { scope, store: createClientToolWriteConsentStore() };\n setHeld(current);\n }\n const { store } = current;\n useEffect(() => () => store.dispose(), [store]);\n return store;\n}\n\n/** Provided by `AthenaProvider`; `null` outside one. */\nexport const ClientToolWriteConsentContext = createContext<ClientToolWriteConsentStore | null>(\n null,\n);\n\nconst NO_SUBSCRIPTION = () => () => {};\nconst NO_PENDING = () => null;\n\nexport interface ClientToolWriteConsent {\n /** The question waiting on the person, or `null`. */\n request: ClientToolWriteConsentRequest | null;\n /** Allow the conversation's writes and run the waiting calls. */\n allow: () => void;\n /** Decline the waiting calls; the agent is told, and a later call asks again. */\n decline: () => void;\n}\n\n/**\n * The write-consent question for the chat the nearest `AthenaProvider` shows,\n * for a host that draws its own card. Calling it marks the question as shown.\n */\nexport function useClientToolWriteConsent(): ClientToolWriteConsent {\n const store = useContext(ClientToolWriteConsentContext);\n const request = useSyncExternalStore(\n store?.subscribe ?? NO_SUBSCRIPTION,\n store?.getPending ?? NO_PENDING,\n NO_PENDING,\n );\n useEffect(() => store?.registerCard(), [store]);\n const requestId = request?.requestId;\n const allow = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: true });\n }, [requestId, store]);\n const decline = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: false });\n }, [requestId, store]);\n return { request, allow, decline };\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client-tool-write-consent.js","sources":["../../src/chat/client-tool-write-consent.ts"],"sourcesContent":["'use client';\n\nimport type { ClientToolWriteConsentDecision } from '@athenaintel/agent-runtime';\nimport {\n createContext,\n useCallback,\n useContext,\n useEffect,\n useState,\n useSyncExternalStore,\n} from 'react';\nimport { athenaDiagnostics } from '../diagnostics/athena-diagnostics';\nimport { AthenaSdkError } from '../diagnostics/errors';\n\n/*\n * Write consent for the app's own frontend tools. A tool marked with\n * `requiresWriteConsent` parks its first call in a conversation until the\n * person answers; this store holds that question between the client-tool\n * bridge (which asks) and whatever draws it (`StatewireClientToolConsentCard`\n * in `AthenaChat`, or a host's own card on `useClientToolWriteConsent`).\n *\n * Allowing covers the tools the question named, for the rest of the\n * conversation, for the person who allowed it: a yes to one tool is never a\n * yes to another. A store belongs to one signed-in person on one\n * Athena (`useClientToolWriteConsentStore`), so someone else signing in to the\n * same mounted chat is asked for themselves. Nothing is remembered across a\n * reload: the next call asks again.\n */\n\n/** One write tool of a batch waiting on the person. */\nexport interface ClientToolWriteConsentTool {\n /** The tool's name, as the model calls it. */\n name: string;\n /** What it does, when the tool said (`requiresWriteConsent(…, { summary })`). */\n summary?: string;\n}\n\n/** The question a parked batch of write calls puts to the person. */\nexport interface ClientToolWriteConsentRequest {\n /** The parked request the answer resumes. */\n requestId: string;\n /** The conversation the answer covers. */\n threadId: string;\n /** The batch's write tools, each once. */\n tools: readonly ClientToolWriteConsentTool[];\n /** Who is asking, when the host named its agent (`agentUi.agentName`). */\n agentName?: string;\n}\n\n/**\n * A host's own consent UI (`AthenaProviderProps.confirmClientToolWrites`).\n * Resolve `true` to allow the conversation's writes, `false` to decline this\n * batch. `signal` aborts when the question no longer has a call to answer\n * (the run was stopped): close the dialog then; the value is ignored.\n */\nexport type ClientToolWriteConfirmer = (\n request: ClientToolWriteConsentRequest & { signal: AbortSignal },\n) => Promise<boolean>;\n\nexport interface ClientToolWriteConsentStore {\n /** The question on screen, or `null`. */\n getPending(): ClientToolWriteConsentRequest | null;\n subscribe(listener: () => void): () => void;\n /** Whether the person already allowed this tool in this conversation. */\n isGranted(args: { threadId: string; toolName: string }): boolean;\n /** Put the question to the person; resolves with their answer, or `abandoned`. */\n ask(request: ClientToolWriteConsentRequest): Promise<ClientToolWriteConsentDecision>;\n /** The person's answer to the pending question. */\n answer(args: { requestId: string; allow: boolean }): void;\n /** The pending question lost its call (stop, another conversation): close it. */\n abandon(requestId: string): void;\n /** Record a consent the host's own UI collected, for the tools it asked about. */\n grant(args: { threadId: string; toolNames: readonly string[] }): void;\n /** A mounted card registers itself, so a question nobody can see is reported. */\n registerCard(): () => void;\n /** The store's person is gone: close the pending question and forget every yes. */\n dispose(): void;\n}\n\ninterface PendingConsent {\n request: ClientToolWriteConsentRequest;\n resolve: (decision: ClientToolWriteConsentDecision) => void;\n}\n\nexport function createClientToolWriteConsentStore(): ClientToolWriteConsentStore {\n let pending: PendingConsent | null = null;\n let cards = 0;\n const granted = new Set<string>();\n const grantKey = (threadId: string, toolName: string): string => `${threadId}\\u0000${toolName}`;\n const grantAll = (threadId: string, toolNames: readonly string[]) => {\n for (const toolName of toolNames) granted.add(grantKey(threadId, toolName));\n };\n const listeners = new Set<() => void>();\n const notify = () => {\n for (const listener of Array.from(listeners)) listener();\n };\n const settle = (decision: ClientToolWriteConsentDecision) => {\n const settled = pending;\n pending = null;\n settled?.resolve(decision);\n notify();\n };\n\n return {\n getPending: () => pending?.request ?? null,\n subscribe(listener) {\n listeners.add(listener);\n return () => {\n listeners.delete(listener);\n };\n },\n isGranted: ({ threadId, toolName }) => granted.has(grantKey(threadId, toolName)),\n ask(request) {\n if (request.tools.every((tool) => granted.has(grantKey(request.threadId, tool.name)))) {\n return Promise.resolve('proceed');\n }\n // One question at a time: a newer batch replaces a question whose call\n // the bridge has already given up on.\n if (pending) settle('abandoned');\n if (cards === 0) {\n athenaDiagnostics.emit('sdk.config.warning', {\n level: 'warn',\n error: new AthenaSdkError({\n code: 'config_invalid',\n message:\n 'A frontend tool is waiting for the person to allow it, and nothing on screen asks them.',\n hint: 'Render <AthenaChat> (it draws the question), mount <StatewireClientToolConsentCard>, or pass confirmClientToolWrites on <AthenaProvider>. The run stays parked until the question is answered.',\n context: { tools: request.tools.map((tool) => tool.name) },\n }),\n data: { tools: request.tools.map((tool) => tool.name) },\n });\n }\n return new Promise<ClientToolWriteConsentDecision>((resolve) => {\n pending = { request, resolve };\n notify();\n });\n },\n answer({ requestId, allow }) {\n if (pending?.request.requestId !== requestId) return;\n if (allow) {\n grantAll(\n pending.request.threadId,\n pending.request.tools.map((tool) => tool.name),\n );\n }\n settle(allow ? 'proceed' : 'declined');\n },\n abandon(requestId) {\n if (pending?.request.requestId !== requestId) return;\n settle('abandoned');\n },\n grant({ threadId, toolNames }) {\n grantAll(threadId, toolNames);\n },\n registerCard() {\n cards += 1;\n return () => {\n cards -= 1;\n };\n },\n dispose() {\n granted.clear();\n if (pending) settle('abandoned');\n },\n };\n}\n\n/**\n * The consent store for one signed-in person on one Athena. `scope` names\n * both (the provider passes the session principal and the sync host): when it\n * changes, the previous person's store is disposed (its question closed, its\n * yeses forgotten) and the next person starts with none.\n */\nexport function useClientToolWriteConsentStore(scope: string): ClientToolWriteConsentStore {\n const [held, setHeld] = useState(() => ({ scope, store: createClientToolWriteConsentStore() }));\n let current = held;\n if (held.scope !== scope) {\n // Replaced during render, so no commit ever pairs the new person with the\n // previous person's store.\n current = { scope, store: createClientToolWriteConsentStore() };\n setHeld(current);\n }\n const { store } = current;\n useEffect(() => () => store.dispose(), [store]);\n return store;\n}\n\n/** Provided by `AthenaProvider`; `null` outside one. */\nexport const ClientToolWriteConsentContext = createContext<ClientToolWriteConsentStore | null>(\n null,\n);\n\nconst NO_SUBSCRIPTION = () => () => {};\nconst NO_PENDING = () => null;\n\nexport interface ClientToolWriteConsent {\n /** The question waiting on the person, or `null`. */\n request: ClientToolWriteConsentRequest | null;\n /** Allow the conversation's writes and run the waiting calls. */\n allow: () => void;\n /** Decline the waiting calls; the agent is told, and a later call asks again. */\n decline: () => void;\n}\n\n/**\n * The write-consent question for the chat the nearest `AthenaProvider` shows,\n * for a host that draws its own card. Calling it marks the question as shown.\n */\nexport function useClientToolWriteConsent(): ClientToolWriteConsent {\n const store = useContext(ClientToolWriteConsentContext);\n const request = useSyncExternalStore(\n store?.subscribe ?? NO_SUBSCRIPTION,\n store?.getPending ?? NO_PENDING,\n NO_PENDING,\n );\n useEffect(() => store?.registerCard(), [store]);\n const requestId = request?.requestId;\n const allow = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: true });\n }, [requestId, store]);\n const decline = useCallback(() => {\n if (requestId !== undefined) store?.answer({ requestId, allow: false });\n }, [requestId, store]);\n return { request, allow, decline };\n}\n"],"names":[],"mappings":"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"}
@@ -257,7 +257,7 @@ const COMPOSER_SURFACE_CLASS = "relative flex flex-col gap-1 rounded-xl border b
257
257
  const COMPOSER_EDITOR_ROW_CLASS = "px-2.5 pt-1";
258
258
  const COMPOSER_TOOLBAR_CLASS = "flex items-center gap-1 border-t border-neutral-100 px-2 py-1.5";
259
259
  const COMPOSER_PAPER_DOCK_CLASS = "aui-composer-dock";
260
- const COMPOSER_PAPER_SURFACE_CLASS = "aui-composer-root relative flex w-full flex-col rounded-[1.375rem] border border-border bg-card p-2.5 text-card-foreground shadow-[0_1px_2px_rgba(0,0,0,0.04)] outline-none transition-shadow duration-150 focus-within:shadow-[0_8px_24px_-14px_rgba(0,0,0,0.25)] focus-within:ring-2 focus-within:ring-ring/40 data-[drag-active=true]:bg-primary/5 data-[drag-active=true]:ring-2 data-[drag-active=true]:ring-primary/40";
260
+ const COMPOSER_PAPER_SURFACE_CLASS = "aui-composer-root relative flex w-full flex-col rounded-[1.375rem] border border-border bg-card p-2.5 text-card-foreground shadow-[0_1px_2px_rgba(0,0,0,0.04)] outline-none transition-shadow duration-150 focus-within:border-foreground/20 focus-within:shadow-[0_8px_24px_-14px_rgba(0,0,0,0.25)] data-[drag-active=true]:bg-primary/5 data-[drag-active=true]:ring-2 data-[drag-active=true]:ring-primary/40";
261
261
  const COMPOSER_PAPER_EDITOR_ROW_CLASS = "px-0.5";
262
262
  const COMPOSER_PAPER_TOOLBAR_CLASS = "aui-composer-action-wrapper relative flex items-center justify-between gap-1 pt-1";
263
263
  const THREAD_SCROLL_TO_BOTTOM_CLASS = "inline-flex items-center justify-center rounded-full border bg-background/80 p-1 shadow-sm backdrop-blur transition-opacity hover:bg-accent disabled:invisible";
@@ -1 +1 @@
1
- {"version":3,"file":"message-components.cjs","sources":["../../../src/chat-ui/messages/message-components.tsx"],"sourcesContent":["import {\n ActionBarPrimitive,\n AuiIf,\n BranchPickerPrimitive,\n MessagePrimitive,\n useAuiState,\n useMessagePartText,\n useSmooth,\n type ToolCallMessagePartComponent,\n} from \"@assistant-ui/react\";\nimport {\n CHAT_EDIT_UNREACHABLE_LABEL,\n DEEP_AGENT_COMPOSER_MARKDOWN_KEY,\n hideUnterminatedMarkdownLink,\n inboundMessageBody,\n isGithubWakeMessage,\n isSubscriptionWakeMessage,\n isVoiceDelegationMessage,\n readDeepAgentComposerMarkdown,\n readRuntimeMessageFromSourceMessages,\n readSoleRuntimeMessageKind,\n useChatOutboxReachable,\n useDeepAgentChatOutboxContext,\n} from \"@athenaintel/agent-runtime\";\nimport {\n ArrowUpIcon,\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n ListPlusIcon,\n PencilIcon,\n RefreshCwIcon,\n} from \"lucide-react\";\nimport {\n type FC,\n type ReactNode,\n useMemo,\n} from \"react\";\nimport { useAthenaRunLive } from \"../../chat/use-athena-run-live\";\nimport type { ComposerAction } from \"../composer/composer-action\";\nimport { cn } from \"../../lib/utils\";\nimport { ChatMessageBody } from \"../rich-text/chat-message-body\";\nimport { AnswerLabel } from \"./answer-label\";\nimport { InboundChannelLine, InboundChannelSubject, useUserMessageCopy } from \"./inbound-channel-message\";\nimport { useMessageActionsReadOnly } from \"./message-actions-config\";\nimport { MessageImageGallery, MessageImagePart } from \"./message-images\";\nimport { RuntimeMessageCard } from \"./runtime-message\";\nimport { hasVisibleContent } from \"./running-indicator\";\nimport { VoiceDelegationMessage } from \"./voice-delegation-card\";\nimport { WakeMarkerMessage } from \"./wake-marker\";\nimport { SmallIconButton } from \"./thread-chrome\";\n\nexport {\n IconButton,\n SmallIconButton,\n THREAD_HEADER_ACTIONS_CLASS,\n THREAD_HEADER_CLASS,\n THREAD_HEADER_ICON_CLASS,\n THREAD_HEADER_PILL_CLASS,\n THREAD_PANEL_CLASS,\n} from \"./thread-chrome\";\n\nexport const BranchPicker: FC = () => (\n <BranchPickerPrimitive.Root\n hideWhenSingleBranch\n className=\"inline-flex items-center gap-0.5 text-xs text-muted-foreground\"\n data-testid=\"chat-branch-picker\"\n >\n <BranchPickerPrimitive.Previous asChild>\n <SmallIconButton\n tooltip=\"Previous branch\"\n data-testid=\"chat-branch-previous\"\n >\n <ChevronLeftIcon className=\"size-3\" />\n </SmallIconButton>\n </BranchPickerPrimitive.Previous>\n <span className=\"font-medium\" data-testid=\"chat-branch-count\">\n <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />\n </span>\n <BranchPickerPrimitive.Next asChild>\n <SmallIconButton tooltip=\"Next branch\" data-testid=\"chat-branch-next\">\n <ChevronRightIcon className=\"size-3\" />\n </SmallIconButton>\n </BranchPickerPrimitive.Next>\n </BranchPickerPrimitive.Root>\n);\n\nconst CopyActionButton: FC = () => (\n <ActionBarPrimitive.Copy asChild>\n <SmallIconButton tooltip=\"Copy\">\n <AuiIf condition={(state) => state.message.isCopied}>\n <CheckIcon className=\"size-3\" />\n </AuiIf>\n <AuiIf condition={(state) => !state.message.isCopied}>\n <CopyIcon className=\"size-3\" />\n </AuiIf>\n </SmallIconButton>\n </ActionBarPrimitive.Copy>\n);\n\n/**\n * The edit primitive does not check the runtime's edit capability itself —\n * clicking it on a runtime without one throws inside onPress, where no error\n * boundary can catch it — so the button must gate on the capability and, to\n * match the rewind semantics of `run/edit`, stay hidden while a run is live.\n * The read-only context covers threads the capability flags cannot: a host's\n * static `edit: true` still applies to fenced legacy conversations. While the\n * host's chat outbox says Athena can't be reached it is not offered: shown\n * disabled, its tooltip says why, and it comes back once Athena answers.\n */\nconst EditActionButton: FC = () => {\n const allowEdit = useAuiState((s) => s.thread.capabilities.edit);\n const isRunning = useAthenaRunLive();\n const readOnly = useMessageActionsReadOnly();\n const reachable = useChatOutboxReachable(useDeepAgentChatOutboxContext());\n if (!allowEdit || isRunning || readOnly) return null;\n if (!reachable) {\n return (\n <SmallIconButton\n tooltip={CHAT_EDIT_UNREACHABLE_LABEL}\n aria-disabled\n data-testid=\"chat-message-edit-unreachable\"\n className=\"cursor-not-allowed opacity-50 hover:bg-transparent hover:text-muted-foreground\"\n >\n <PencilIcon className=\"size-3\" />\n </SmallIconButton>\n );\n }\n return (\n <ActionBarPrimitive.Edit asChild>\n <SmallIconButton tooltip=\"Edit\" data-testid=\"chat-message-edit\">\n <PencilIcon className=\"size-3\" />\n </SmallIconButton>\n </ActionBarPrimitive.Edit>\n );\n};\n\n/** Regenerate dispatches `run/reload`, so it hides on read-only threads too. */\nconst RegenerateActionButton: FC = () => {\n const readOnly = useMessageActionsReadOnly();\n if (readOnly) return null;\n return (\n <ActionBarPrimitive.Reload asChild>\n <SmallIconButton tooltip=\"Regenerate\">\n <RefreshCwIcon className=\"size-3\" />\n </SmallIconButton>\n </ActionBarPrimitive.Reload>\n );\n};\n\n/**\n * Reveal-on-demand treatment for action bars: invisible and click-through\n * until the surrounding named group is hovered or a control inside receives\n * keyboard focus. The `focus-within` escape hatch is what keeps the hidden\n * controls reachable for keyboard users.\n */\nexport const ACTION_BAR_REVEAL_CLASS =\n \"pointer-events-none opacity-0 transition-opacity focus-within:pointer-events-auto focus-within:opacity-100\";\n\n/** Shared chat body; the reveal holds incomplete links until their URL is complete. */\nexport const MarkdownText: FC = () => {\n const revealed = useSmooth(useMessagePartText(), true);\n const isRunning = revealed.status.type === \"running\";\n const text = isRunning ? hideUnterminatedMarkdownLink(revealed.text) : revealed.text;\n return <ChatMessageBody text={text} isRunning={isRunning} />;\n};\n\n/**\n * A user message's text, shown as it was typed.\n *\n * A composer sends plain text exactly as typed, because the model reads it\n * literally, so the text alone no longer says which characters are\n * formatting: read as Markdown, `List<T>` loses `<T>`, `obj.__init__` shows a\n * bold \"init\" and `a\\\\b` one backslash. A composer send therefore carries the\n * draft it was serialized from (`metadata.composer_markdown`, only when the\n * two differ), and this renders that draft when it is a draft of the\n * message's own text (`readDeepAgentComposerMarkdown`), formatting and\n * mention chips included. Any other text — no draft, or one of other words —\n * renders with the typed reading (`ChatMessageBody typed`), which leaves raw\n * HTML and underscore emphasis as the characters they were typed with, so\n * `List<T>`, `obj.__init__` and `a\\b` show as typed there too.\n *\n * A message that came in over a channel (Slack, email, a text, Teams) starts\n * with the routing block the agent read (`<inbound-message-context>`); it\n * shows without it, as the words that were sent (`inboundMessageBody`), and\n * the line above the bubble opens the block (`InboundChannelSource`). Only a\n * message with the channel's `inbound_channel` envelope is one: tags a person\n * typed at the start of a chat message show as typed.\n *\n * Copy copies the message text, which is the text as sent, and Edit opens it\n * in a plain-text box, so neither reads the draft.\n */\nexport const UserMessageText: FC<{ text: string }> = ({ text }) => {\n const custom = useAuiState((s) => s.message.metadata?.custom);\n const shown = useMemo(() => {\n const body = inboundMessageBody({ custom, text });\n return (\n readDeepAgentComposerMarkdown({\n metadata: { [DEEP_AGENT_COMPOSER_MARKDOWN_KEY]: custom?.[DEEP_AGENT_COMPOSER_MARKDOWN_KEY] },\n text: body,\n }) ?? body\n );\n }, [custom, text]);\n return <ChatMessageBody text={shown} typed />;\n};\n\n/** A user message's parts: its text as typed, and its pictures, each opening the viewer. */\nconst USER_MESSAGE_PART_COMPONENTS = { Text: UserMessageText, Image: MessageImagePart };\n\n/** Copy on a user bubble copies what the bubble shows (`useUserMessageCopy`). */\nconst UserCopyActionButton: FC = () => {\n const { copy, disabled, isCopied } = useUserMessageCopy();\n return (\n <SmallIconButton\n tooltip=\"Copy\"\n onClick={copy}\n disabled={disabled}\n data-copied={isCopied ? \"true\" : undefined}\n >\n {isCopied ? <CheckIcon className=\"size-3\" /> : <CopyIcon className=\"size-3\" />}\n </SmallIconButton>\n );\n};\n\nconst UserActionBar: FC = () => (\n <ActionBarPrimitive.Root\n autohide=\"never\"\n className={cn(\n \"flex min-h-6 items-center gap-0.5\",\n ACTION_BAR_REVEAL_CLASS,\n \"group-hover/user-message:pointer-events-auto group-hover/user-message:opacity-100\",\n )}\n >\n <UserCopyActionButton />\n <EditActionButton />\n </ActionBarPrimitive.Root>\n);\n\nexport const UserMessage: FC = () => {\n // A GitHub wake or a subscription's delivery is an event, not words the\n // user typed: it renders as the compact wake marker instead of a bubble\n // quoting the injected text.\n const isWake = useAuiState(\n (s) =>\n isGithubWakeMessage(s.message.metadata?.custom) ||\n isSubscriptionWakeMessage(s.message.metadata?.custom),\n );\n // A voice delegation's anchor is the ask the live voice model handed to the\n // thread, not words the user typed: it renders as the shared voice-task\n // card on every host instead of a second user bubble under the spoken turn.\n const isVoiceDelegation = useAuiState((s) =>\n isVoiceDelegationMessage({ messageId: s.message.id, custom: s.message.metadata?.custom }),\n );\n if (isWake) {\n return (\n <MessagePrimitive.Root data-message-role=\"user\" className=\"mb-3\">\n <WakeMarkerMessage />\n </MessagePrimitive.Root>\n );\n }\n if (isVoiceDelegation) {\n return (\n <MessagePrimitive.Root data-message-role=\"user\" className=\"mb-3\">\n <VoiceDelegationMessage />\n </MessagePrimitive.Root>\n );\n }\n return (\n <MessagePrimitive.Root\n data-message-role=\"user\"\n className=\"group/user-message mb-3 flex justify-end\"\n >\n <div className=\"flex min-w-0 max-w-[85%] flex-col items-end gap-1\">\n {/* A message that came in over a channel: the line naming the channel\n and the sender, above the words that were sent. */}\n <InboundChannelLine />\n <div className=\"min-w-0 max-w-full rounded-2xl rounded-br-md bg-muted px-3.5 py-2 text-sm text-foreground\">\n <InboundChannelSubject />\n <MessageImageGallery>\n <MessagePrimitive.Content components={USER_MESSAGE_PART_COMPONENTS} />\n </MessageImageGallery>\n </div>\n <div className=\"flex min-h-6 items-center gap-1\">\n <BranchPicker />\n <UserActionBar />\n </div>\n </div>\n </MessagePrimitive.Root>\n );\n};\n\nexport interface CreateAssistantMessageOptions {\n /** Host-registered card for tools without a dedicated UI. */\n toolFallback: ToolCallMessagePartComponent;\n /** Extra action-bar controls appended after copy/regenerate. */\n extraActions?: ReactNode;\n /**\n * Renders the message body. Hosts that group content into activity\n * timelines supply their own; the default renders markdown and tool cards.\n */\n content?: FC;\n /**\n * Inline affordance rendered inside a runtime-ending card (e.g. a Continue\n * button). Hosts without one leave it unset.\n */\n runtimeMessageAction?: ReactNode;\n}\n\n/**\n * Builds the assistant bubble. Call this once per host (memoized on its\n * inputs) so the returned component identity stays stable across renders.\n */\nexport function createAssistantMessage({\n toolFallback,\n extraActions,\n content: Content,\n runtimeMessageAction,\n}: CreateAssistantMessageOptions): FC {\n const AssistantActionBar: FC = () => {\n // The latest reply keeps its actions visible; older replies reveal them\n // on hover so a long transcript is not a wall of icon rows.\n const isLast = useAuiState((s) => s.message.isLast);\n return (\n <ActionBarPrimitive.Root\n autohide=\"never\"\n className={cn(\n \"mt-1 flex min-h-6 items-center gap-0.5\",\n !isLast && [\n ACTION_BAR_REVEAL_CLASS,\n \"group-hover/assistant-message:pointer-events-auto group-hover/assistant-message:opacity-100\",\n ],\n )}\n >\n <CopyActionButton />\n <RegenerateActionButton />\n {extraActions}\n </ActionBarPrimitive.Root>\n );\n };\n\n // A run-lifecycle marker or handoff summary is a system event, not\n // assistant speech. When it owns the whole row, render the status card in\n // place of the bubble; when it is chunked in with the assistant's own work\n // (or is a handoff whose text IS the closing summary), keep the body and\n // frame it with the card above.\n const RuntimeEnding: FC<{ placement: \"row\" | \"banner\" }> = ({ placement }) => {\n // Select the message (a stable store reference) and derive from it: the\n // reader allocates a fresh object per call, and `useAuiState` compares\n // snapshots with `Object.is`, so selecting the reader's result directly\n // would re-render on every store update and trip React's update-depth\n // guard.\n const message = useAuiState((s) => s.message);\n const runtime = useMemo(() => readRuntimeMessageFromSourceMessages(message), [message]);\n if (!runtime || runtime.silent) return null;\n if (placement === \"row\" && !runtime.isSoleSourceMessage) return null;\n if (placement === \"banner\" && runtime.isSoleSourceMessage && runtime.kind !== \"handoff\") {\n return null;\n }\n return <RuntimeMessageCard message={runtime} action={runtimeMessageAction} />;\n };\n\n const AssistantMessage: FC = () => {\n // An assistant row exists before its first token streams; showing\n // copy/regenerate against nothing looks broken, so wait for content.\n const showActions = useAuiState((state) =>\n hasVisibleContent(state.message.content),\n );\n // Primitive selector: the reader builds a fresh object per call.\n const soleRuntimeKind = useAuiState((s) => readSoleRuntimeMessageKind(s.message));\n const soleRuntimeSilent = useAuiState(\n (s) => readRuntimeMessageFromSourceMessages(s.message)?.silent === true,\n );\n\n if (soleRuntimeKind && soleRuntimeKind !== \"handoff\") {\n if (soleRuntimeSilent) return null;\n return (\n <MessagePrimitive.Root\n data-message-role=\"assistant\"\n className=\"group group/assistant-message mb-3\"\n >\n <div className=\"max-w-[95%]\">\n <RuntimeEnding placement=\"row\" />\n </div>\n </MessagePrimitive.Root>\n );\n }\n\n return (\n <MessagePrimitive.Root\n data-message-role=\"assistant\"\n className=\"group group/assistant-message mb-3\"\n >\n <div className=\"max-w-[95%]\">\n <AnswerLabel className=\"mb-1\" />\n <RuntimeEnding placement=\"banner\" />\n {Content ? (\n <Content />\n ) : (\n <MessagePrimitive.Content\n components={{\n Text: MarkdownText,\n tools: { Fallback: toolFallback },\n }}\n />\n )}\n {showActions ? (\n <div className=\"flex min-h-6 items-center gap-1\">\n <BranchPicker />\n <AssistantActionBar />\n </div>\n ) : null}\n </div>\n </MessagePrimitive.Root>\n );\n };\n\n return AssistantMessage;\n}\n\n/** Composer chrome: the dock around the composer, its surface and its rows. */\nexport const COMPOSER_DOCK_CLASS = \"bg-background px-2.5 pb-2 pt-1.5\";\nexport const COMPOSER_SURFACE_CLASS =\n \"relative flex flex-col gap-1 rounded-xl border border-neutral-200 bg-white shadow-[0_2px_10px_rgba(54,45,33,0.08)]\";\nexport const COMPOSER_EDITOR_ROW_CLASS = \"px-2.5 pt-1\";\nexport const COMPOSER_TOOLBAR_CLASS =\n \"flex items-center gap-1 border-t border-neutral-100 px-2 py-1.5\";\n/**\n * Paper composer chrome (`ChatComposerDock appearance=\"paper\"`): a card-toned\n * surface with a larger radius, a focus-within ring, a drag-over tint keyed\n * off `data-drag-active`, and a bottom rail — the chrome the SDK's\n * `AthenaChat` thread renders. Hosts on the default chrome above are\n * untouched. The dock carries no padding of its own; the paper host's footer\n * spaces it. The radius and border colour also bind to the `--composer-radius`\n * / `--composer-border` theme variables in `styles.css`.\n */\nexport const COMPOSER_PAPER_DOCK_CLASS = \"aui-composer-dock\";\nexport const COMPOSER_PAPER_SURFACE_CLASS =\n \"aui-composer-root relative flex w-full flex-col rounded-[1.375rem] border border-border bg-card p-2.5 text-card-foreground shadow-[0_1px_2px_rgba(0,0,0,0.04)] outline-none transition-shadow duration-150 focus-within:shadow-[0_8px_24px_-14px_rgba(0,0,0,0.25)] focus-within:ring-2 focus-within:ring-ring/40 data-[drag-active=true]:bg-primary/5 data-[drag-active=true]:ring-2 data-[drag-active=true]:ring-primary/40\";\nexport const COMPOSER_PAPER_EDITOR_ROW_CLASS = \"px-0.5\";\nexport const COMPOSER_PAPER_TOOLBAR_CLASS =\n \"aui-composer-action-wrapper relative flex items-center justify-between gap-1 pt-1\";\nexport const THREAD_SCROLL_TO_BOTTOM_CLASS =\n \"inline-flex items-center justify-center rounded-full border bg-background/80 p-1 shadow-sm backdrop-blur transition-opacity hover:bg-accent disabled:invisible\";\n\nconst ComposerStopIcon: FC = () => (\n <svg\n aria-hidden=\"true\"\n className=\"size-4\"\n viewBox=\"0 0 16 16\"\n fill=\"currentColor\"\n >\n <rect width=\"10\" height=\"10\" x=\"3\" y=\"3\" rx=\"2\" />\n </svg>\n);\n\nexport interface ComposerStopButtonProps {\n onStop: () => void;\n disabled?: boolean;\n title?: string;\n}\n\nexport interface ComposerSendButtonProps {\n action: Extract<ComposerAction, \"send\" | \"queue\">;\n onSend: () => void;\n disabled?: boolean;\n title?: string;\n}\n\n/** Stop control shown while a run is in flight and there is nothing queued. */\nexport const ComposerStopButton: FC<ComposerStopButtonProps> = ({\n onStop,\n disabled = false,\n title = \"Stop generation\",\n}) => (\n <button\n type=\"button\"\n onClick={onStop}\n disabled={disabled}\n title={title}\n className={cn(\n \"inline-flex size-8 shrink-0 items-center justify-center rounded-lg bg-foreground text-background shadow-sm transition-colors hover:bg-foreground/80\",\n disabled && \"cursor-not-allowed opacity-60\",\n )}\n >\n <ComposerStopIcon />\n </button>\n);\n/** Send control, which becomes a queue control while a run is in flight. */\nexport const ComposerSendButton: FC<ComposerSendButtonProps> = ({\n action,\n onSend,\n disabled = false,\n title,\n}) => (\n <button\n type=\"button\"\n onClick={onSend}\n disabled={disabled}\n title={title ?? (action === \"queue\" ? \"Queue message\" : \"Send\")}\n className={cn(\n \"inline-flex size-8 shrink-0 items-center justify-center rounded-full shadow-sm transition-colors\",\n disabled\n ? \"cursor-not-allowed bg-muted text-muted-foreground opacity-60\"\n : \"bg-foreground text-background hover:bg-foreground/80\",\n )}\n >\n {action === \"queue\" ? (\n <ListPlusIcon className=\"size-4\" />\n ) : (\n <ArrowUpIcon className=\"size-4\" />\n )}\n </button>\n);\n"],"names":["jsxs","BranchPickerPrimitive","jsx","SmallIconButton","ChevronLeftIcon","ChevronRightIcon","ActionBarPrimitive","AuiIf","CheckIcon","CopyIcon","useAuiState","useAthenaRunLive","useMessageActionsReadOnly","useChatOutboxReachable","useDeepAgentChatOutboxContext","CHAT_EDIT_UNREACHABLE_LABEL","PencilIcon","RefreshCwIcon","useSmooth","useMessagePartText","hideUnterminatedMarkdownLink","ChatMessageBody","useMemo","inboundMessageBody","readDeepAgentComposerMarkdown","DEEP_AGENT_COMPOSER_MARKDOWN_KEY","MessageImagePart","useUserMessageCopy","cn","isGithubWakeMessage","isSubscriptionWakeMessage","isVoiceDelegationMessage","MessagePrimitive","WakeMarkerMessage","VoiceDelegationMessage","InboundChannelLine","InboundChannelSubject","MessageImageGallery","readRuntimeMessageFromSourceMessages","RuntimeMessageCard","hasVisibleContent","readSoleRuntimeMessageKind","AnswerLabel","ListPlusIcon","ArrowUpIcon"],"mappings":"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"}
1
+ {"version":3,"file":"message-components.cjs","sources":["../../../src/chat-ui/messages/message-components.tsx"],"sourcesContent":["import {\n ActionBarPrimitive,\n AuiIf,\n BranchPickerPrimitive,\n MessagePrimitive,\n useAuiState,\n useMessagePartText,\n useSmooth,\n type ToolCallMessagePartComponent,\n} from \"@assistant-ui/react\";\nimport {\n CHAT_EDIT_UNREACHABLE_LABEL,\n DEEP_AGENT_COMPOSER_MARKDOWN_KEY,\n hideUnterminatedMarkdownLink,\n inboundMessageBody,\n isGithubWakeMessage,\n isSubscriptionWakeMessage,\n isVoiceDelegationMessage,\n readDeepAgentComposerMarkdown,\n readRuntimeMessageFromSourceMessages,\n readSoleRuntimeMessageKind,\n useChatOutboxReachable,\n useDeepAgentChatOutboxContext,\n} from \"@athenaintel/agent-runtime\";\nimport {\n ArrowUpIcon,\n CheckIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n CopyIcon,\n ListPlusIcon,\n PencilIcon,\n RefreshCwIcon,\n} from \"lucide-react\";\nimport {\n type FC,\n type ReactNode,\n useMemo,\n} from \"react\";\nimport { useAthenaRunLive } from \"../../chat/use-athena-run-live\";\nimport type { ComposerAction } from \"../composer/composer-action\";\nimport { cn } from \"../../lib/utils\";\nimport { ChatMessageBody } from \"../rich-text/chat-message-body\";\nimport { AnswerLabel } from \"./answer-label\";\nimport { InboundChannelLine, InboundChannelSubject, useUserMessageCopy } from \"./inbound-channel-message\";\nimport { useMessageActionsReadOnly } from \"./message-actions-config\";\nimport { MessageImageGallery, MessageImagePart } from \"./message-images\";\nimport { RuntimeMessageCard } from \"./runtime-message\";\nimport { hasVisibleContent } from \"./running-indicator\";\nimport { VoiceDelegationMessage } from \"./voice-delegation-card\";\nimport { WakeMarkerMessage } from \"./wake-marker\";\nimport { SmallIconButton } from \"./thread-chrome\";\n\nexport {\n IconButton,\n SmallIconButton,\n THREAD_HEADER_ACTIONS_CLASS,\n THREAD_HEADER_CLASS,\n THREAD_HEADER_ICON_CLASS,\n THREAD_HEADER_PILL_CLASS,\n THREAD_PANEL_CLASS,\n} from \"./thread-chrome\";\n\nexport const BranchPicker: FC = () => (\n <BranchPickerPrimitive.Root\n hideWhenSingleBranch\n className=\"inline-flex items-center gap-0.5 text-xs text-muted-foreground\"\n data-testid=\"chat-branch-picker\"\n >\n <BranchPickerPrimitive.Previous asChild>\n <SmallIconButton\n tooltip=\"Previous branch\"\n data-testid=\"chat-branch-previous\"\n >\n <ChevronLeftIcon className=\"size-3\" />\n </SmallIconButton>\n </BranchPickerPrimitive.Previous>\n <span className=\"font-medium\" data-testid=\"chat-branch-count\">\n <BranchPickerPrimitive.Number /> / <BranchPickerPrimitive.Count />\n </span>\n <BranchPickerPrimitive.Next asChild>\n <SmallIconButton tooltip=\"Next branch\" data-testid=\"chat-branch-next\">\n <ChevronRightIcon className=\"size-3\" />\n </SmallIconButton>\n </BranchPickerPrimitive.Next>\n </BranchPickerPrimitive.Root>\n);\n\nconst CopyActionButton: FC = () => (\n <ActionBarPrimitive.Copy asChild>\n <SmallIconButton tooltip=\"Copy\">\n <AuiIf condition={(state) => state.message.isCopied}>\n <CheckIcon className=\"size-3\" />\n </AuiIf>\n <AuiIf condition={(state) => !state.message.isCopied}>\n <CopyIcon className=\"size-3\" />\n </AuiIf>\n </SmallIconButton>\n </ActionBarPrimitive.Copy>\n);\n\n/**\n * The edit primitive does not check the runtime's edit capability itself —\n * clicking it on a runtime without one throws inside onPress, where no error\n * boundary can catch it — so the button must gate on the capability and, to\n * match the rewind semantics of `run/edit`, stay hidden while a run is live.\n * The read-only context covers threads the capability flags cannot: a host's\n * static `edit: true` still applies to fenced legacy conversations. While the\n * host's chat outbox says Athena can't be reached it is not offered: shown\n * disabled, its tooltip says why, and it comes back once Athena answers.\n */\nconst EditActionButton: FC = () => {\n const allowEdit = useAuiState((s) => s.thread.capabilities.edit);\n const isRunning = useAthenaRunLive();\n const readOnly = useMessageActionsReadOnly();\n const reachable = useChatOutboxReachable(useDeepAgentChatOutboxContext());\n if (!allowEdit || isRunning || readOnly) return null;\n if (!reachable) {\n return (\n <SmallIconButton\n tooltip={CHAT_EDIT_UNREACHABLE_LABEL}\n aria-disabled\n data-testid=\"chat-message-edit-unreachable\"\n className=\"cursor-not-allowed opacity-50 hover:bg-transparent hover:text-muted-foreground\"\n >\n <PencilIcon className=\"size-3\" />\n </SmallIconButton>\n );\n }\n return (\n <ActionBarPrimitive.Edit asChild>\n <SmallIconButton tooltip=\"Edit\" data-testid=\"chat-message-edit\">\n <PencilIcon className=\"size-3\" />\n </SmallIconButton>\n </ActionBarPrimitive.Edit>\n );\n};\n\n/** Regenerate dispatches `run/reload`, so it hides on read-only threads too. */\nconst RegenerateActionButton: FC = () => {\n const readOnly = useMessageActionsReadOnly();\n if (readOnly) return null;\n return (\n <ActionBarPrimitive.Reload asChild>\n <SmallIconButton tooltip=\"Regenerate\">\n <RefreshCwIcon className=\"size-3\" />\n </SmallIconButton>\n </ActionBarPrimitive.Reload>\n );\n};\n\n/**\n * Reveal-on-demand treatment for action bars: invisible and click-through\n * until the surrounding named group is hovered or a control inside receives\n * keyboard focus. The `focus-within` escape hatch is what keeps the hidden\n * controls reachable for keyboard users.\n */\nexport const ACTION_BAR_REVEAL_CLASS =\n \"pointer-events-none opacity-0 transition-opacity focus-within:pointer-events-auto focus-within:opacity-100\";\n\n/** Shared chat body; the reveal holds incomplete links until their URL is complete. */\nexport const MarkdownText: FC = () => {\n const revealed = useSmooth(useMessagePartText(), true);\n const isRunning = revealed.status.type === \"running\";\n const text = isRunning ? hideUnterminatedMarkdownLink(revealed.text) : revealed.text;\n return <ChatMessageBody text={text} isRunning={isRunning} />;\n};\n\n/**\n * A user message's text, shown as it was typed.\n *\n * A composer sends plain text exactly as typed, because the model reads it\n * literally, so the text alone no longer says which characters are\n * formatting: read as Markdown, `List<T>` loses `<T>`, `obj.__init__` shows a\n * bold \"init\" and `a\\\\b` one backslash. A composer send therefore carries the\n * draft it was serialized from (`metadata.composer_markdown`, only when the\n * two differ), and this renders that draft when it is a draft of the\n * message's own text (`readDeepAgentComposerMarkdown`), formatting and\n * mention chips included. Any other text — no draft, or one of other words —\n * renders with the typed reading (`ChatMessageBody typed`), which leaves raw\n * HTML and underscore emphasis as the characters they were typed with, so\n * `List<T>`, `obj.__init__` and `a\\b` show as typed there too.\n *\n * A message that came in over a channel (Slack, email, a text, Teams) starts\n * with the routing block the agent read (`<inbound-message-context>`); it\n * shows without it, as the words that were sent (`inboundMessageBody`), and\n * the line above the bubble opens the block (`InboundChannelSource`). Only a\n * message with the channel's `inbound_channel` envelope is one: tags a person\n * typed at the start of a chat message show as typed.\n *\n * Copy copies the message text, which is the text as sent, and Edit opens it\n * in a plain-text box, so neither reads the draft.\n */\nexport const UserMessageText: FC<{ text: string }> = ({ text }) => {\n const custom = useAuiState((s) => s.message.metadata?.custom);\n const shown = useMemo(() => {\n const body = inboundMessageBody({ custom, text });\n return (\n readDeepAgentComposerMarkdown({\n metadata: { [DEEP_AGENT_COMPOSER_MARKDOWN_KEY]: custom?.[DEEP_AGENT_COMPOSER_MARKDOWN_KEY] },\n text: body,\n }) ?? body\n );\n }, [custom, text]);\n return <ChatMessageBody text={shown} typed />;\n};\n\n/** A user message's parts: its text as typed, and its pictures, each opening the viewer. */\nconst USER_MESSAGE_PART_COMPONENTS = { Text: UserMessageText, Image: MessageImagePart };\n\n/** Copy on a user bubble copies what the bubble shows (`useUserMessageCopy`). */\nconst UserCopyActionButton: FC = () => {\n const { copy, disabled, isCopied } = useUserMessageCopy();\n return (\n <SmallIconButton\n tooltip=\"Copy\"\n onClick={copy}\n disabled={disabled}\n data-copied={isCopied ? \"true\" : undefined}\n >\n {isCopied ? <CheckIcon className=\"size-3\" /> : <CopyIcon className=\"size-3\" />}\n </SmallIconButton>\n );\n};\n\nconst UserActionBar: FC = () => (\n <ActionBarPrimitive.Root\n autohide=\"never\"\n className={cn(\n \"flex min-h-6 items-center gap-0.5\",\n ACTION_BAR_REVEAL_CLASS,\n \"group-hover/user-message:pointer-events-auto group-hover/user-message:opacity-100\",\n )}\n >\n <UserCopyActionButton />\n <EditActionButton />\n </ActionBarPrimitive.Root>\n);\n\nexport const UserMessage: FC = () => {\n // A GitHub wake or a subscription's delivery is an event, not words the\n // user typed: it renders as the compact wake marker instead of a bubble\n // quoting the injected text.\n const isWake = useAuiState(\n (s) =>\n isGithubWakeMessage(s.message.metadata?.custom) ||\n isSubscriptionWakeMessage(s.message.metadata?.custom),\n );\n // A voice delegation's anchor is the ask the live voice model handed to the\n // thread, not words the user typed: it renders as the shared voice-task\n // card on every host instead of a second user bubble under the spoken turn.\n const isVoiceDelegation = useAuiState((s) =>\n isVoiceDelegationMessage({ messageId: s.message.id, custom: s.message.metadata?.custom }),\n );\n if (isWake) {\n return (\n <MessagePrimitive.Root data-message-role=\"user\" className=\"mb-3\">\n <WakeMarkerMessage />\n </MessagePrimitive.Root>\n );\n }\n if (isVoiceDelegation) {\n return (\n <MessagePrimitive.Root data-message-role=\"user\" className=\"mb-3\">\n <VoiceDelegationMessage />\n </MessagePrimitive.Root>\n );\n }\n return (\n <MessagePrimitive.Root\n data-message-role=\"user\"\n className=\"group/user-message mb-3 flex justify-end\"\n >\n <div className=\"flex min-w-0 max-w-[85%] flex-col items-end gap-1\">\n {/* A message that came in over a channel: the line naming the channel\n and the sender, above the words that were sent. */}\n <InboundChannelLine />\n <div className=\"min-w-0 max-w-full rounded-2xl rounded-br-md bg-muted px-3.5 py-2 text-sm text-foreground\">\n <InboundChannelSubject />\n <MessageImageGallery>\n <MessagePrimitive.Content components={USER_MESSAGE_PART_COMPONENTS} />\n </MessageImageGallery>\n </div>\n <div className=\"flex min-h-6 items-center gap-1\">\n <BranchPicker />\n <UserActionBar />\n </div>\n </div>\n </MessagePrimitive.Root>\n );\n};\n\nexport interface CreateAssistantMessageOptions {\n /** Host-registered card for tools without a dedicated UI. */\n toolFallback: ToolCallMessagePartComponent;\n /** Extra action-bar controls appended after copy/regenerate. */\n extraActions?: ReactNode;\n /**\n * Renders the message body. Hosts that group content into activity\n * timelines supply their own; the default renders markdown and tool cards.\n */\n content?: FC;\n /**\n * Inline affordance rendered inside a runtime-ending card (e.g. a Continue\n * button). Hosts without one leave it unset.\n */\n runtimeMessageAction?: ReactNode;\n}\n\n/**\n * Builds the assistant bubble. Call this once per host (memoized on its\n * inputs) so the returned component identity stays stable across renders.\n */\nexport function createAssistantMessage({\n toolFallback,\n extraActions,\n content: Content,\n runtimeMessageAction,\n}: CreateAssistantMessageOptions): FC {\n const AssistantActionBar: FC = () => {\n // The latest reply keeps its actions visible; older replies reveal them\n // on hover so a long transcript is not a wall of icon rows.\n const isLast = useAuiState((s) => s.message.isLast);\n return (\n <ActionBarPrimitive.Root\n autohide=\"never\"\n className={cn(\n \"mt-1 flex min-h-6 items-center gap-0.5\",\n !isLast && [\n ACTION_BAR_REVEAL_CLASS,\n \"group-hover/assistant-message:pointer-events-auto group-hover/assistant-message:opacity-100\",\n ],\n )}\n >\n <CopyActionButton />\n <RegenerateActionButton />\n {extraActions}\n </ActionBarPrimitive.Root>\n );\n };\n\n // A run-lifecycle marker or handoff summary is a system event, not\n // assistant speech. When it owns the whole row, render the status card in\n // place of the bubble; when it is chunked in with the assistant's own work\n // (or is a handoff whose text IS the closing summary), keep the body and\n // frame it with the card above.\n const RuntimeEnding: FC<{ placement: \"row\" | \"banner\" }> = ({ placement }) => {\n // Select the message (a stable store reference) and derive from it: the\n // reader allocates a fresh object per call, and `useAuiState` compares\n // snapshots with `Object.is`, so selecting the reader's result directly\n // would re-render on every store update and trip React's update-depth\n // guard.\n const message = useAuiState((s) => s.message);\n const runtime = useMemo(() => readRuntimeMessageFromSourceMessages(message), [message]);\n if (!runtime || runtime.silent) return null;\n if (placement === \"row\" && !runtime.isSoleSourceMessage) return null;\n if (placement === \"banner\" && runtime.isSoleSourceMessage && runtime.kind !== \"handoff\") {\n return null;\n }\n return <RuntimeMessageCard message={runtime} action={runtimeMessageAction} />;\n };\n\n const AssistantMessage: FC = () => {\n // An assistant row exists before its first token streams; showing\n // copy/regenerate against nothing looks broken, so wait for content.\n const showActions = useAuiState((state) =>\n hasVisibleContent(state.message.content),\n );\n // Primitive selector: the reader builds a fresh object per call.\n const soleRuntimeKind = useAuiState((s) => readSoleRuntimeMessageKind(s.message));\n const soleRuntimeSilent = useAuiState(\n (s) => readRuntimeMessageFromSourceMessages(s.message)?.silent === true,\n );\n\n if (soleRuntimeKind && soleRuntimeKind !== \"handoff\") {\n if (soleRuntimeSilent) return null;\n return (\n <MessagePrimitive.Root\n data-message-role=\"assistant\"\n className=\"group group/assistant-message mb-3\"\n >\n <div className=\"max-w-[95%]\">\n <RuntimeEnding placement=\"row\" />\n </div>\n </MessagePrimitive.Root>\n );\n }\n\n return (\n <MessagePrimitive.Root\n data-message-role=\"assistant\"\n className=\"group group/assistant-message mb-3\"\n >\n <div className=\"max-w-[95%]\">\n <AnswerLabel className=\"mb-1\" />\n <RuntimeEnding placement=\"banner\" />\n {Content ? (\n <Content />\n ) : (\n <MessagePrimitive.Content\n components={{\n Text: MarkdownText,\n tools: { Fallback: toolFallback },\n }}\n />\n )}\n {showActions ? (\n <div className=\"flex min-h-6 items-center gap-1\">\n <BranchPicker />\n <AssistantActionBar />\n </div>\n ) : null}\n </div>\n </MessagePrimitive.Root>\n );\n };\n\n return AssistantMessage;\n}\n\n/** Composer chrome: the dock around the composer, its surface and its rows. */\nexport const COMPOSER_DOCK_CLASS = \"bg-background px-2.5 pb-2 pt-1.5\";\nexport const COMPOSER_SURFACE_CLASS =\n \"relative flex flex-col gap-1 rounded-xl border border-neutral-200 bg-white shadow-[0_2px_10px_rgba(54,45,33,0.08)]\";\nexport const COMPOSER_EDITOR_ROW_CLASS = \"px-2.5 pt-1\";\nexport const COMPOSER_TOOLBAR_CLASS =\n \"flex items-center gap-1 border-t border-neutral-100 px-2 py-1.5\";\n/**\n * Paper composer chrome (`ChatComposerDock appearance=\"paper\"`): a card-toned\n * surface with a larger radius, a border that darkens a step on focus-within, a drag-over tint keyed\n * off `data-drag-active`, and a bottom rail — the chrome the SDK's\n * `AthenaChat` thread renders. Hosts on the default chrome above are\n * untouched. The dock carries no padding of its own; the paper host's footer\n * spaces it. The radius and border colour also bind to the `--composer-radius`\n * / `--composer-border` theme variables in `styles.css`.\n */\nexport const COMPOSER_PAPER_DOCK_CLASS = \"aui-composer-dock\";\nexport const COMPOSER_PAPER_SURFACE_CLASS =\n \"aui-composer-root relative flex w-full flex-col rounded-[1.375rem] border border-border bg-card p-2.5 text-card-foreground shadow-[0_1px_2px_rgba(0,0,0,0.04)] outline-none transition-shadow duration-150 focus-within:border-foreground/20 focus-within:shadow-[0_8px_24px_-14px_rgba(0,0,0,0.25)] data-[drag-active=true]:bg-primary/5 data-[drag-active=true]:ring-2 data-[drag-active=true]:ring-primary/40\";\nexport const COMPOSER_PAPER_EDITOR_ROW_CLASS = \"px-0.5\";\nexport const COMPOSER_PAPER_TOOLBAR_CLASS =\n \"aui-composer-action-wrapper relative flex items-center justify-between gap-1 pt-1\";\nexport const THREAD_SCROLL_TO_BOTTOM_CLASS =\n \"inline-flex items-center justify-center rounded-full border bg-background/80 p-1 shadow-sm backdrop-blur transition-opacity hover:bg-accent disabled:invisible\";\n\nconst ComposerStopIcon: FC = () => (\n <svg\n aria-hidden=\"true\"\n className=\"size-4\"\n viewBox=\"0 0 16 16\"\n fill=\"currentColor\"\n >\n <rect width=\"10\" height=\"10\" x=\"3\" y=\"3\" rx=\"2\" />\n </svg>\n);\n\nexport interface ComposerStopButtonProps {\n onStop: () => void;\n disabled?: boolean;\n title?: string;\n}\n\nexport interface ComposerSendButtonProps {\n action: Extract<ComposerAction, \"send\" | \"queue\">;\n onSend: () => void;\n disabled?: boolean;\n title?: string;\n}\n\n/** Stop control shown while a run is in flight and there is nothing queued. */\nexport const ComposerStopButton: FC<ComposerStopButtonProps> = ({\n onStop,\n disabled = false,\n title = \"Stop generation\",\n}) => (\n <button\n type=\"button\"\n onClick={onStop}\n disabled={disabled}\n title={title}\n className={cn(\n \"inline-flex size-8 shrink-0 items-center justify-center rounded-lg bg-foreground text-background shadow-sm transition-colors hover:bg-foreground/80\",\n disabled && \"cursor-not-allowed opacity-60\",\n )}\n >\n <ComposerStopIcon />\n </button>\n);\n/** Send control, which becomes a queue control while a run is in flight. */\nexport const ComposerSendButton: FC<ComposerSendButtonProps> = ({\n action,\n onSend,\n disabled = false,\n title,\n}) => (\n <button\n type=\"button\"\n onClick={onSend}\n disabled={disabled}\n title={title ?? (action === \"queue\" ? \"Queue message\" : \"Send\")}\n className={cn(\n \"inline-flex size-8 shrink-0 items-center justify-center rounded-full shadow-sm transition-colors\",\n disabled\n ? \"cursor-not-allowed bg-muted text-muted-foreground opacity-60\"\n : \"bg-foreground text-background hover:bg-foreground/80\",\n )}\n >\n {action === \"queue\" ? (\n <ListPlusIcon className=\"size-4\" />\n ) : (\n <ArrowUpIcon className=\"size-4\" />\n )}\n </button>\n);\n"],"names":["jsxs","BranchPickerPrimitive","jsx","SmallIconButton","ChevronLeftIcon","ChevronRightIcon","ActionBarPrimitive","AuiIf","CheckIcon","CopyIcon","useAuiState","useAthenaRunLive","useMessageActionsReadOnly","useChatOutboxReachable","useDeepAgentChatOutboxContext","CHAT_EDIT_UNREACHABLE_LABEL","PencilIcon","RefreshCwIcon","useSmooth","useMessagePartText","hideUnterminatedMarkdownLink","ChatMessageBody","useMemo","inboundMessageBody","readDeepAgentComposerMarkdown","DEEP_AGENT_COMPOSER_MARKDOWN_KEY","MessageImagePart","useUserMessageCopy","cn","isGithubWakeMessage","isSubscriptionWakeMessage","isVoiceDelegationMessage","MessagePrimitive","WakeMarkerMessage","VoiceDelegationMessage","InboundChannelLine","InboundChannelSubject","MessageImageGallery","readRuntimeMessageFromSourceMessages","RuntimeMessageCard","hasVisibleContent","readSoleRuntimeMessageKind","AnswerLabel","ListPlusIcon","ArrowUpIcon"],"mappings":"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"}
@@ -69,7 +69,7 @@ export declare const COMPOSER_EDITOR_ROW_CLASS = "px-2.5 pt-1";
69
69
  export declare const COMPOSER_TOOLBAR_CLASS = "flex items-center gap-1 border-t border-neutral-100 px-2 py-1.5";
70
70
  /**
71
71
  * Paper composer chrome (`ChatComposerDock appearance="paper"`): a card-toned
72
- * surface with a larger radius, a focus-within ring, a drag-over tint keyed
72
+ * surface with a larger radius, a border that darkens a step on focus-within, a drag-over tint keyed
73
73
  * off `data-drag-active`, and a bottom rail — the chrome the SDK's
74
74
  * `AthenaChat` thread renders. Hosts on the default chrome above are
75
75
  * untouched. The dock carries no padding of its own; the paper host's footer
@@ -77,7 +77,7 @@ export declare const COMPOSER_TOOLBAR_CLASS = "flex items-center gap-1 border-t
77
77
  * / `--composer-border` theme variables in `styles.css`.
78
78
  */
79
79
  export declare const COMPOSER_PAPER_DOCK_CLASS = "aui-composer-dock";
80
- export declare const COMPOSER_PAPER_SURFACE_CLASS = "aui-composer-root relative flex w-full flex-col rounded-[1.375rem] border border-border bg-card p-2.5 text-card-foreground shadow-[0_1px_2px_rgba(0,0,0,0.04)] outline-none transition-shadow duration-150 focus-within:shadow-[0_8px_24px_-14px_rgba(0,0,0,0.25)] focus-within:ring-2 focus-within:ring-ring/40 data-[drag-active=true]:bg-primary/5 data-[drag-active=true]:ring-2 data-[drag-active=true]:ring-primary/40";
80
+ export declare const COMPOSER_PAPER_SURFACE_CLASS = "aui-composer-root relative flex w-full flex-col rounded-[1.375rem] border border-border bg-card p-2.5 text-card-foreground shadow-[0_1px_2px_rgba(0,0,0,0.04)] outline-none transition-shadow duration-150 focus-within:border-foreground/20 focus-within:shadow-[0_8px_24px_-14px_rgba(0,0,0,0.25)] data-[drag-active=true]:bg-primary/5 data-[drag-active=true]:ring-2 data-[drag-active=true]:ring-primary/40";
81
81
  export declare const COMPOSER_PAPER_EDITOR_ROW_CLASS = "px-0.5";
82
82
  export declare const COMPOSER_PAPER_TOOLBAR_CLASS = "aui-composer-action-wrapper relative flex items-center justify-between gap-1 pt-1";
83
83
  export declare const THREAD_SCROLL_TO_BOTTOM_CLASS = "inline-flex items-center justify-center rounded-full border bg-background/80 p-1 shadow-sm backdrop-blur transition-opacity hover:bg-accent disabled:invisible";
@@ -256,7 +256,7 @@ const COMPOSER_SURFACE_CLASS = "relative flex flex-col gap-1 rounded-xl border b
256
256
  const COMPOSER_EDITOR_ROW_CLASS = "px-2.5 pt-1";
257
257
  const COMPOSER_TOOLBAR_CLASS = "flex items-center gap-1 border-t border-neutral-100 px-2 py-1.5";
258
258
  const COMPOSER_PAPER_DOCK_CLASS = "aui-composer-dock";
259
- const COMPOSER_PAPER_SURFACE_CLASS = "aui-composer-root relative flex w-full flex-col rounded-[1.375rem] border border-border bg-card p-2.5 text-card-foreground shadow-[0_1px_2px_rgba(0,0,0,0.04)] outline-none transition-shadow duration-150 focus-within:shadow-[0_8px_24px_-14px_rgba(0,0,0,0.25)] focus-within:ring-2 focus-within:ring-ring/40 data-[drag-active=true]:bg-primary/5 data-[drag-active=true]:ring-2 data-[drag-active=true]:ring-primary/40";
259
+ const COMPOSER_PAPER_SURFACE_CLASS = "aui-composer-root relative flex w-full flex-col rounded-[1.375rem] border border-border bg-card p-2.5 text-card-foreground shadow-[0_1px_2px_rgba(0,0,0,0.04)] outline-none transition-shadow duration-150 focus-within:border-foreground/20 focus-within:shadow-[0_8px_24px_-14px_rgba(0,0,0,0.25)] data-[drag-active=true]:bg-primary/5 data-[drag-active=true]:ring-2 data-[drag-active=true]:ring-primary/40";
260
260
  const COMPOSER_PAPER_EDITOR_ROW_CLASS = "px-0.5";
261
261
  const COMPOSER_PAPER_TOOLBAR_CLASS = "aui-composer-action-wrapper relative flex items-center justify-between gap-1 pt-1";
262
262
  const THREAD_SCROLL_TO_BOTTOM_CLASS = "inline-flex items-center justify-center rounded-full border bg-background/80 p-1 shadow-sm backdrop-blur transition-opacity hover:bg-accent disabled:invisible";