@opengeni/react-native 1.4.5-canary.37615713321001

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 (132) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +74 -0
  3. package/dist/adapters.d.ts +53 -0
  4. package/dist/adapters.js +3 -0
  5. package/dist/adapters.js.map +1 -0
  6. package/dist/attachments.d.ts +75 -0
  7. package/dist/chunk-3VSZAQ7G.js +391 -0
  8. package/dist/chunk-3VSZAQ7G.js.map +1 -0
  9. package/dist/chunk-5ZVK7WKA.js +88 -0
  10. package/dist/chunk-5ZVK7WKA.js.map +1 -0
  11. package/dist/chunk-EEDIMY3L.js +240 -0
  12. package/dist/chunk-EEDIMY3L.js.map +1 -0
  13. package/dist/chunk-M23SDI4R.js +83 -0
  14. package/dist/chunk-M23SDI4R.js.map +1 -0
  15. package/dist/chunk-PHJBSAR3.js +2006 -0
  16. package/dist/chunk-PHJBSAR3.js.map +1 -0
  17. package/dist/environment.d.ts +19 -0
  18. package/dist/expo.d.ts +25 -0
  19. package/dist/expo.js +267 -0
  20. package/dist/expo.js.map +1 -0
  21. package/dist/index.d.ts +13 -0
  22. package/dist/index.js +3345 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/presentation.d.ts +262 -0
  25. package/dist/read-acknowledgement.d.ts +39 -0
  26. package/dist/realtime/call-events.d.ts +11 -0
  27. package/dist/realtime/call-view.d.ts +26 -0
  28. package/dist/realtime/expo-call.d.ts +11 -0
  29. package/dist/realtime/native-call.d.ts +104 -0
  30. package/dist/realtime/native-realtime.d.ts +35 -0
  31. package/dist/realtime/use-native-session-realtime.d.ts +25 -0
  32. package/dist/session-view.d.ts +87 -0
  33. package/dist/timeline/activity.d.ts +34 -0
  34. package/dist/timeline/composer.d.ts +86 -0
  35. package/dist/timeline/controls.d.ts +27 -0
  36. package/dist/timeline/decisions.d.ts +29 -0
  37. package/dist/timeline/feedback.d.ts +26 -0
  38. package/dist/timeline/icon-nodes.generated.d.ts +738 -0
  39. package/dist/timeline/icon.d.ts +11 -0
  40. package/dist/timeline/index.d.ts +32 -0
  41. package/dist/timeline/index.js +5419 -0
  42. package/dist/timeline/index.js.map +1 -0
  43. package/dist/timeline/markdown.d.ts +11 -0
  44. package/dist/timeline/markdown.js +415 -0
  45. package/dist/timeline/markdown.js.map +1 -0
  46. package/dist/timeline/message-attachments.d.ts +13 -0
  47. package/dist/timeline/message-timeline.d.ts +36 -0
  48. package/dist/timeline/model-picker.d.ts +17 -0
  49. package/dist/timeline/primitives.d.ts +85 -0
  50. package/dist/timeline/queue-dock.d.ts +19 -0
  51. package/dist/timeline/session-actions.d.ts +34 -0
  52. package/dist/timeline/session-list.d.ts +50 -0
  53. package/dist/timeline/session-screen.d.ts +35 -0
  54. package/dist/timeline/session-signals.d.ts +37 -0
  55. package/dist/timeline/sheet.d.ts +14 -0
  56. package/dist/timeline/theme.d.ts +58 -0
  57. package/dist/timeline/thinking-orb.d.ts +13 -0
  58. package/dist/timeline/turn.d.ts +80 -0
  59. package/dist/ui/attention.d.ts +14 -0
  60. package/dist/ui/cards.d.ts +21 -0
  61. package/dist/ui/composer.d.ts +21 -0
  62. package/dist/ui/format.d.ts +13 -0
  63. package/dist/ui/icons.d.ts +9 -0
  64. package/dist/ui/index.d.ts +12 -0
  65. package/dist/ui/index.js +1757 -0
  66. package/dist/ui/index.js.map +1 -0
  67. package/dist/ui/steps-sheet.d.ts +10 -0
  68. package/dist/ui/theme.d.ts +63 -0
  69. package/dist/ui/timeline.d.ts +48 -0
  70. package/dist/ui/web-tokens.gen.d.ts +90 -0
  71. package/dist/use-native-session.d.ts +40 -0
  72. package/dist/view-model.d.ts +52 -0
  73. package/dist/voice-input.d.ts +102 -0
  74. package/dist/webrtc.d.ts +9 -0
  75. package/dist/webrtc.js +19 -0
  76. package/dist/webrtc.js.map +1 -0
  77. package/expo-module.config.json +7 -0
  78. package/ios/OpenGeniCall.podspec +22 -0
  79. package/ios/OpenGeniCallAppDelegateSubscriber.swift +29 -0
  80. package/ios/OpenGeniCallCenter.swift +189 -0
  81. package/ios/OpenGeniCallModule.swift +64 -0
  82. package/package.json +93 -0
  83. package/src/adapters.ts +150 -0
  84. package/src/attachments.ts +396 -0
  85. package/src/environment.tsx +214 -0
  86. package/src/expo.ts +295 -0
  87. package/src/index.ts +98 -0
  88. package/src/presentation.ts +285 -0
  89. package/src/read-acknowledgement.ts +72 -0
  90. package/src/realtime/call-events.ts +41 -0
  91. package/src/realtime/call-view.tsx +220 -0
  92. package/src/realtime/expo-call.ts +49 -0
  93. package/src/realtime/native-call.ts +266 -0
  94. package/src/realtime/native-realtime.ts +115 -0
  95. package/src/realtime/use-native-session-realtime.ts +29 -0
  96. package/src/session-view.tsx +2251 -0
  97. package/src/timeline/activity.tsx +736 -0
  98. package/src/timeline/composer.tsx +686 -0
  99. package/src/timeline/controls.tsx +151 -0
  100. package/src/timeline/decisions.tsx +651 -0
  101. package/src/timeline/feedback.tsx +251 -0
  102. package/src/timeline/icon-nodes.generated.ts +1830 -0
  103. package/src/timeline/icon.tsx +44 -0
  104. package/src/timeline/index.ts +67 -0
  105. package/src/timeline/markdown.tsx +510 -0
  106. package/src/timeline/message-attachments.tsx +155 -0
  107. package/src/timeline/message-timeline.tsx +1042 -0
  108. package/src/timeline/model-picker.tsx +450 -0
  109. package/src/timeline/primitives.tsx +561 -0
  110. package/src/timeline/queue-dock.tsx +331 -0
  111. package/src/timeline/session-actions.tsx +379 -0
  112. package/src/timeline/session-list.tsx +338 -0
  113. package/src/timeline/session-screen.tsx +488 -0
  114. package/src/timeline/session-signals.tsx +312 -0
  115. package/src/timeline/sheet.tsx +153 -0
  116. package/src/timeline/theme.tsx +128 -0
  117. package/src/timeline/thinking-orb.tsx +93 -0
  118. package/src/timeline/turn.tsx +569 -0
  119. package/src/ui/attention.tsx +220 -0
  120. package/src/ui/cards.tsx +267 -0
  121. package/src/ui/composer.tsx +296 -0
  122. package/src/ui/format.ts +86 -0
  123. package/src/ui/icons.tsx +131 -0
  124. package/src/ui/index.ts +18 -0
  125. package/src/ui/steps-sheet.tsx +71 -0
  126. package/src/ui/theme.ts +261 -0
  127. package/src/ui/timeline.tsx +837 -0
  128. package/src/ui/web-tokens.gen.ts +94 -0
  129. package/src/use-native-session.ts +219 -0
  130. package/src/view-model.ts +485 -0
  131. package/src/voice-input.ts +382 -0
  132. package/src/webrtc.ts +30 -0
@@ -0,0 +1,296 @@
1
+ import { GlassView, isLiquidGlassAvailable } from "expo-glass-effect";
2
+ import type { ReactNode } from "react";
3
+ import { Platform, Pressable, StyleSheet, Text, TextInput, View } from "react-native";
4
+ import { AgentIcon } from "./icons";
5
+ import type { AgentTheme } from "./theme";
6
+
7
+ export interface AgentComposerProps {
8
+ theme: AgentTheme;
9
+ value: string;
10
+ onChangeText(text: string): void;
11
+ onSend(): void;
12
+ onStop?: (() => void) | undefined;
13
+ onAttach?: (() => void) | undefined;
14
+ onVoice?: (() => void) | undefined;
15
+ running: boolean;
16
+ placeholder: string;
17
+ /** Chips shown in the bar composer (model, permissions…). */
18
+ accessories?: ReactNode | undefined;
19
+ bottomInset: number;
20
+ }
21
+
22
+ function RoundButton(props: {
23
+ label: string;
24
+ onPress?: (() => void) | undefined;
25
+ size: number;
26
+ background: string;
27
+ children: ReactNode;
28
+ disabled?: boolean | undefined;
29
+ }) {
30
+ return (
31
+ <Pressable
32
+ accessibilityLabel={props.label}
33
+ accessibilityRole="button"
34
+ disabled={props.disabled}
35
+ hitSlop={6}
36
+ onPress={props.onPress}
37
+ style={({ pressed }) => ({
38
+ width: props.size,
39
+ height: props.size,
40
+ borderRadius: props.size / 2,
41
+ alignItems: "center",
42
+ justifyContent: "center",
43
+ backgroundColor: props.background,
44
+ opacity: props.disabled ? 0.35 : pressed ? 0.7 : 1,
45
+ })}
46
+ >
47
+ {props.children}
48
+ </Pressable>
49
+ );
50
+ }
51
+
52
+ export function AgentComposer(props: AgentComposerProps) {
53
+ const { theme } = props;
54
+ if (theme.layout.composer === "bar") return <BarComposer {...props} />;
55
+ if (theme.layout.composer === "voice") return <VoiceComposer {...props} />;
56
+ return <PillComposer {...props} />;
57
+ }
58
+
59
+ function SendOrStop(props: AgentComposerProps & { size: number }) {
60
+ const { theme, running, value } = props;
61
+ if (running && !value.trim()) {
62
+ return (
63
+ <RoundButton
64
+ background={theme.colors.accent}
65
+ label="Stop"
66
+ onPress={props.onStop}
67
+ size={props.size}
68
+ >
69
+ <AgentIcon color={theme.colors.onAccent} name="stop" size={props.size * 0.4} />
70
+ </RoundButton>
71
+ );
72
+ }
73
+ return (
74
+ <RoundButton
75
+ background={theme.colors.accent}
76
+ disabled={!value.trim()}
77
+ label="Send"
78
+ onPress={props.onSend}
79
+ size={props.size}
80
+ >
81
+ <AgentIcon
82
+ color={theme.colors.onAccent}
83
+ name="arrow-up"
84
+ size={props.size * 0.5}
85
+ strokeWidth={2.4}
86
+ />
87
+ </RoundButton>
88
+ );
89
+ }
90
+
91
+ function Input(props: AgentComposerProps & { fontSize: number }) {
92
+ return (
93
+ <TextInput
94
+ accessibilityLabel={props.placeholder}
95
+ multiline
96
+ onChangeText={props.onChangeText}
97
+ placeholder={props.placeholder}
98
+ placeholderTextColor={props.theme.colors.textFaint}
99
+ style={{
100
+ flex: 1,
101
+ color: props.theme.colors.text,
102
+ fontSize: props.fontSize,
103
+ minHeight: 40,
104
+ maxHeight: 160,
105
+ paddingTop: Platform.OS === "ios" ? 10 : 8,
106
+ paddingBottom: Platform.OS === "ios" ? 10 : 8,
107
+ }}
108
+ value={props.value}
109
+ />
110
+ );
111
+ }
112
+
113
+ /** Calm: floating pill; Liquid Glass on iOS 26. */
114
+ function PillComposer(props: AgentComposerProps) {
115
+ const { theme } = props;
116
+ const glass = Platform.OS === "ios" && isLiquidGlassAvailable();
117
+ const inner = (
118
+ <View
119
+ style={{
120
+ flexDirection: "row",
121
+ alignItems: "flex-end",
122
+ gap: 6,
123
+ paddingHorizontal: 6,
124
+ paddingVertical: 6,
125
+ }}
126
+ >
127
+ <RoundButton
128
+ background="transparent"
129
+ label="Add attachment"
130
+ onPress={props.onAttach}
131
+ size={36}
132
+ >
133
+ <AgentIcon color={theme.colors.text} name="plus" size={20} />
134
+ </RoundButton>
135
+ <Input {...props} fontSize={theme.type.body} />
136
+ {props.value.trim() || props.running ? (
137
+ <SendOrStop {...props} size={36} />
138
+ ) : (
139
+ <RoundButton background="transparent" label="Dictate" onPress={props.onVoice} size={36}>
140
+ <AgentIcon color={theme.colors.text} name="mic" size={19} />
141
+ </RoundButton>
142
+ )}
143
+ </View>
144
+ );
145
+ const shell = {
146
+ borderRadius: theme.radius.composer,
147
+ overflow: "hidden" as const,
148
+ borderWidth: glass ? 0 : StyleSheet.hairlineWidth * 2,
149
+ borderColor: theme.colors.border,
150
+ backgroundColor: glass ? undefined : theme.colors.surfaceRaised,
151
+ shadowColor: "#000",
152
+ shadowOpacity: theme.scheme === "light" ? 0.08 : 0,
153
+ shadowRadius: 16,
154
+ shadowOffset: { width: 0, height: 6 },
155
+ };
156
+ return (
157
+ <View
158
+ style={{
159
+ paddingHorizontal: 12,
160
+ paddingTop: 6,
161
+ paddingBottom: Math.max(props.bottomInset, 10),
162
+ }}
163
+ >
164
+ {glass ? (
165
+ <GlassView glassEffectStyle="regular" isInteractive style={shell}>
166
+ {inner}
167
+ </GlassView>
168
+ ) : (
169
+ <View style={shell}>{inner}</View>
170
+ )}
171
+ </View>
172
+ );
173
+ }
174
+
175
+ /** Workbench: docked bar with a toolbar row for context chips. */
176
+ function BarComposer(props: AgentComposerProps) {
177
+ const { theme } = props;
178
+ return (
179
+ <View
180
+ style={{
181
+ borderTopWidth: StyleSheet.hairlineWidth,
182
+ borderColor: theme.colors.border,
183
+ backgroundColor: theme.colors.surfaceRaised,
184
+ paddingHorizontal: 12,
185
+ paddingTop: 8,
186
+ paddingBottom: Math.max(props.bottomInset, 8),
187
+ gap: 6,
188
+ }}
189
+ >
190
+ <View
191
+ style={{
192
+ borderRadius: theme.radius.composer,
193
+ borderWidth: StyleSheet.hairlineWidth * 2,
194
+ borderColor: theme.colors.border,
195
+ backgroundColor: theme.colors.background,
196
+ paddingHorizontal: 12,
197
+ }}
198
+ >
199
+ <Input {...props} fontSize={theme.type.body} />
200
+ </View>
201
+ <View style={{ flexDirection: "row", alignItems: "center", gap: 8 }}>
202
+ <RoundButton
203
+ background={theme.colors.surface}
204
+ label="Add attachment"
205
+ onPress={props.onAttach}
206
+ size={34}
207
+ >
208
+ <AgentIcon color={theme.colors.text} name="plus" size={18} />
209
+ </RoundButton>
210
+ <RoundButton
211
+ background={theme.colors.surface}
212
+ label="Dictate"
213
+ onPress={props.onVoice}
214
+ size={34}
215
+ >
216
+ <AgentIcon color={theme.colors.text} name="mic" size={17} />
217
+ </RoundButton>
218
+ <View style={{ flex: 1, flexDirection: "row", gap: 6 }}>{props.accessories}</View>
219
+ <SendOrStop {...props} size={34} />
220
+ </View>
221
+ </View>
222
+ );
223
+ }
224
+
225
+ /** Field: large field with a dominant voice button. */
226
+ function VoiceComposer(props: AgentComposerProps) {
227
+ const { theme } = props;
228
+ const hasText = props.value.trim().length > 0;
229
+ return (
230
+ <View
231
+ style={{
232
+ flexDirection: "row",
233
+ alignItems: "flex-end",
234
+ gap: 10,
235
+ paddingHorizontal: 14,
236
+ paddingTop: 8,
237
+ paddingBottom: Math.max(props.bottomInset, 12),
238
+ }}
239
+ >
240
+ <View
241
+ style={{
242
+ flex: 1,
243
+ flexDirection: "row",
244
+ alignItems: "flex-end",
245
+ minHeight: 56,
246
+ borderRadius: theme.radius.composer,
247
+ backgroundColor: theme.colors.surface,
248
+ borderWidth: StyleSheet.hairlineWidth * 2,
249
+ borderColor: theme.colors.border,
250
+ paddingLeft: 6,
251
+ paddingRight: 14,
252
+ }}
253
+ >
254
+ <RoundButton
255
+ background="transparent"
256
+ label="Add photo or file"
257
+ onPress={props.onAttach}
258
+ size={44}
259
+ >
260
+ <AgentIcon color={theme.colors.text} name="plus" size={24} />
261
+ </RoundButton>
262
+ <Input {...props} fontSize={theme.type.body} />
263
+ </View>
264
+ {hasText || props.running ? (
265
+ <SendOrStop {...props} size={56} />
266
+ ) : (
267
+ <RoundButton
268
+ background={theme.colors.accent}
269
+ label="Talk"
270
+ onPress={props.onVoice}
271
+ size={56}
272
+ >
273
+ <AgentIcon color={theme.colors.onAccent} name="mic" size={26} />
274
+ </RoundButton>
275
+ )}
276
+ </View>
277
+ );
278
+ }
279
+
280
+ export function ComposerChip({ theme, label }: { theme: AgentTheme; label: string }) {
281
+ return (
282
+ <View
283
+ style={{
284
+ height: 28,
285
+ paddingHorizontal: 10,
286
+ borderRadius: 14,
287
+ backgroundColor: theme.colors.surface,
288
+ justifyContent: "center",
289
+ }}
290
+ >
291
+ <Text style={{ color: theme.colors.textMuted, fontSize: 12, fontWeight: "500" }}>
292
+ {label}
293
+ </Text>
294
+ </View>
295
+ );
296
+ }
@@ -0,0 +1,86 @@
1
+ import type { ActivityItem, TimelineGroup, ToolCallItem } from "@opengeni/react/session";
2
+ import { toolDisplayName } from "@opengeni/react/session";
3
+
4
+ export function toolTitle(item: ToolCallItem): string {
5
+ return toolDisplayName(item.name, item.display);
6
+ }
7
+
8
+ function firstString(record: Record<string, unknown>, keys: string[]): string | null {
9
+ for (const key of keys) {
10
+ const value = record[key];
11
+ if (typeof value === "string" && value.trim()) return value.trim();
12
+ }
13
+ return null;
14
+ }
15
+
16
+ /** One-line human summary of a tool call's input. */
17
+ export function toolDetail(item: ToolCallItem): string | null {
18
+ const args = item.arguments;
19
+ if (typeof args === "string") return args.split("\n")[0] ?? null;
20
+ if (!args || typeof args !== "object") return null;
21
+ const record = args as Record<string, unknown>;
22
+ const known = firstString(record, [
23
+ "cmd",
24
+ "command",
25
+ "query",
26
+ "q",
27
+ "path",
28
+ "url",
29
+ "title",
30
+ "id",
31
+ "name",
32
+ ]);
33
+ if (known) return known;
34
+ const pairs = Object.entries(record)
35
+ .filter(([, value]) => value !== null && value !== undefined && value !== "")
36
+ .slice(0, 3)
37
+ .map(
38
+ ([key, value]) =>
39
+ `${key.replace(/[_-]+/g, " ")}: ${typeof value === "string" ? value : JSON.stringify(value)}`,
40
+ );
41
+ if (pairs.length === 0) return null;
42
+ const text = pairs.join(" · ");
43
+ return text.length > 110 ? `${text.slice(0, 109)}…` : text;
44
+ }
45
+
46
+ /** Short preview of a settled tool output. */
47
+ export function toolOutputPreview(item: ToolCallItem, lines = 2): string | null {
48
+ const output = item.output;
49
+ const text =
50
+ typeof output === "string"
51
+ ? output
52
+ : output &&
53
+ typeof output === "object" &&
54
+ typeof (output as { text?: unknown }).text === "string"
55
+ ? String((output as { text: string }).text)
56
+ : output == null
57
+ ? null
58
+ : JSON.stringify(output);
59
+ if (!text) return null;
60
+ return text.split("\n").slice(0, lines).join("\n");
61
+ }
62
+
63
+ export function formatDuration(ms: number): string {
64
+ if (!Number.isFinite(ms) || ms < 1000) return "";
65
+ const seconds = Math.round(ms / 1000);
66
+ if (seconds < 60) return `${seconds}s`;
67
+ const minutes = Math.floor(seconds / 60);
68
+ const rest = seconds % 60;
69
+ return rest ? `${minutes}m ${rest}s` : `${minutes}m`;
70
+ }
71
+
72
+ export function itemTime(item: { occurredAt?: string }): number {
73
+ return item.occurredAt ? Date.parse(item.occurredAt) : Number.NaN;
74
+ }
75
+
76
+ /** Flatten nested work details of a group into its activity items, in order. */
77
+ export function activityItemsOf(group: TimelineGroup): ActivityItem[] {
78
+ if (group.kind === "item") return [];
79
+ if (group.kind === "turn") return group.groups.flatMap(activityItemsOf);
80
+ const nested = group.work?.details.flatMap(activityItemsOf) ?? [];
81
+ return nested.length > 0 ? nested : group.items;
82
+ }
83
+
84
+ export function toolCallsOf(items: readonly ActivityItem[]): ToolCallItem[] {
85
+ return items.filter((item): item is ToolCallItem => item.kind === "tool-call");
86
+ }
@@ -0,0 +1,131 @@
1
+ // Minimal stroke icon set (Lucide geometry, ISC) so the kit has no icon-font dependency.
2
+ import Svg, { Circle, Path, Polyline, Rect } from "react-native-svg";
3
+
4
+ export type AgentIconName =
5
+ | "terminal"
6
+ | "search"
7
+ | "edit"
8
+ | "globe"
9
+ | "check"
10
+ | "x"
11
+ | "chevron-right"
12
+ | "chevron-down"
13
+ | "sparkle"
14
+ | "mic"
15
+ | "plus"
16
+ | "arrow-up"
17
+ | "stop"
18
+ | "tool"
19
+ | "send"
20
+ | "alert"
21
+ | "question"
22
+ | "rocket";
23
+
24
+ export function AgentIcon({
25
+ name,
26
+ size = 18,
27
+ color,
28
+ strokeWidth = 2,
29
+ }: {
30
+ name: AgentIconName;
31
+ size?: number;
32
+ color: string;
33
+ strokeWidth?: number;
34
+ }) {
35
+ const common = {
36
+ stroke: color,
37
+ strokeWidth,
38
+ strokeLinecap: "round" as const,
39
+ strokeLinejoin: "round" as const,
40
+ fill: "none",
41
+ };
42
+ return (
43
+ <Svg width={size} height={size} viewBox="0 0 24 24">
44
+ {name === "terminal" ? (
45
+ <>
46
+ <Polyline points="4 17 10 11 4 5" {...common} />
47
+ <Path d="M 12 19h8" {...common} />
48
+ </>
49
+ ) : null}
50
+ {name === "search" ? (
51
+ <>
52
+ <Circle cx="11" cy="11" r="7" {...common} />
53
+ <Path d="m20 20-3.5-3.5" {...common} />
54
+ </>
55
+ ) : null}
56
+ {name === "edit" ? (
57
+ <>
58
+ <Path d="M 12 20h9" {...common} />
59
+ <Path d="M16.5 3.5a2.1 2.1 0 0 1 3 3L7 19l-4 1 1-4Z" {...common} />
60
+ </>
61
+ ) : null}
62
+ {name === "globe" ? (
63
+ <>
64
+ <Circle cx="12" cy="12" r="9" {...common} />
65
+ <Path d="M3 12h18M12 3a14 14 0 0 1 0 18M12 3a14 14 0 0 0 0 18" {...common} />
66
+ </>
67
+ ) : null}
68
+ {name === "check" ? <Polyline points="20 6 9 17 4 12" {...common} /> : null}
69
+ {name === "x" ? <Path d="M18 6 6 18M6 6l12 12" {...common} /> : null}
70
+ {name === "chevron-right" ? <Polyline points="9 18 15 12 9 6" {...common} /> : null}
71
+ {name === "chevron-down" ? <Polyline points="6 9 12 15 18 9" {...common} /> : null}
72
+ {name === "sparkle" ? (
73
+ <Path d="M 12 3l1.9 5.1L19 10l-5.1 1.9L12 17l-1.9-5.1L5 10l5.1-1.9Z" {...common} />
74
+ ) : null}
75
+ {name === "mic" ? (
76
+ <>
77
+ <Rect x="9" y="2" width="6" height="12" rx="3" {...common} />
78
+ <Path d="M5 10a7 7 0 0 0 14 0M12 17v5" {...common} />
79
+ </>
80
+ ) : null}
81
+ {name === "plus" ? <Path d="M 12 5v14M5 12h14" {...common} /> : null}
82
+ {name === "arrow-up" ? <Path d="M 12 19V5M5 12l7-7 7 7" {...common} /> : null}
83
+ {name === "stop" ? <Rect x="6" y="6" width="12" height="12" rx="2" fill={color} /> : null}
84
+ {name === "tool" ? (
85
+ <Path
86
+ d="M14.7 6.3a4 4 0 0 0-5.4 5.4L3 18l3 3 6.3-6.3a4 4 0 0 0 5.4-5.4l-2.6 2.6-2.4-.6-.6-2.4Z"
87
+ {...common}
88
+ />
89
+ ) : null}
90
+ {name === "send" ? <Path d="m22 2-7 20-4-9-9-4Z M22 2 11 13" {...common} /> : null}
91
+ {name === "alert" ? (
92
+ <>
93
+ <Path
94
+ d="M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z"
95
+ {...common}
96
+ />
97
+ <Path d="M 12 9v4M12 17h.01" {...common} />
98
+ </>
99
+ ) : null}
100
+ {name === "question" ? (
101
+ <>
102
+ <Circle cx="12" cy="12" r="9" {...common} />
103
+ <Path d="M9.1 9a3 3 0 0 1 5.8 1c0 2-3 3-3 3M12 17h.01" {...common} />
104
+ </>
105
+ ) : null}
106
+ {name === "rocket" ? (
107
+ <>
108
+ <Path
109
+ d="M4.5 16.5c-1.5 1.3-2 5-2 5s3.7-.5 5-2c.7-.8.7-2.1-.1-2.9a2.2 2.2 0 0 0-2.9-.1Z"
110
+ {...common}
111
+ />
112
+ <Path
113
+ d="m 12 15-3-3a22 22 0 0 1 2-4A13 13 0 0 1 22 2c0 2.7-.8 7.5-6 11a22 22 0 0 1-4 2Z"
114
+ {...common}
115
+ />
116
+ </>
117
+ ) : null}
118
+ </Svg>
119
+ );
120
+ }
121
+
122
+ /** Category icon for a tool name (best effort, purely presentational). */
123
+ export function toolIconName(name: string): AgentIconName {
124
+ const leaf = name.toLowerCase();
125
+ if (/exec|command|shell|bash|terminal|run/.test(leaf)) return "terminal";
126
+ if (/search|find|grep|list|query|read/.test(leaf)) return "search";
127
+ if (/patch|edit|write|create|update|save/.test(leaf)) return "edit";
128
+ if (/web|browse|fetch|http|url/.test(leaf)) return "globe";
129
+ if (/deploy|release|ship/.test(leaf)) return "rocket";
130
+ return "tool";
131
+ }
@@ -0,0 +1,18 @@
1
+ export { AgentStepList, AgentTimeline, UserMessage, WorkingLine } from "./timeline";
2
+ export { AgentStepsSheet } from "./steps-sheet";
3
+ export { AgentAttentionTray } from "./attention";
4
+ export type { AgentTimelineProps, MarkdownRenderer } from "./timeline";
5
+ export { AgentComposer, ComposerChip } from "./composer";
6
+ export type { AgentComposerProps } from "./composer";
7
+ export { ApprovalCard, QuestionCard, ActionButton } from "./cards";
8
+ export { AgentIcon, toolIconName } from "./icons";
9
+ export type { AgentIconName } from "./icons";
10
+ export { agentDirections, agentTheme } from "./theme";
11
+ export type {
12
+ AgentDirection,
13
+ AgentDirectionId,
14
+ AgentTheme,
15
+ ActivityStyle,
16
+ ComposerStyle,
17
+ } from "./theme";
18
+ export { formatDuration, toolDetail, toolOutputPreview, toolTitle } from "./format";
@@ -0,0 +1,71 @@
1
+ import type { ActivityItem } from "@opengeni/react/session";
2
+ import { Modal, Platform, Pressable, ScrollView, Text, View } from "react-native";
3
+ import { useSafeAreaInsets } from "react-native-safe-area-context";
4
+ import { AgentStepList } from "./timeline";
5
+ import type { AgentTheme } from "./theme";
6
+
7
+ /** Native sheet with the full step-by-step view of one piece of agent work. */
8
+ export function AgentStepsSheet(props: {
9
+ visible: boolean;
10
+ onClose(): void;
11
+ title: string;
12
+ steps: readonly ActivityItem[];
13
+ theme: AgentTheme;
14
+ }) {
15
+ const { theme } = props;
16
+ const insets = useSafeAreaInsets();
17
+ return (
18
+ <Modal
19
+ animationType="slide"
20
+ onRequestClose={props.onClose}
21
+ presentationStyle={Platform.OS === "ios" ? "pageSheet" : "fullScreen"}
22
+ visible={props.visible}
23
+ >
24
+ <View
25
+ style={{
26
+ flex: 1,
27
+ backgroundColor: theme.colors.background,
28
+ paddingTop: Platform.OS === "ios" ? 0 : insets.top,
29
+ }}
30
+ >
31
+ <View
32
+ style={{
33
+ height: 56,
34
+ flexDirection: "row",
35
+ alignItems: "center",
36
+ paddingHorizontal: theme.space.gutter,
37
+ }}
38
+ >
39
+ <Text
40
+ numberOfLines={1}
41
+ style={{
42
+ flex: 1,
43
+ color: theme.colors.text,
44
+ fontSize: theme.type.body + 1,
45
+ fontWeight: theme.type.weightStrong,
46
+ }}
47
+ >
48
+ {props.title}
49
+ </Text>
50
+ <Pressable
51
+ accessibilityRole="button"
52
+ hitSlop={12}
53
+ onPress={props.onClose}
54
+ style={{ minHeight: 44, justifyContent: "center" }}
55
+ >
56
+ <Text
57
+ style={{ color: theme.colors.accent, fontSize: theme.type.body, fontWeight: "600" }}
58
+ >
59
+ Done
60
+ </Text>
61
+ </Pressable>
62
+ </View>
63
+ <ScrollView
64
+ contentContainerStyle={{ padding: theme.space.gutter, paddingBottom: insets.bottom + 24 }}
65
+ >
66
+ <AgentStepList items={props.steps} theme={theme} />
67
+ </ScrollView>
68
+ </View>
69
+ </Modal>
70
+ );
71
+ }