@alia.onl/sdk 15.8.0 → 19.1.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 (142) hide show
  1. package/CHANGELOG.md +262 -13
  2. package/README.md +223 -151
  3. package/package.json +16 -6
  4. package/src/animations.ts +0 -1
  5. package/src/components/AliaAskProvider.tsx +127 -0
  6. package/src/components/AliaChatContent.tsx +604 -392
  7. package/src/components/AliaChatHeader.tsx +14 -38
  8. package/src/components/AliaChatScreen.tsx +56 -31
  9. package/src/components/AliaChatSheet.tsx +110 -36
  10. package/src/components/AliaComposer.tsx +1 -1
  11. package/src/components/AliaParticipants.tsx +202 -0
  12. package/src/components/AliaParticipantsHeader.tsx +60 -0
  13. package/src/components/AliaSharedConversation.tsx +86 -0
  14. package/src/components/AliaWelcomeMessage.tsx +18 -5
  15. package/src/components/Image.tsx +12 -0
  16. package/src/components/Markdown.tsx +498 -14
  17. package/src/components/PlanPreviewCard.tsx +7 -49
  18. package/src/components/ReplyMarkdown.tsx +359 -0
  19. package/src/components/RichBlocks.tsx +195 -0
  20. package/src/components/canvas/CanvasComponent.tsx +59 -0
  21. package/src/components/canvas/ChartRenderer.tsx +73 -0
  22. package/src/components/canvas/CodeRenderer.tsx +24 -0
  23. package/src/components/canvas/FormRenderer.tsx +101 -0
  24. package/src/components/canvas/MarkdownRenderer.tsx +14 -0
  25. package/src/components/canvas/TableRenderer.tsx +47 -0
  26. package/src/components/thought/FilesAndSources.tsx +120 -0
  27. package/src/components/thought/ThoughtPanel.tsx +417 -0
  28. package/src/components/thought/ToolStep.tsx +141 -0
  29. package/src/components/thread/AgentResultCard.tsx +125 -0
  30. package/src/components/thread/AgentTaskCard.tsx +127 -0
  31. package/src/components/thread/AliaThread.tsx +401 -0
  32. package/src/components/thread/MessageBlockBoundary.tsx +78 -0
  33. package/src/components/thread/NewConversationOffer.tsx +54 -0
  34. package/src/components/thread/ThreadRow.tsx +458 -0
  35. package/src/components/thread/ThreadSearch.tsx +129 -0
  36. package/src/components/thread/ToolResultCard.tsx +254 -0
  37. package/src/components/thread/TurnMenu.tsx +54 -0
  38. package/src/components/thread/TurnStatusLine.tsx +34 -0
  39. package/src/components/voice/AliaVoiceCall.tsx +21 -0
  40. package/src/components/voice/VoiceControls.tsx +2 -28
  41. package/src/components/voice/VoiceSession.tsx +39 -11
  42. package/src/hooks/useAliaActivity.ts +2 -2
  43. package/src/hooks/useAtBottom.ts +51 -0
  44. package/src/hooks/useAttachMenu.ts +94 -0
  45. package/src/hooks/useChatComposer.ts +299 -0
  46. package/src/hooks/useChatVoice.ts +129 -0
  47. package/src/hooks/useDocumentPicker.ts +3 -29
  48. package/src/hooks/useFailedTurnToast.ts +43 -0
  49. package/src/hooks/useMessageVote.ts +50 -0
  50. package/src/hooks/useOpenThought.ts +79 -0
  51. package/src/hooks/usePendingQuestions.ts +2 -2
  52. package/src/hooks/useReadAloud.ts +92 -0
  53. package/src/hooks/useShareHandler.ts +24 -0
  54. package/src/hooks/useSoundEffects.ts +1 -1
  55. package/src/hooks/useThoughtScope.ts +59 -0
  56. package/src/hooks/useThreadRows.ts +145 -0
  57. package/src/hooks/useThreadView.ts +68 -0
  58. package/src/hooks/useTimelineWindow.ts +256 -0
  59. package/src/hooks/useVoiceLauncher.ts +180 -0
  60. package/src/hooks/useVoiceRoom.ts +4 -4
  61. package/src/index.ts +43 -29
  62. package/src/lib/activity-store.ts +1 -1
  63. package/src/lib/add-menu.ts +7 -0
  64. package/src/lib/call-thread.ts +33 -0
  65. package/src/lib/citations.ts +79 -2
  66. package/src/lib/clipboard.ts +23 -0
  67. package/src/lib/content-utils.ts +10 -0
  68. package/src/lib/copy-en.ts +284 -0
  69. package/src/lib/copy.ts +32 -0
  70. package/src/lib/document-picker.ts +31 -0
  71. package/src/lib/flights.ts +22 -13
  72. package/src/lib/focus-return.ts +66 -0
  73. package/src/lib/keyboard.native.tsx +19 -0
  74. package/src/lib/keyboard.tsx +9 -0
  75. package/src/lib/labels.tsx +69 -17
  76. package/src/lib/participants.ts +86 -0
  77. package/src/lib/power-level-options.ts +66 -0
  78. package/src/lib/questions.ts +2 -2
  79. package/src/lib/task-labels.ts +4 -4
  80. package/src/lib/tool-call-status.ts +1 -1
  81. package/src/lib/tool-icons.ts +36 -0
  82. package/src/lib/tool-renderers.tsx +48 -0
  83. package/src/lib/turn-state.ts +2 -2
  84. package/src/lib/voice-call-request.ts +49 -0
  85. package/src/lib/voice-call.ts +98 -35
  86. package/src/lib/voice-controls-labels.ts +31 -0
  87. package/src/lib/voice-protocol.ts +34 -15
  88. package/src/lib/work-log.ts +6 -6
  89. package/src/runtime/agents.ts +151 -0
  90. package/src/runtime/api.ts +141 -0
  91. package/src/runtime/app-assertion.ts +53 -0
  92. package/src/runtime/attachment-utils.ts +96 -0
  93. package/src/runtime/composer-draft-store.ts +270 -0
  94. package/src/runtime/config.tsx +186 -0
  95. package/src/runtime/conversation-store.ts +49 -0
  96. package/src/runtime/conversations.ts +557 -0
  97. package/src/runtime/error-utils.ts +104 -0
  98. package/src/runtime/host-tools.ts +140 -0
  99. package/src/runtime/index.ts +113 -0
  100. package/src/runtime/message-history.ts +124 -0
  101. package/src/runtime/model.ts +39 -0
  102. package/src/runtime/oxy-files.ts +37 -0
  103. package/src/runtime/power-levels.ts +66 -0
  104. package/src/runtime/query-keys.ts +34 -0
  105. package/src/runtime/random-uuid.ts +25 -0
  106. package/src/runtime/routes.ts +51 -0
  107. package/src/runtime/sharing.ts +165 -0
  108. package/src/runtime/sse-frame-reader.ts +88 -0
  109. package/src/runtime/stream-events.ts +146 -0
  110. package/src/runtime/stream-outcome.ts +21 -0
  111. package/src/runtime/thought-store.ts +103 -0
  112. package/src/runtime/thread/agent-activity.ts +261 -0
  113. package/src/runtime/thread/automation-labels.ts +79 -0
  114. package/src/runtime/thread/conversation-export.ts +135 -0
  115. package/src/runtime/thread/message-days.ts +120 -0
  116. package/src/runtime/thread/message-processor.ts +93 -0
  117. package/src/runtime/thread/task-utils.ts +11 -0
  118. package/src/runtime/thread/thought-utils.ts +752 -0
  119. package/src/runtime/thread/thread-history.ts +139 -0
  120. package/src/runtime/thread/timeline.ts +202 -0
  121. package/src/runtime/thread/title-tags.ts +8 -0
  122. package/src/runtime/thread/tool-cards.ts +383 -0
  123. package/src/runtime/turn-failure.ts +108 -0
  124. package/src/runtime/types.ts +148 -0
  125. package/src/runtime/usage-limit-error.ts +44 -0
  126. package/src/runtime/use-agent-activity.ts +80 -0
  127. package/src/runtime/use-conversation-stream.ts +1433 -0
  128. package/src/runtime/use-conversation-thread.ts +556 -0
  129. package/src/runtime/use-start-conversation.ts +107 -0
  130. package/src/runtime/use-thread-history.ts +64 -0
  131. package/src/runtime/use-thread-search.ts +95 -0
  132. package/src/runtime/use-turn-edit.ts +99 -0
  133. package/src/tools.ts +2 -2
  134. package/src/types.ts +18 -47
  135. package/src/components/AliaChatMessageList.tsx +0 -532
  136. package/src/components/ThinkingBlock.tsx +0 -43
  137. package/src/hooks/useAliaChat.ts +0 -573
  138. package/src/lib/catalogue.ts +0 -236
  139. package/src/lib/chat-stream.ts +0 -655
  140. package/src/lib/chat-transport.ts +0 -98
  141. package/src/lib/delegation.ts +0 -34
  142. package/src/lib/work-log-copy.ts +0 -61
@@ -7,19 +7,16 @@ import {
7
7
  } from '@oxy.so/bloom/dropdown-menu';
8
8
  import { RiArrowLeftLine, RiDeleteBin6Line, RiMoreFill, RiShare2Line } from '@oxy.so/bloom/icons';
9
9
  import { PageHeader } from '@oxy.so/bloom/page-header';
10
- import { AgentProfile } from '@oxy.so/bloom/agent-reply';
11
- import { memo, type ReactNode, useMemo } from 'react';
12
- import { readAgentCharacter } from '../lib/agent-character';
10
+ import { memo, type ReactNode } from 'react';
13
11
  import { useLabels } from '../lib/labels';
14
- import { ALIA_ANCHORS, useAliaFlights } from '../lib/flights';
15
- import { ALIA_AVATAR } from './AliaAvatar';
16
- import type { AliaChatAgent } from './AliaChatContent';
12
+ import type { AliaChatParticipants } from '../lib/participants';
13
+ import { AliaParticipants } from './AliaParticipants';
17
14
 
18
15
  export interface AliaChatHeaderProps {
19
16
  /** The chat has messages: there is something to share and to clear. */
20
17
  hasMessages: boolean;
21
- /** Delegated agents that have answered: they join Alia in the profile, up to three in all. */
22
- agents?: AliaChatAgent[];
18
+ /** Who is in the conversation (`AliaChatState.participants`): the bar's title. */
19
+ participants: AliaChatParticipants;
23
20
  /** Clears the conversation, from the menu. */
24
21
  onClear: () => void;
25
22
  /** Draws the back button at the start of the bar; omit where there is nowhere to go back to. */
@@ -32,44 +29,19 @@ export interface AliaChatHeaderProps {
32
29
  leading?: ReactNode;
33
30
  /** The host's own actions, before share and the menu. */
34
31
  trailing?: ReactNode;
35
- /** Pressing Alia's avatar or name. Omitted, the avatar waves. */
32
+ /** Pressing the participants. Omitted, every avatar in it waves. */
36
33
  onProfilePress?: () => void;
37
34
  }
38
35
 
39
- /**
40
- * Alia's profile, Bloom's `AgentProfile`: her avatar above a name capsule, joined by the agents
41
- * that answered ("Alia + Lumi + Atlas"). Pressing it makes every avatar wave.
42
- */
43
- function AliaProfile({ agents, onPress }: { agents: AliaChatAgent[]; onPress?: () => void }) {
44
- const flights = useAliaFlights();
45
- const team = useMemo(
46
- () => [
47
- { id: 'alia', name: 'Alia', avatar: ALIA_AVATAR },
48
- ...agents.map((agent) => ({ ...agent, avatar: readAgentCharacter(agent.character) })),
49
- ],
50
- [agents],
51
- );
52
- return (
53
- <AgentProfile
54
- agents={team}
55
- onPress={onPress ?? noop}
56
- avatarRef={flights?.anchor(ALIA_ANCHORS.profile)}
57
- avatarHidden={flights?.isLanding(ALIA_ANCHORS.profile)}
58
- />
59
- );
60
- }
61
-
62
- const noop = () => undefined;
63
-
64
36
  /**
65
37
  * The chat's top bar: Bloom's `PageHeader`, set up as the shell sets it up for the
66
- * agents' screens (inline, no scrim, no safe area). Alia's profile is its title only once
67
- * there is a conversation: the empty chat already shows the avatar beside its greeting,
38
+ * agents' screens (inline, no scrim, no safe area). The participants are its title only once
39
+ * there is a conversation: the empty chat already shows the lead's avatar beside its greeting,
68
40
  * so the bar carries just its buttons until then.
69
41
  */
70
42
  export const AliaChatHeader = memo(function AliaChatHeader({
71
43
  hasMessages,
72
- agents = [],
44
+ participants,
73
45
  onClear,
74
46
  onBack,
75
47
  onShare,
@@ -98,7 +70,11 @@ export const AliaChatHeader = memo(function AliaChatHeader({
98
70
  </ButtonGroup>
99
71
  ) : undefined)
100
72
  }
101
- title={hasMessages ? <AliaProfile agents={agents} onPress={onProfilePress} /> : undefined}
73
+ title={
74
+ hasMessages ? (
75
+ <AliaParticipants participants={participants} onPress={onProfilePress} />
76
+ ) : undefined
77
+ }
102
78
  titleAlign="center"
103
79
  actions={
104
80
  <>
@@ -12,9 +12,9 @@
12
12
  * />
13
13
  * ```
14
14
  *
15
- * Voice is opt-in — pass `voiceSession={VoiceSession}` from
16
- * `@alia.onl/sdk/voice` to offer calls. Without it the screen is text-only and
17
- * `react-native-audio-api` never enters the bundle.
15
+ * Voice is part of the screen: the button, the call bar, the notices and the transcript in the
16
+ * thread. `voice={false}` makes it text-only; `voice={{ canStartVoice, onCallEnded, … }}` brings
17
+ * the host's own callbacks. The call loads on the first one.
18
18
  */
19
19
 
20
20
  import type React from 'react';
@@ -24,16 +24,31 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
24
24
  import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
25
25
  import { ALIA_CHARACTER_RUNTIME_URL } from '../lib/character-runtime';
26
26
  import { KeyboardAvoidingView } from '../lib/keyboard';
27
- import { AliaChatContent, type AliaChatContentProps } from './AliaChatContent';
27
+ import {
28
+ type AliaChatAgentRef,
29
+ AliaChatContent,
30
+ type AliaChatContentProps,
31
+ } from './AliaChatContent';
28
32
  import { AliaChatHeader } from './AliaChatHeader';
29
33
  import type { AliaSuggestion } from '../types';
30
34
  import type { AliaChatLabels } from '../lib/labels';
31
- import type { VoiceSessionComponent } from '../types';
35
+ import type { AliaToolRenderers } from '../lib/tool-renderers';
36
+ import { useShareHandler } from '../hooks/useShareHandler';
37
+ import type { AliaHostTools } from '../runtime/host-tools';
38
+ import type { AliaConversationShare } from '../runtime/sharing';
32
39
 
33
40
  export interface AliaChatScreenProps {
41
+ /** The agent the screen is with, any agent the person can reach; omitted, Alia. */
42
+ agent?: AliaChatAgentRef;
34
43
  /** App context injected into system prompt */
35
44
  clientContext?: string;
36
- /** Kaana routing profile. Checked against `GET /catalogue`; omitted, the build's preference is used. */
45
+ /** The UI effects this app performs for the lead (see `AliaChatContentProps.hostTools`). */
46
+ hostTools?: AliaHostTools;
47
+ /** The app's own drawing of catalogue tools' results, keyed `<app>:<tool>`. */
48
+ toolRenderers?: AliaToolRenderers;
49
+ /** The lead's feature gate refused something: show the app's upsell for `key`. */
50
+ onFeatureLocked?: (key: string) => void;
51
+ /** The power level (`auto`, `instant`, `medium`, `high`, `xhigh`, `pro`, `ultra`) or an exact `publisher/model` every turn is sent with; omitted, the server picks. */
37
52
  model?: string;
38
53
  /** API URL override */
39
54
  apiUrl?: string;
@@ -41,8 +56,11 @@ export interface AliaChatScreenProps {
41
56
  onBack?: () => void;
42
57
  /** Optional header left content instead of the back button (e.g. a drawer's menu button) */
43
58
  headerLeft?: React.ReactNode;
44
- /** Draws the header's share button once there is a conversation; omitted, no share button. */
45
- onShare?: () => void;
59
+ /**
60
+ * Draws the header's share button once there is a conversation; omitted, no share button.
61
+ * Pressed, the conversation's read-only link is minted and handed here for the app to show.
62
+ */
63
+ onShare?: (share: AliaConversationShare) => void;
46
64
  /** More rows for the header's menu (`DropdownMenuItem`s from `@oxy.so/bloom/dropdown-menu`). */
47
65
  headerMenu?: React.ReactNode;
48
66
  /** Optional header right action (e.g., settings button) */
@@ -53,16 +71,14 @@ export interface AliaChatScreenProps {
53
71
  welcomeSubtitle?: string;
54
72
  /** Welcome screen suggestions */
55
73
  welcomeSuggestions?: AliaSuggestion[];
56
- /** The agents the host knows, with their look: a delegation's thinking avatar is drawn from them until the agent answers. */
57
- agents?: AliaChatContentProps['agents'];
58
74
  /** What the host found for the typed text, shown over the composer (see `onSuggestionQuery`). */
59
75
  suggestionMatches?: AliaSuggestion[];
60
76
  /** The typed text, once typing has settled, for the host to search suggestions with. */
61
77
  onSuggestionQuery?: (query: string) => void;
62
78
  /** Dark-mode flag — forwarded to the ambient wave overlay. */
63
79
  isDarkMode?: boolean;
64
- /** Voice capability — `VoiceSession` from `@alia.onl/sdk/voice`. */
65
- voiceSession?: VoiceSessionComponent;
80
+ /** The chat's voice call: on by default, `false` for text only, an object for the host's callbacks. */
81
+ voice?: AliaChatContentProps['voice'];
66
82
  /** Overrides for the chat's own copy (header, composer, plan, research…); omitted, English. */
67
83
  labels?: Partial<AliaChatLabels>;
68
84
  /** Where the 3D avatar's runtime is served from; omitted, Alia's shared copy (`ALIA_CHARACTER_RUNTIME_URL`). */
@@ -70,7 +86,11 @@ export interface AliaChatScreenProps {
70
86
  }
71
87
 
72
88
  export function AliaChatScreen({
89
+ agent,
73
90
  clientContext,
91
+ hostTools,
92
+ toolRenderers,
93
+ onFeatureLocked,
74
94
  model,
75
95
  apiUrl,
76
96
  onBack,
@@ -81,11 +101,10 @@ export function AliaChatScreen({
81
101
  welcomeGreeting,
82
102
  welcomeSubtitle,
83
103
  welcomeSuggestions,
84
- agents,
85
104
  suggestionMatches,
86
105
  onSuggestionQuery,
87
106
  isDarkMode,
88
- voiceSession,
107
+ voice,
89
108
  labels,
90
109
  characterRuntimeUrl = ALIA_CHARACTER_RUNTIME_URL,
91
110
  }: AliaChatScreenProps) {
@@ -100,20 +119,23 @@ export function AliaChatScreen({
100
119
  // keyboard's height minus the inset: the composer sits on the keyboard, not
101
120
  // one inset above it. (Its parent-relative layout would read as zero overlap
102
121
  // this deep in a host's tree, which is why the offset is measured.)
103
- const header = useCallback<NonNullable<AliaChatContentProps['header']>>(
104
- ({ hasMessages, clear, agents: answered }) => (
105
- <AliaChatHeader
106
- hasMessages={hasMessages}
107
- agents={answered}
108
- onClear={clear}
109
- onBack={onBack}
110
- onShare={onShare}
111
- menu={headerMenu}
112
- leading={headerLeft}
113
- trailing={headerRight}
114
- />
115
- ),
116
- [onBack, onShare, headerMenu, headerLeft, headerRight],
122
+ const share = useShareHandler(onShare);
123
+ const chrome = useCallback<NonNullable<AliaChatContentProps['chrome']>>(
124
+ ({ hasMessages, startOver, participants, conversationId }) => ({
125
+ header: (
126
+ <AliaChatHeader
127
+ hasMessages={hasMessages}
128
+ participants={participants}
129
+ onClear={startOver}
130
+ onBack={onBack}
131
+ onShare={share && conversationId !== undefined ? () => share(conversationId) : undefined}
132
+ menu={headerMenu}
133
+ leading={headerLeft}
134
+ trailing={headerRight}
135
+ />
136
+ ),
137
+ }),
138
+ [onBack, share, headerMenu, headerLeft, headerRight],
117
139
  );
118
140
 
119
141
  return (
@@ -124,19 +146,22 @@ export function AliaChatScreen({
124
146
  >
125
147
  <KeyboardAvoidingView behavior="padding" automaticOffset style={{ flex: 1 }}>
126
148
  <AliaChatContent
149
+ agent={agent}
127
150
  clientContext={clientContext}
151
+ hostTools={hostTools}
152
+ toolRenderers={toolRenderers}
153
+ onFeatureLocked={onFeatureLocked}
128
154
  model={model}
129
155
  apiUrl={apiUrl}
130
156
  welcomeGreeting={welcomeGreeting}
131
157
  welcomeSubtitle={welcomeSubtitle}
132
158
  welcomeSuggestions={welcomeSuggestions}
133
- agents={agents}
134
159
  suggestionMatches={suggestionMatches}
135
160
  onSuggestionQuery={onSuggestionQuery}
136
161
  isDarkMode={isDarkMode}
137
- voiceSession={voiceSession}
162
+ voice={voice}
138
163
  labels={labels}
139
- header={header}
164
+ chrome={chrome}
140
165
  />
141
166
  </KeyboardAvoidingView>
142
167
  </View>
@@ -1,7 +1,15 @@
1
1
  import { AgentAvatarProvider } from '@oxy.so/bloom/agent-avatar';
2
2
  import { AvatarFlightLayer, useAvatarFlights } from '@oxy.so/bloom/avatar-flight';
3
3
  import { BottomSheet, type BottomSheetRef } from '@oxy.so/bloom/bottom-sheet';
4
- import { forwardRef, useCallback, useEffect, useImperativeHandle, useRef, useState } from 'react';
4
+ import {
5
+ forwardRef,
6
+ useCallback,
7
+ useEffect,
8
+ useImperativeHandle,
9
+ useMemo,
10
+ useRef,
11
+ useState,
12
+ } from 'react';
5
13
  import { useTheme } from '@oxy.so/bloom/theme';
6
14
  import { useWindowDimensions, View } from 'react-native';
7
15
  import { useSharedValue } from 'react-native-reanimated';
@@ -9,9 +17,20 @@ import { useSafeAreaInsets } from 'react-native-safe-area-context';
9
17
  import { BREAKPOINTS } from '@oxy.so/bloom/styles';
10
18
  import { ALIA_CHARACTER_RUNTIME_URL } from '../lib/character-runtime';
11
19
  import type { AliaChatLabels } from '../lib/labels';
12
- import type { VoiceSessionComponent } from '../types';
13
- import { AliaChatContent, type AliaChatContentProps, type AliaTurn } from './AliaChatContent';
20
+ import type { AliaToolRenderers } from '../lib/tool-renderers';
21
+ import { useShareHandler } from '../hooks/useShareHandler';
22
+ import type { AliaRuntimeHost } from '../runtime/config';
23
+ import type { AliaHostTools } from '../runtime/host-tools';
24
+ import type { AliaConversationShare } from '../runtime/sharing';
25
+ import { useThoughtStore } from '../runtime/thought-store';
26
+ import type { AliaTurn } from '../hooks/useChatComposer';
27
+ import {
28
+ type AliaChatAgentRef,
29
+ AliaChatContent,
30
+ type AliaChatContentProps,
31
+ } from './AliaChatContent';
14
32
  import { AliaChatHeader } from './AliaChatHeader';
33
+ import { ThoughtPanel } from './thought/ThoughtPanel';
15
34
  import type { AliaSuggestion } from '../types';
16
35
 
17
36
  /**
@@ -23,13 +42,24 @@ import type { AliaSuggestion } from '../types';
23
42
  */
24
43
  const SHEET_RADIUS = 40;
25
44
 
45
+ /** How much of the screen's height the sheet of a turn's work takes. */
46
+ const THOUGHT_SHEET_HEIGHT = 0.7;
47
+
26
48
  /** The sheet floats (detached) from tablet width up; on a phone it is flush with the screen edges. */
27
49
  const DETACHED_FROM = BREAKPOINTS.md;
28
50
 
29
51
  export interface AliaChatSheetProps {
52
+ /** The agent the sheet opens on, any agent the person can reach; omitted, Alia. */
53
+ agent?: AliaChatAgentRef;
30
54
  /** App context injected into system prompt */
31
55
  clientContext?: string;
32
- /** Kaana routing profile. Checked against `GET /catalogue`; omitted, the build's preference is used. */
56
+ /** The UI effects this app performs for the lead (see `AliaChatContentProps.hostTools`). */
57
+ hostTools?: AliaHostTools;
58
+ /** The app's own drawing of catalogue tools' results, keyed `<app>:<tool>`. */
59
+ toolRenderers?: AliaToolRenderers;
60
+ /** The lead's feature gate refused something: show the app's upsell for `key`. */
61
+ onFeatureLocked?: (key: string) => void;
62
+ /** The power level (`auto`, `instant`, `medium`, `high`, `xhigh`, `pro`, `ultra`) or an exact `publisher/model` every turn is sent with; omitted, the server picks. */
33
63
  model?: string;
34
64
  /** API URL override */
35
65
  apiUrl?: string;
@@ -39,26 +69,23 @@ export interface AliaChatSheetProps {
39
69
  welcomeSubtitle?: string;
40
70
  /** Welcome screen suggestions */
41
71
  welcomeSuggestions?: AliaSuggestion[];
42
- /** The agents the host knows, with their look: a delegation's thinking avatar is drawn from them until the agent answers. */
43
- agents?: AliaChatContentProps['agents'];
44
72
  /** What the host found for the typed text, shown over the composer (see `onSuggestionQuery`). */
45
73
  suggestionMatches?: AliaSuggestion[];
46
74
  /** The typed text, once typing has settled, for the host to search suggestions with. */
47
75
  onSuggestionQuery?: (query: string) => void;
48
76
  /** Dark-mode flag — forwarded to the ambient wave overlay (defaults to the sheet's own color scheme). */
49
77
  isDarkMode?: boolean;
50
- /**
51
- * Voice capability — pass `VoiceSession` from `@alia.onl/sdk/voice` to offer
52
- * voice calls. Omitted, the sheet is text-only and `react-native-audio-api` never
53
- * enters the bundle.
54
- */
55
- voiceSession?: VoiceSessionComponent;
78
+ /** The chat's voice call: on by default, `false` for text only, an object for the host's callbacks. */
79
+ voice?: AliaChatContentProps['voice'];
56
80
  /** Overrides for the chat's own copy (header, composer, plan, research…); omitted, English. */
57
81
  labels?: Partial<AliaChatLabels>;
58
82
  /** Where the 3D avatar's runtime is served from; omitted, Alia's shared copy (`ALIA_CHARACTER_RUNTIME_URL`). */
59
83
  characterRuntimeUrl?: string;
60
- /** Draws the header's share button once there is a conversation; omitted, no share button. */
61
- onShare?: () => void;
84
+ /**
85
+ * Draws the header's share button once there is a conversation; omitted, no share button.
86
+ * Pressed, the conversation's read-only link is minted and handed here for the app to show.
87
+ */
88
+ onShare?: (share: AliaConversationShare) => void;
62
89
  /** More rows for the header's menu (`DropdownMenuItem`s from `@oxy.so/bloom/dropdown-menu`). */
63
90
  headerMenu?: React.ReactNode;
64
91
  }
@@ -76,17 +103,20 @@ export interface AliaChatSheetRef {
76
103
  export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
77
104
  (
78
105
  {
106
+ agent,
79
107
  clientContext,
108
+ hostTools,
109
+ toolRenderers,
110
+ onFeatureLocked,
80
111
  model,
81
112
  apiUrl,
82
113
  welcomeGreeting,
83
114
  welcomeSubtitle,
84
115
  welcomeSuggestions,
85
- agents,
86
116
  suggestionMatches,
87
117
  onSuggestionQuery,
88
118
  isDarkMode,
89
- voiceSession,
119
+ voice,
90
120
  labels,
91
121
  characterRuntimeUrl = ALIA_CHARACTER_RUNTIME_URL,
92
122
  onShare,
@@ -133,12 +163,32 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
133
163
  return true;
134
164
  }, []);
135
165
 
166
+ // What a turn did — its steps, its sources, its reasoning — opens in a sheet over the
167
+ // conversation when the reader asks for it. A turn that begins using tools does not open one by
168
+ // itself: on a phone that would cover the answer being written.
169
+ const thoughtRef = useRef<BottomSheetRef>(null);
170
+ const closeThought = useCallback(() => {
171
+ useThoughtStore.getState().close();
172
+ thoughtRef.current?.dismiss();
173
+ }, []);
174
+ const runtime = useMemo<AliaRuntimeHost>(
175
+ () => ({
176
+ showThought: (opening) => {
177
+ if (opening === 'automatic') return false;
178
+ thoughtRef.current?.present();
179
+ return true;
180
+ },
181
+ }),
182
+ [],
183
+ );
184
+
136
185
  const handleDismiss = useCallback(() => {
137
186
  conversationStopRef.current?.();
138
187
  mainStopRef.current?.();
139
188
  conversationRef.current?.dismiss();
140
189
  mainRef.current?.dismiss();
141
- }, []);
190
+ closeThought();
191
+ }, [closeThought]);
142
192
 
143
193
  useImperativeHandle(
144
194
  ref,
@@ -157,10 +207,11 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
157
207
  mainStopRef.current?.();
158
208
  conversationStopRef.current?.();
159
209
  conversationRef.current?.dismiss();
210
+ closeThought();
160
211
  return true;
161
- }, []);
212
+ }, [closeThought]);
162
213
 
163
- const { width } = useWindowDimensions();
214
+ const { width, height } = useWindowDimensions();
164
215
  const detached = width >= DETACHED_FROM;
165
216
  // Flush with the screen edge the sheet runs behind the home indicator: the composer clears it.
166
217
  const { bottom } = useSafeAreaInsets();
@@ -170,13 +221,40 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
170
221
 
171
222
  // The avatars fly between the two sheets: one controller, one layer.
172
223
  const flights = useAvatarFlights();
224
+ // The conversation's bar: back to the main screen, the participants, share and the menu.
225
+ const share = useShareHandler(onShare);
226
+ const conversationChrome = useCallback<NonNullable<AliaChatContentProps['chrome']>>(
227
+ ({ participants, conversationId }) => ({
228
+ header: (
229
+ <View className="pt-8">
230
+ <AliaChatHeader
231
+ participants={participants}
232
+ hasMessages
233
+ onClear={closeConversation}
234
+ onBack={closeConversation}
235
+ onShare={
236
+ share && conversationId !== undefined ? () => share(conversationId) : undefined
237
+ }
238
+ menu={headerMenu}
239
+ />
240
+ </View>
241
+ ),
242
+ }),
243
+ [closeConversation, share, headerMenu],
244
+ );
173
245
  const shared = {
174
246
  flights,
247
+ layout: 'sheet' as const,
248
+ agent,
175
249
  clientContext,
250
+ hostTools,
251
+ toolRenderers,
252
+ onFeatureLocked,
176
253
  model,
177
254
  apiUrl,
178
255
  isDarkMode: isDarkMode ?? isDark,
179
- voiceSession,
256
+ voice,
257
+ runtime,
180
258
  bottomInset: detached ? 0 : bottom,
181
259
  labels,
182
260
  };
@@ -202,7 +280,6 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
202
280
  welcomeGreeting={welcomeGreeting}
203
281
  welcomeSubtitle={welcomeSubtitle}
204
282
  welcomeSuggestions={welcomeSuggestions}
205
- agents={agents}
206
283
  suggestionMatches={suggestionMatches}
207
284
  onSuggestionQuery={onSuggestionQuery}
208
285
  presentedProgress={mainProgress}
@@ -227,25 +304,22 @@ export const AliaChatSheet = forwardRef<AliaChatSheetRef, AliaChatSheetProps>(
227
304
  {...shared}
228
305
  initialTurn={conversation.turn}
229
306
  presentedProgress={conversationProgress}
230
- agents={agents}
231
307
  onStopReady={registerConversationStop}
232
- // The bar is the conversation's: back to the main screen, the profile, share and
233
- // the menu.
234
- header={({ agents }) => (
235
- <View className="pt-8">
236
- <AliaChatHeader
237
- hasMessages
238
- agents={agents}
239
- onClear={closeConversation}
240
- onBack={closeConversation}
241
- onShare={onShare}
242
- menu={headerMenu}
243
- />
244
- </View>
245
- )}
308
+ chrome={conversationChrome}
246
309
  />
247
310
  </BottomSheet>
248
311
  ) : null}
312
+ <BottomSheet
313
+ ref={thoughtRef}
314
+ detached={detached}
315
+ scrollable={false}
316
+ onDismiss={() => useThoughtStore.getState().close()}
317
+ style={sheetStyle}
318
+ >
319
+ <View style={{ height: height * THOUGHT_SHEET_HEIGHT }}>
320
+ <ThoughtPanel onClose={closeThought} />
321
+ </View>
322
+ </BottomSheet>
249
323
  <AvatarFlightLayer flights={flights} />
250
324
  </AgentAvatarProvider>
251
325
  );
@@ -321,7 +321,7 @@ function AliaComposerBody<T extends AliaSuggestion>({
321
321
  busy={busy}
322
322
  onStop={onStop}
323
323
  disabled={disabled || intake.isBusy || !hasContent}
324
- placeholder={placeholder ?? labels.placeholder}
324
+ placeholder={placeholder ?? labels.placeholder('Alia')}
325
325
  addMenu={addMenu ?? EMPTY_ADD_MENU}
326
326
  onAddMenuSelect={onAddMenuSelect}
327
327
  permissions={modes ?? NO_MODES}