@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,736 @@
1
+ import type {
2
+ ActivityItem,
3
+ AgentMessageItem,
4
+ ReasoningItem,
5
+ SandboxItem,
6
+ ToolCallItem,
7
+ WorkerItem,
8
+ } from "@opengeni/react/session";
9
+ import {
10
+ pathBasename,
11
+ pathDirname,
12
+ sandboxRowTitle,
13
+ startupDuration,
14
+ startupPhaseTitle,
15
+ STARTUP_WAIT_TITLES,
16
+ toolDisplayName,
17
+ toolRowPresentation,
18
+ workerRowTitle,
19
+ type ToolBody,
20
+ type ToolIconKind,
21
+ type ToolPreview,
22
+ type ToolRowPresentation,
23
+ type WebSearchResult,
24
+ } from "@opengeni/react/timeline-model";
25
+ import { createContext, useContext, type ReactNode } from "react";
26
+ import { Pressable, Text, View } from "react-native";
27
+ import { Icon, type NativeIconName } from "./icon";
28
+ import {
29
+ ActivityDisclosure,
30
+ BodyNote,
31
+ Chip,
32
+ PayloadBlock,
33
+ RunningPreview,
34
+ ShimmerText,
35
+ TermBlock,
36
+ ROW_MIN_HEIGHT,
37
+ } from "./primitives";
38
+ import { fontStyle, useNativeTimelineTheme } from "./theme";
39
+
40
+ /* ----------------------------------------------------------------------------
41
+ Native activity rows. Tool rows draw from the shared presentation model
42
+ (`toolRowPresentation`), the same source the web renderers use; hosts can
43
+ replace a tool's row via `NativeToolRenderers` (e.g. product-specific tools).
44
+ -------------------------------------------------------------------------- */
45
+
46
+ export type NativeToolRendererProps = {
47
+ item: ToolCallItem;
48
+ presentation: ToolRowPresentation;
49
+ compact: boolean;
50
+ };
51
+
52
+ /** Return null to fall back to the shared row. */
53
+ export type NativeToolRenderer = (props: NativeToolRendererProps) => ReactNode | null;
54
+
55
+ export interface NativeActivityOptions {
56
+ /** Per-tool overrides keyed by exact tool name or MCP leaf. */
57
+ toolRenderers?: Record<string, NativeToolRenderer> | undefined;
58
+ /** Host renderer consulted for every tool call (return null for the shared row). */
59
+ renderTool?: NativeToolRenderer | undefined;
60
+ /** Active compute label for exec previews ("on <label> · …"). */
61
+ computeLabel?: string | null | undefined;
62
+ renderMarkdown?:
63
+ | ((text: string, options: { tone: "body" | "muted"; streaming?: boolean }) => ReactNode)
64
+ | undefined;
65
+ onOpenSession?: ((sessionId: string) => void) | undefined;
66
+ }
67
+
68
+ const ActivityOptionsContext = createContext<NativeActivityOptions>({});
69
+ export const NativeActivityOptionsProvider = ActivityOptionsContext.Provider;
70
+ export function useNativeActivityOptions(): NativeActivityOptions {
71
+ return useContext(ActivityOptionsContext);
72
+ }
73
+
74
+ const TOOL_ICON: Record<ToolIconKind, NativeIconName> = {
75
+ terminal: "terminal",
76
+ keyboard: "keyboard",
77
+ "file-diff": "file-diff",
78
+ search: "search",
79
+ question: "message-circle-question",
80
+ target: "target",
81
+ brain: "brain-circuit",
82
+ sessions: "messages-square",
83
+ server: "server",
84
+ "server-cog": "server-cog",
85
+ calendar: "calendar-clock",
86
+ panels: "panels-top-left",
87
+ share: "share-2",
88
+ message: "message-square",
89
+ git: "folder-git",
90
+ box: "box",
91
+ key: "key-round",
92
+ "file-search": "file-search",
93
+ "package-search": "package-search",
94
+ plug: "plug",
95
+ wrench: "wrench",
96
+ };
97
+
98
+ function PreviewNode({ preview, compact }: { preview: ToolPreview; compact: boolean }) {
99
+ const theme = useNativeTimelineTheme();
100
+ const mono = { ...fontStyle(theme, 400, "mono"), fontSize: theme.size.sm, lineHeight: 18 };
101
+ switch (preview.kind) {
102
+ case "text":
103
+ return preview.running ? (
104
+ <RunningPreview text={preview.text} compact={compact} />
105
+ ) : (
106
+ <>{preview.text}</>
107
+ );
108
+ case "path":
109
+ return (
110
+ <Text numberOfLines={1} style={mono}>
111
+ <Text style={{ color: theme.colors["fg-subtle"] }}>{pathDirname(preview.path)}</Text>
112
+ <Text style={{ color: theme.colors["fg-muted"] }}>{pathBasename(preview.path)}</Text>
113
+ {preview.add != null || preview.del != null ? (
114
+ <Text style={{ color: theme.colors["fg-subtle"] }}>
115
+ {" "}
116
+ {preview.add != null ? `+${preview.add}` : ""}
117
+ {preview.add != null && preview.del != null ? " " : ""}
118
+ {preview.del != null ? `−${preview.del}` : ""}
119
+ </Text>
120
+ ) : null}
121
+ </Text>
122
+ );
123
+ case "files":
124
+ return (
125
+ <Text numberOfLines={1} style={mono}>
126
+ <Text style={{ color: theme.colors["fg-muted"] }}>{preview.count} files</Text>
127
+ <Text
128
+ style={{ color: theme.colors["fg-subtle"] }}
129
+ >{` +${preview.add} −${preview.del}`}</Text>
130
+ </Text>
131
+ );
132
+ case "malformed":
133
+ return (
134
+ <Text numberOfLines={1} style={mono}>
135
+ <Text style={{ color: theme.colors["fg-muted"] }}>{preview.name}</Text>
136
+ <Text style={{ color: theme.colors["fg-subtle"] }}>{" malformed V4A"}</Text>
137
+ </Text>
138
+ );
139
+ }
140
+ }
141
+
142
+ function previewContent(preview: ToolPreview, compact: boolean): ReactNode {
143
+ if (preview.kind === "text" && !preview.running) return preview.text;
144
+ return <PreviewNode preview={preview} compact={compact} />;
145
+ }
146
+
147
+ function keyedResults(results: readonly WebSearchResult[]) {
148
+ const seen = new Map<string, number>();
149
+ return results.map((result) => {
150
+ const content = `${result.domain}\u0000${result.title}\u0000${result.snippet}`;
151
+ const occurrence = (seen.get(content) ?? 0) + 1;
152
+ seen.set(content, occurrence);
153
+ return { key: `${content}\u0000${occurrence}`, result };
154
+ });
155
+ }
156
+
157
+ function ToolBodyView({ body }: { body: ToolBody }) {
158
+ const theme = useNativeTimelineTheme();
159
+ switch (body.kind) {
160
+ case "term":
161
+ return (
162
+ <>
163
+ <TermBlock
164
+ command={body.command}
165
+ workdir={body.workdir}
166
+ output={body.output}
167
+ live={body.live}
168
+ failed={body.failed}
169
+ />
170
+ {body.note ? <BodyNote>{body.note}</BodyNote> : null}
171
+ </>
172
+ );
173
+ case "note":
174
+ return <BodyNote tone={body.tone}>{body.text}</BodyNote>;
175
+ case "payloads":
176
+ return (
177
+ <>
178
+ {body.blocks.map((block) => (
179
+ <PayloadBlock
180
+ key={block.label}
181
+ label={block.label}
182
+ value={block.value}
183
+ failed={block.failed}
184
+ />
185
+ ))}
186
+ </>
187
+ );
188
+ case "patch":
189
+ return (
190
+ <>
191
+ {body.files.map((entry) => (
192
+ <PatchView key={entry.key} path={entry.path} diff={entry.diff} bare={body.bare} />
193
+ ))}
194
+ </>
195
+ );
196
+ case "web-results":
197
+ if (!body.results?.length) {
198
+ return <BodyNote>results folded into model context — no list available.</BodyNote>;
199
+ }
200
+ return (
201
+ <View style={{ gap: 8 }}>
202
+ {keyedResults(body.results).map(({ key, result }) => (
203
+ <View key={key} style={{ flexDirection: "row", gap: 10 }}>
204
+ <View style={{ marginTop: 3 }}>
205
+ <Icon name="globe" size={14} color={theme.colors["fg-subtle"]} />
206
+ </View>
207
+ <View style={{ flex: 1, minWidth: 0 }}>
208
+ <Text
209
+ numberOfLines={1}
210
+ style={{ ...fontStyle(theme), fontSize: theme.size.base, color: theme.colors.fg }}
211
+ >
212
+ {result.title}{" "}
213
+ <Text style={{ color: theme.colors["fg-subtle"] }}>{result.domain}</Text>
214
+ </Text>
215
+ <Text
216
+ style={{
217
+ ...fontStyle(theme),
218
+ fontSize: theme.size.sm,
219
+ lineHeight: 20,
220
+ color: theme.colors["fg-muted"],
221
+ }}
222
+ >
223
+ {result.snippet}
224
+ </Text>
225
+ </View>
226
+ </View>
227
+ ))}
228
+ </View>
229
+ );
230
+ case "listing":
231
+ return (
232
+ <>
233
+ {body.note ? <BodyNote>{body.note}</BodyNote> : null}
234
+ {body.entries.length > 0 ? (
235
+ <View style={{ gap: body.entries.some((entry) => entry.snippet) ? 8 : 6 }}>
236
+ {body.entries.map((entry) => (
237
+ <View key={entry.key} style={{ minWidth: 0 }}>
238
+ <View style={{ flexDirection: "row", alignItems: "baseline", gap: 8 }}>
239
+ {entry.eyebrow ? (
240
+ <Text
241
+ style={{
242
+ ...fontStyle(theme),
243
+ fontSize: theme.size.xs,
244
+ color: theme.colors["fg-subtle"],
245
+ }}
246
+ >
247
+ {entry.eyebrow}
248
+ </Text>
249
+ ) : null}
250
+ <Text
251
+ numberOfLines={1}
252
+ style={{
253
+ ...(entry.mono ? fontStyle(theme, 400, "mono") : fontStyle(theme, 500)),
254
+ fontSize: theme.size.sm,
255
+ color: theme.colors.fg,
256
+ flexShrink: 1,
257
+ }}
258
+ >
259
+ {entry.title}
260
+ </Text>
261
+ </View>
262
+ {entry.snippet ? (
263
+ <Text
264
+ numberOfLines={2}
265
+ style={{
266
+ ...fontStyle(theme),
267
+ marginTop: 2,
268
+ fontSize: theme.size.xs,
269
+ color: theme.colors["fg-muted"],
270
+ }}
271
+ >
272
+ {entry.snippet}
273
+ </Text>
274
+ ) : null}
275
+ </View>
276
+ ))}
277
+ {body.more ? (
278
+ <Text
279
+ style={{
280
+ ...fontStyle(theme),
281
+ fontSize: theme.size.xs,
282
+ color: theme.colors["fg-muted"],
283
+ }}
284
+ >
285
+ +{body.more} more
286
+ </Text>
287
+ ) : null}
288
+ </View>
289
+ ) : body.empty ? (
290
+ <BodyNote>{body.empty}</BodyNote>
291
+ ) : null}
292
+ {body.blocks.map((block) => (
293
+ <PayloadBlock
294
+ key={block.label}
295
+ label={block.label}
296
+ value={block.value}
297
+ failed={block.failed}
298
+ />
299
+ ))}
300
+ </>
301
+ );
302
+ }
303
+ }
304
+
305
+ /** Unified-diff-ish view of a V4A patch: add/del gutters tinted like the web diff. */
306
+ function PatchView({
307
+ path,
308
+ diff,
309
+ bare,
310
+ }: {
311
+ path: string;
312
+ diff: string;
313
+ bare?: boolean | undefined;
314
+ }) {
315
+ const theme = useNativeTimelineTheme();
316
+ const mono = { ...fontStyle(theme, 400, "mono"), fontSize: theme.size.xs, lineHeight: 18 };
317
+ let offset = 0;
318
+ const lines = diff
319
+ .split("\n")
320
+ .map((text) => {
321
+ const entry = { text, key: `l${offset}` };
322
+ offset += text.length + 1;
323
+ return entry;
324
+ })
325
+ .filter((entry) => !entry.text.startsWith("*** "));
326
+ return (
327
+ <View
328
+ style={{
329
+ borderRadius: theme.radius.sm,
330
+ borderWidth: 1,
331
+ borderColor: theme.colors.border,
332
+ overflow: "hidden",
333
+ }}
334
+ >
335
+ {bare ? null : (
336
+ <Text
337
+ style={[
338
+ mono,
339
+ {
340
+ paddingHorizontal: 8,
341
+ paddingVertical: 6,
342
+ color: theme.colors["fg-muted"],
343
+ backgroundColor: theme.colors["surface-2"],
344
+ },
345
+ ]}
346
+ numberOfLines={1}
347
+ >
348
+ {path}
349
+ </Text>
350
+ )}
351
+ {lines.slice(0, 400).map(({ text: line, key }) => (
352
+ <Text
353
+ key={key}
354
+ style={[
355
+ mono,
356
+ {
357
+ paddingHorizontal: 8,
358
+ color: theme.colors["fg-muted"],
359
+ backgroundColor: line.startsWith("+")
360
+ ? theme.colors["diff-add-bg"]
361
+ : line.startsWith("-")
362
+ ? theme.colors["diff-del-bg"]
363
+ : "transparent",
364
+ },
365
+ ]}
366
+ >
367
+ {line || " "}
368
+ </Text>
369
+ ))}
370
+ </View>
371
+ );
372
+ }
373
+
374
+ export function PresentedToolRow({
375
+ presentation: p,
376
+ compact = false,
377
+ }: {
378
+ presentation: ToolRowPresentation;
379
+ compact?: boolean;
380
+ }) {
381
+ return (
382
+ <ActivityDisclosure
383
+ icon={TOOL_ICON[p.icon]}
384
+ iconTone={p.iconTone}
385
+ title={p.title}
386
+ titleMono={p.titleMono}
387
+ running={p.running}
388
+ chip={p.chip}
389
+ failed={p.failed}
390
+ cancelled={p.cancelled}
391
+ compact={compact}
392
+ preview={p.preview ? previewContent(p.preview, compact) : undefined}
393
+ >
394
+ <ToolBodyView body={p.body} />
395
+ </ActivityDisclosure>
396
+ );
397
+ }
398
+
399
+ function ToolCallRow({ item, compact }: { item: ToolCallItem; compact: boolean }) {
400
+ const options = useNativeActivityOptions();
401
+ const presentation = toolRowPresentation(item, { computeLabel: options.computeLabel ?? null });
402
+ const leaf = item.name.includes("__")
403
+ ? item.name.slice(item.name.lastIndexOf("__") + 2)
404
+ : item.name;
405
+ const custom = options.toolRenderers?.[item.name] ?? options.toolRenderers?.[leaf];
406
+ for (const renderer of [custom, options.renderTool]) {
407
+ if (!renderer) continue;
408
+ const node = renderer({ item, presentation, compact });
409
+ if (node != null) return <>{node}</>;
410
+ }
411
+ return <PresentedToolRow presentation={presentation} compact={compact} />;
412
+ }
413
+
414
+ function ReasoningRow({ item, compact }: { item: ReasoningItem; compact: boolean }) {
415
+ const theme = useNativeTimelineTheme();
416
+ const options = useNativeActivityOptions();
417
+ const plain = item.text
418
+ .replace(/\*\*|__|`/g, "")
419
+ .replace(/\s+/g, " ")
420
+ .trim();
421
+ const titleStyle = {
422
+ ...fontStyle(theme, 500),
423
+ fontSize: theme.size.base,
424
+ lineHeight: 20,
425
+ color: theme.colors["fg-muted"],
426
+ };
427
+ return (
428
+ <ActivityDisclosure
429
+ icon="brain"
430
+ iconTone="muted"
431
+ compact={compact}
432
+ running={item.streaming}
433
+ title={
434
+ item.streaming ? (
435
+ <ShimmerText style={titleStyle} numberOfLines={1}>
436
+ Thinking
437
+ </ShimmerText>
438
+ ) : (
439
+ <Text
440
+ style={{
441
+ ...fontStyle(theme, 400),
442
+ fontSize: theme.size.base,
443
+ lineHeight: 20,
444
+ fontStyle: "italic",
445
+ color: theme.colors["fg-subtle"],
446
+ }}
447
+ >
448
+ Thought
449
+ </Text>
450
+ )
451
+ }
452
+ accessibilityLabel={item.streaming ? "Thinking" : "Thought"}
453
+ preview={plain || undefined}
454
+ >
455
+ {options.renderMarkdown ? (
456
+ options.renderMarkdown(item.text, { tone: "muted", streaming: item.streaming })
457
+ ) : (
458
+ <Text
459
+ style={{
460
+ ...fontStyle(theme),
461
+ fontSize: theme.size.base,
462
+ lineHeight: 24,
463
+ color: theme.colors["fg-muted"],
464
+ }}
465
+ >
466
+ {item.text}
467
+ </Text>
468
+ )}
469
+ </ActivityDisclosure>
470
+ );
471
+ }
472
+
473
+ function SandboxRow({ item, compact }: { item: SandboxItem; compact: boolean }) {
474
+ return (
475
+ <ActivityDisclosure
476
+ icon="square-terminal"
477
+ iconTone={
478
+ item.status === "failed" ? "failed" : item.status === "running" ? "running" : "muted"
479
+ }
480
+ title={sandboxRowTitle(item, (name) => toolDisplayName(name))}
481
+ running={item.status === "running"}
482
+ failed={item.status === "failed"}
483
+ cancelled={item.status === "cancelled"}
484
+ compact={compact}
485
+ preview={item.command ?? undefined}
486
+ >
487
+ {item.command ? <PayloadBlock label="Command" value={item.command} /> : null}
488
+ {item.output ? <PayloadBlock label="Output" value={item.output} /> : null}
489
+ </ActivityDisclosure>
490
+ );
491
+ }
492
+
493
+ function StartupPhaseRow({
494
+ item,
495
+ compact,
496
+ }: {
497
+ item: Extract<ActivityItem, { kind: "startup-phase" }>;
498
+ compact: boolean;
499
+ }) {
500
+ const failed = item.status === "failed";
501
+ const running = item.status === "running";
502
+ const duration = startupDuration(item.durationMs);
503
+ return (
504
+ <ActivityDisclosure
505
+ icon="bot"
506
+ iconTone={failed ? "failed" : running ? "running" : "muted"}
507
+ title={
508
+ item.blockedReason
509
+ ? STARTUP_WAIT_TITLES[item.blockedReason]
510
+ : startupPhaseTitle(item.phase, item.status, item.outcome)
511
+ }
512
+ preview={
513
+ item.phase === "model_preparation"
514
+ ? "Includes overlapping sandbox startup, custom environment setup, repository preparation, and runtime setup shown below."
515
+ : undefined
516
+ }
517
+ running={running}
518
+ failed={failed}
519
+ cancelled={item.status === "cancelled"}
520
+ chip={duration ? { tone: failed ? "bad" : "muted", text: duration } : undefined}
521
+ expandable={false}
522
+ compact={compact}
523
+ />
524
+ );
525
+ }
526
+
527
+ function WorkerRow({ item, compact }: { item: WorkerItem; compact: boolean }) {
528
+ const theme = useNativeTimelineTheme();
529
+ const options = useNativeActivityOptions();
530
+ const running = item.status === "running";
531
+ const failed = item.status === "failed";
532
+ const cancelled = item.status === "cancelled";
533
+ const title = workerRowTitle(item);
534
+ if (compact) {
535
+ return (
536
+ <ActivityDisclosure
537
+ icon="bot"
538
+ title={title}
539
+ preview={item.prompt}
540
+ running={running}
541
+ compact
542
+ />
543
+ );
544
+ }
545
+ const sessionId = item.workerSessionId;
546
+ const deepLink = Boolean(sessionId) && Boolean(options.onOpenSession) && !failed && !cancelled;
547
+ const titleStyle = {
548
+ ...fontStyle(theme, 500),
549
+ fontSize: theme.size.base,
550
+ lineHeight: 20,
551
+ color: failed ? theme.colors["status-failed"] : theme.colors.fg,
552
+ };
553
+ const inner = (
554
+ <View
555
+ style={{
556
+ flexDirection: "row",
557
+ alignItems: "flex-start",
558
+ gap: 8,
559
+ minHeight: ROW_MIN_HEIGHT,
560
+ paddingHorizontal: 6,
561
+ paddingVertical: 10,
562
+ }}
563
+ >
564
+ <View style={{ width: 14 }} />
565
+ <View style={{ marginTop: 3 }}>
566
+ <Icon
567
+ name="bot"
568
+ size={14}
569
+ color={failed ? theme.colors["status-failed"] : theme.colors.accent}
570
+ />
571
+ </View>
572
+ <View style={{ flex: 1, minWidth: 0 }}>
573
+ <ShimmerText style={titleStyle} active={running}>
574
+ {title}
575
+ </ShimmerText>
576
+ {item.prompt ? (
577
+ <Text
578
+ numberOfLines={1}
579
+ style={{
580
+ ...fontStyle(theme),
581
+ marginTop: 2,
582
+ fontSize: theme.size.sm,
583
+ color: theme.colors["fg-muted"],
584
+ }}
585
+ >
586
+ {item.prompt}
587
+ </Text>
588
+ ) : null}
589
+ {failed && item.failure ? (
590
+ <View style={{ marginTop: 4 }}>
591
+ <Text
592
+ style={{
593
+ ...fontStyle(theme, 400, "mono"),
594
+ fontSize: theme.size.xs,
595
+ color: theme.colors["status-failed"],
596
+ }}
597
+ >
598
+ {item.failure.code}
599
+ </Text>
600
+ <Text
601
+ style={{
602
+ ...fontStyle(theme),
603
+ marginTop: 2,
604
+ fontSize: theme.size.sm,
605
+ color: theme.colors["status-failed"],
606
+ }}
607
+ >
608
+ {item.failure.message}
609
+ </Text>
610
+ </View>
611
+ ) : null}
612
+ </View>
613
+ {failed ? (
614
+ <Chip chip={{ tone: "bad", text: "failed" }} />
615
+ ) : cancelled ? (
616
+ <Chip chip={{ tone: "interrupted", text: "interrupted" }} />
617
+ ) : deepLink ? (
618
+ <Icon name="chevron-right" size={14} color={theme.colors["fg-subtle"]} />
619
+ ) : null}
620
+ </View>
621
+ );
622
+ if (deepLink && sessionId && options.onOpenSession) {
623
+ const open = options.onOpenSession;
624
+ return (
625
+ <Pressable accessibilityRole="button" onPress={() => open(sessionId)}>
626
+ {inner}
627
+ </Pressable>
628
+ );
629
+ }
630
+ return inner;
631
+ }
632
+
633
+ /** Assistant commentary folded into the rail: quieter than an answer. */
634
+ function ActivityNoteRow({ item }: { item: AgentMessageItem }) {
635
+ const theme = useNativeTimelineTheme();
636
+ const options = useNativeActivityOptions();
637
+ if (!item.text.trim()) return null;
638
+ return (
639
+ <View
640
+ style={{
641
+ flexDirection: "row",
642
+ alignItems: "flex-start",
643
+ gap: 8,
644
+ paddingHorizontal: 6,
645
+ paddingVertical: 6,
646
+ }}
647
+ >
648
+ <View style={{ width: 14 }} />
649
+ <View style={{ marginTop: 4 }}>
650
+ <Icon name="message-square-text" size={14} color={theme.colors["fg-subtle"]} />
651
+ </View>
652
+ <View style={{ flex: 1, minWidth: 0 }}>
653
+ {options.renderMarkdown ? (
654
+ options.renderMarkdown(item.text, { tone: "muted", streaming: item.streaming })
655
+ ) : (
656
+ <Text
657
+ style={{
658
+ ...fontStyle(theme),
659
+ fontSize: theme.size.sm,
660
+ lineHeight: 20,
661
+ color: theme.colors["fg-muted"],
662
+ }}
663
+ >
664
+ {item.text}
665
+ </Text>
666
+ )}
667
+ </View>
668
+ </View>
669
+ );
670
+ }
671
+
672
+ /** Renders one activity item — the native `renderActivity`. */
673
+ export function ActivityRow({ item, compact = false }: { item: ActivityItem; compact?: boolean }) {
674
+ switch (item.kind) {
675
+ case "tool-call":
676
+ return <ToolCallRow item={item} compact={compact} />;
677
+ case "reasoning":
678
+ return <ReasoningRow item={item} compact={compact} />;
679
+ case "sandbox":
680
+ return <SandboxRow item={item} compact={compact} />;
681
+ case "startup-phase":
682
+ return <StartupPhaseRow item={item} compact={compact} />;
683
+ case "worker":
684
+ return <WorkerRow item={item} compact={compact} />;
685
+ case "agent-message":
686
+ return compact ? (
687
+ <ActivityDisclosure
688
+ icon="message-square-text"
689
+ title={item.text.replace(/\s+/g, " ").trim()}
690
+ compact
691
+ />
692
+ ) : (
693
+ <ActivityNoteRow item={item} />
694
+ );
695
+ case "knowledge":
696
+ return (
697
+ <ActivityDisclosure
698
+ icon="file-search"
699
+ iconTone={item.status === "failed" ? "failed" : "muted"}
700
+ title={
701
+ item.outcome === "pending"
702
+ ? "Proposed knowledge"
703
+ : item.outcome === "failed"
704
+ ? "Knowledge save failed"
705
+ : "Saved to knowledge"
706
+ }
707
+ preview={
708
+ "filename" in item && typeof item.filename === "string" ? item.filename : undefined
709
+ }
710
+ expandable={false}
711
+ compact={compact}
712
+ />
713
+ );
714
+ case "memory":
715
+ return (
716
+ <ActivityDisclosure
717
+ icon="brain-circuit"
718
+ title="Saved to memory"
719
+ preview={"text" in item && typeof item.text === "string" ? item.text : undefined}
720
+ expandable={false}
721
+ compact={compact}
722
+ />
723
+ );
724
+ case "fleet-decision":
725
+ return (
726
+ <ActivityDisclosure
727
+ icon="messages-square"
728
+ title="Worker decision"
729
+ expandable={false}
730
+ compact={compact}
731
+ />
732
+ );
733
+ default:
734
+ return null;
735
+ }
736
+ }