@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,485 @@
1
+ import type { HumanInputAnswer, HumanInputQuestion, SessionStatus } from "@opengeni/sdk";
2
+ import type { ActivityItem, TimelineItem } from "@opengeni/react/session";
3
+ import { DEFAULT_OPENGENI_NATIVE_LABELS, type OpenGeniNativeLabels } from "./presentation";
4
+
5
+ export function nativeKnowledgeOutcomeLabel(
6
+ item: Extract<TimelineItem, { kind: "knowledge" }>,
7
+ labels: OpenGeniNativeLabels,
8
+ ): string {
9
+ return (labels.knowledgeOutcome ?? DEFAULT_OPENGENI_NATIVE_LABELS.knowledgeOutcome)[item.outcome];
10
+ }
11
+
12
+ export type NativeSessionStatusTone = "neutral" | "working" | "attention" | "failed" | "complete";
13
+
14
+ export interface NativeActivityPresentation {
15
+ title: string;
16
+ preview: string;
17
+ status: string | null;
18
+ detail: string | null;
19
+ expandedByDefault: boolean;
20
+ tone: "neutral" | "running" | "failed";
21
+ }
22
+
23
+ export function nativeSessionStatusTone(status: SessionStatus | null): NativeSessionStatusTone {
24
+ switch (status) {
25
+ case "queued":
26
+ case "running":
27
+ case "recovering":
28
+ case "waiting_capacity":
29
+ return "working";
30
+ case "requires_action":
31
+ return "attention";
32
+ case "failed":
33
+ return "failed";
34
+ case "cancelled":
35
+ return "complete";
36
+ default:
37
+ return "neutral";
38
+ }
39
+ }
40
+
41
+ export function boundedJson(value: unknown, maximum = 1_200): string {
42
+ let serialized: string;
43
+ try {
44
+ serialized = JSON.stringify(value, null, 2) ?? "";
45
+ } catch {
46
+ serialized = String(value);
47
+ }
48
+ return serialized.length <= maximum ? serialized : `${serialized.slice(0, maximum)}…`;
49
+ }
50
+
51
+ function plainTextContent(value: string): string {
52
+ return value
53
+ .replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1")
54
+ .replace(/\[([^\]]+)\]\([^)]*\)/g, "$1")
55
+ .replace(/^\s{0,3}(?:#{1,6}|>|[-+])\s+/gm, "")
56
+ .replace(/\*\*|__|~~|`/g, "")
57
+ .replace(/(^|\s)[*_](?=\S)/g, "$1")
58
+ .replace(/(?<=\S)[*_](?=\s|$|[.,!?;:])/g, "")
59
+ .trim();
60
+ }
61
+
62
+ function normalizedPreview(value: string, maximum = 140): string {
63
+ const normalized = plainTextContent(value).replace(/\s+/g, " ").trim();
64
+ if (!normalized) return "";
65
+ return normalized.length <= maximum ? normalized : `${normalized.slice(0, maximum - 1)}…`;
66
+ }
67
+
68
+ function recordPreview(value: object): string {
69
+ for (const key of ["summary", "message", "title", "name", "status", "text", "query", "cmd"]) {
70
+ const candidate: unknown = Reflect.get(value, key);
71
+ if (typeof candidate === "string") {
72
+ const preview = normalizedPreview(candidate);
73
+ if (preview) return preview;
74
+ }
75
+ }
76
+ const keys = Object.keys(value);
77
+ return keys.length > 0 ? keys.slice(0, 4).join(", ") : "";
78
+ }
79
+
80
+ export function inspectableValuePreview(value: unknown): string {
81
+ if (typeof value === "string") return normalizedPreview(value);
82
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
83
+ if (Array.isArray(value)) return value.length === 1 ? "1 item" : `${value.length} items`;
84
+ if (value && typeof value === "object") return recordPreview(value);
85
+ return "";
86
+ }
87
+
88
+ export function nativeToolDisplayName(name: string): string {
89
+ const boundary = name.indexOf("__");
90
+ const leaf = boundary >= 0 ? name.slice(boundary + 2) : name;
91
+ const phrase = leaf.replace(/[_-]+/g, " ").trim();
92
+ return phrase ? phrase.charAt(0).toLocaleUpperCase() + phrase.slice(1) : name;
93
+ }
94
+
95
+ function activityStatus(
96
+ item: Extract<ActivityItem, { status: "running" | "complete" | "failed" | "cancelled" }>,
97
+ labels: OpenGeniNativeLabels,
98
+ ): {
99
+ status: string | null;
100
+ fallbackStatus: string;
101
+ expandedByDefault: boolean;
102
+ tone: NativeActivityPresentation["tone"];
103
+ } {
104
+ const fallbackStatus = nativeTimelineStatusLabel(item.status, labels);
105
+ return {
106
+ status: item.status === "complete" ? null : fallbackStatus,
107
+ fallbackStatus,
108
+ expandedByDefault: item.status === "failed",
109
+ tone: item.status === "running" ? "running" : item.status === "failed" ? "failed" : "neutral",
110
+ };
111
+ }
112
+
113
+ export function nativeActivityPresentation(
114
+ item: ActivityItem,
115
+ labels: OpenGeniNativeLabels,
116
+ ): NativeActivityPresentation {
117
+ switch (item.kind) {
118
+ case "agent-message":
119
+ return {
120
+ title: labels.assistant,
121
+ preview: normalizedPreview(item.text),
122
+ status: item.streaming ? labels.statusRunning : null,
123
+ detail: plainTextContent(item.text) || null,
124
+ expandedByDefault: false,
125
+ tone: item.streaming ? "running" : "neutral",
126
+ };
127
+ case "reasoning":
128
+ return {
129
+ title: item.streaming ? labels.thinking : labels.thought,
130
+ preview: normalizedPreview(item.text),
131
+ status: item.streaming ? labels.statusRunning : null,
132
+ detail: plainTextContent(item.text) || null,
133
+ expandedByDefault: false,
134
+ tone: item.streaming ? "running" : "neutral",
135
+ };
136
+ case "tool-call": {
137
+ const outputPreview = inspectableValuePreview(item.output);
138
+ const argumentPreview = inspectableValuePreview(item.arguments);
139
+ const status = activityStatus(item, labels);
140
+ return {
141
+ title: nativeToolDisplayName(item.name),
142
+ preview: outputPreview || argumentPreview || status.fallbackStatus,
143
+ detail: boundedJson({
144
+ input: item.arguments,
145
+ ...(item.output !== undefined ? { result: item.output } : {}),
146
+ ...(item.truncation ? { truncation: item.truncation } : {}),
147
+ }),
148
+ status: status.status,
149
+ expandedByDefault: status.expandedByDefault,
150
+ tone: status.tone,
151
+ };
152
+ }
153
+ case "worker": {
154
+ const status = activityStatus(item, labels);
155
+ return {
156
+ title: labels.worker,
157
+ preview:
158
+ normalizedPreview(item.prompt ?? item.failure?.message ?? "") || status.fallbackStatus,
159
+ detail: item.failure
160
+ ? boundedJson(item.failure)
161
+ : item.prompt?.trim() || item.workerSessionId || null,
162
+ status: status.status,
163
+ expandedByDefault: status.expandedByDefault,
164
+ tone: status.tone,
165
+ };
166
+ }
167
+ case "sandbox": {
168
+ const status = activityStatus(item, labels);
169
+ return {
170
+ title: nativeToolDisplayName(item.name || labels.sandbox),
171
+ preview:
172
+ inspectableValuePreview(item.output) ||
173
+ inspectableValuePreview(item.command) ||
174
+ status.fallbackStatus,
175
+ detail: boundedJson({
176
+ ...(item.command ? { command: item.command } : {}),
177
+ ...(item.output ? { output: item.output } : {}),
178
+ ...(item.origin ? { origin: item.origin } : {}),
179
+ }),
180
+ status: status.status,
181
+ expandedByDefault: status.expandedByDefault,
182
+ tone: status.tone,
183
+ };
184
+ }
185
+ case "startup-phase": {
186
+ const status = activityStatus(item, labels);
187
+ return {
188
+ title: nativeToolDisplayName(item.phase),
189
+ preview: item.outcome ? nativeToolDisplayName(item.outcome) : status.fallbackStatus,
190
+ detail:
191
+ item.durationMs == null
192
+ ? null
193
+ : `${Math.max(0, Math.round(item.durationMs / 100) / 10)}s`,
194
+ status: status.status,
195
+ expandedByDefault: status.expandedByDefault,
196
+ tone: status.tone,
197
+ };
198
+ }
199
+ case "knowledge":
200
+ return {
201
+ title: nativeKnowledgeOutcomeLabel(item, labels),
202
+ preview: item.filename ?? "",
203
+ status: null,
204
+ detail: item.filename ?? null,
205
+ expandedByDefault: false,
206
+ tone: item.status === "failed" ? "failed" : "neutral",
207
+ };
208
+ case "memory":
209
+ return {
210
+ title: nativeMemoryActionLabel(item, labels),
211
+ preview: normalizedPreview(item.replacementPreview ?? item.preview),
212
+ status: null,
213
+ detail: item.replacementPreview
214
+ ? boundedJson({ before: item.preview, after: item.replacementPreview })
215
+ : item.preview,
216
+ expandedByDefault: false,
217
+ tone: "neutral",
218
+ };
219
+ case "fleet-decision": {
220
+ const summary = nativeFleetDecisionSummary(item, labels);
221
+ return {
222
+ title: labels.fleetDecision,
223
+ preview: summary,
224
+ status: null,
225
+ detail: boundedJson({
226
+ outcome: item.actualOutcome,
227
+ reason: item.actualReason,
228
+ admission: item.admissionOutcome,
229
+ candidates: item.candidateCount,
230
+ }),
231
+ expandedByDefault: false,
232
+ tone: item.actualOutcome === "waiting" ? "running" : "neutral",
233
+ };
234
+ }
235
+ }
236
+ }
237
+
238
+ function formatLabel(template: string, values: Readonly<Record<string, string | number>>): string {
239
+ return Object.entries(values).reduce(
240
+ (formatted, [key, value]) => formatted.split(`{${key}}`).join(String(value)),
241
+ template,
242
+ );
243
+ }
244
+
245
+ export function formatNativeRelativeTime(
246
+ iso: string,
247
+ labels: Pick<
248
+ OpenGeniNativeLabels,
249
+ "relativeNow" | "relativeSeconds" | "relativeMinutes" | "relativeHours" | "relativeDays"
250
+ >,
251
+ now: Date = new Date(),
252
+ ): string {
253
+ const then = new Date(iso).getTime();
254
+ if (Number.isNaN(then)) return "";
255
+
256
+ const seconds = Math.max(0, Math.floor((now.getTime() - then) / 1_000));
257
+ if (seconds < 10) return labels.relativeNow;
258
+ if (seconds < 60) return formatLabel(labels.relativeSeconds, { value: seconds });
259
+
260
+ const minutes = Math.floor(seconds / 60);
261
+ if (minutes < 60) return formatLabel(labels.relativeMinutes, { value: minutes });
262
+
263
+ const hours = Math.floor(minutes / 60);
264
+ if (hours < 24) return formatLabel(labels.relativeHours, { value: hours });
265
+
266
+ const days = Math.floor(hours / 24);
267
+ if (days < 14) return formatLabel(labels.relativeDays, { value: days });
268
+ // oxlint-disable-next-line architecture/no-unsafe-date-format -- Timeline age intentionally matches OpenGeni's device-local relative-time formatter; this is not an organization-domain date.
269
+ return new Date(iso).toLocaleDateString();
270
+ }
271
+
272
+ export function nativeTimelineStatusLabel(
273
+ status: "running" | "complete" | "failed" | "cancelled",
274
+ labels: OpenGeniNativeLabels,
275
+ ): string {
276
+ switch (status) {
277
+ case "running":
278
+ return labels.statusRunning;
279
+ case "complete":
280
+ return labels.statusComplete;
281
+ case "failed":
282
+ return labels.statusFailed;
283
+ case "cancelled":
284
+ return labels.statusCancelled;
285
+ }
286
+ }
287
+
288
+ export function nativeGoalActionLabel(
289
+ action: Extract<TimelineItem, { kind: "goal" }>["action"],
290
+ labels: OpenGeniNativeLabels,
291
+ ): string {
292
+ return labels.goalAction[action];
293
+ }
294
+
295
+ export function nativeHumanInputRequestPreview(
296
+ requests: readonly {
297
+ questions: readonly Pick<HumanInputQuestion, "label" | "prompt">[];
298
+ }[],
299
+ ): string {
300
+ return requests
301
+ .flatMap((request) =>
302
+ request.questions.map((question) => question.label?.trim() || question.prompt.trim()),
303
+ )
304
+ .filter((label) => label.length > 0)
305
+ .join(" · ");
306
+ }
307
+
308
+ export function nativeHumanInputSummary(
309
+ item: Extract<TimelineItem, { kind: "human-input" }>,
310
+ labels: OpenGeniNativeLabels,
311
+ ): string {
312
+ const questions = item.questions
313
+ .map((question) => (question.label ? `${question.label}: ${question.prompt}` : question.prompt))
314
+ .join("\n");
315
+ const outcome = labels.humanInputOutcome[item.response.outcome];
316
+ const answers = item.answers
317
+ .map(
318
+ (answer) =>
319
+ `${answer.label}: ${answer.values.join(", ") || labels.humanInputOutcome.answered}`,
320
+ )
321
+ .join("\n");
322
+
323
+ return [questions, answers ? `${outcome}: ${answers}` : outcome].filter(Boolean).join("\n");
324
+ }
325
+
326
+ export function nativeContextCompactionPhaseLabel(
327
+ phase: Extract<TimelineItem, { kind: "context-compaction" }>["phase"],
328
+ labels: OpenGeniNativeLabels,
329
+ ): string {
330
+ return labels.contextCompactionPhase[phase];
331
+ }
332
+
333
+ export function nativeMemoryActionLabel(
334
+ item: Extract<TimelineItem, { kind: "memory" }>,
335
+ labels: OpenGeniNativeLabels,
336
+ ): string {
337
+ if (item.variant === "saved") return labels.memoryAction.saved;
338
+ if (item.replacementPreview) return labels.memoryAction.corrected;
339
+ return item.action === "updated" ? labels.memoryAction.updated : labels.memoryAction.archived;
340
+ }
341
+
342
+ export function nativeFleetDecisionSummary(
343
+ item: Extract<TimelineItem, { kind: "fleet-decision" }>,
344
+ labels: OpenGeniNativeLabels,
345
+ ): string {
346
+ const outcome =
347
+ item.actualOutcome === "selected" && item.actualCandidateKey
348
+ ? formatLabel(labels.fleetOutcome.selected, { candidate: item.actualCandidateKey })
349
+ : item.actualOutcome === "waiting"
350
+ ? labels.fleetOutcome.waiting
351
+ : labels.fleetOutcome.none;
352
+ const reason = (() => {
353
+ switch (item.actualReason) {
354
+ case "lease_reused":
355
+ return labels.fleetReason.leaseReused;
356
+ case "pin":
357
+ return labels.fleetReason.pin;
358
+ case "rotation":
359
+ return labels.fleetReason.rotation;
360
+ case "active":
361
+ return labels.fleetReason.active;
362
+ case "all_capped":
363
+ return labels.fleetReason.allCapped;
364
+ case "none":
365
+ return labels.fleetReason.none;
366
+ }
367
+ })();
368
+ return `${outcome} · ${reason}`;
369
+ }
370
+
371
+ export function timelineAccessibilityLabel(
372
+ item: TimelineItem,
373
+ labels: OpenGeniNativeLabels,
374
+ ): string {
375
+ switch (item.kind) {
376
+ case "user-message":
377
+ return `${labels.user}: ${item.text}`;
378
+ case "human-input":
379
+ return `${labels.humanInputAsked}: ${nativeHumanInputSummary(item, labels)}`;
380
+ case "agent-message":
381
+ return `${labels.assistant}: ${item.text}`;
382
+ case "reasoning":
383
+ return `${labels.reasoning}: ${item.text}`;
384
+ case "tool-call":
385
+ return `${labels.tool} ${item.name}, ${nativeTimelineStatusLabel(item.status, labels)}`;
386
+ case "worker":
387
+ return `${labels.worker}, ${nativeTimelineStatusLabel(item.status, labels)}${item.prompt ? `: ${item.prompt}` : ""}`;
388
+ case "sandbox":
389
+ return `${labels.sandbox} ${item.name}, ${nativeTimelineStatusLabel(item.status, labels)}`;
390
+ case "startup-phase":
391
+ return `${labels.activity}, ${nativeTimelineStatusLabel(item.status, labels)}`;
392
+ case "session-status":
393
+ return nativeSessionStatusLabel(item.status, labels);
394
+ case "goal":
395
+ return `${nativeGoalActionLabel(item.action, labels)}${item.text ? `: ${item.text}` : ""}`;
396
+ case "notice":
397
+ return item.text;
398
+ case "context-compaction":
399
+ return nativeContextCompactionPhaseLabel(item.phase, labels);
400
+ case "machine-input-batch":
401
+ return formatLabel(labels.machineUpdates, { value: item.members.length });
402
+ case "auth-needed":
403
+ return formatLabel(labels.connectionRequired, { provider: item.providerDomain });
404
+ case "knowledge":
405
+ return `${nativeKnowledgeOutcomeLabel(item, labels)}${item.filename ? `: ${item.filename}` : ""}`;
406
+ case "memory":
407
+ return `${nativeMemoryActionLabel(item, labels)}: ${item.replacementPreview ?? item.preview}`;
408
+ case "fleet-decision":
409
+ return `${labels.fleetDecision}: ${nativeFleetDecisionSummary(item, labels)}`;
410
+ case "turn-end":
411
+ return `${labels.turn}, ${nativeTimelineStatusLabel(item.outcome, labels)}`;
412
+ case "worker-completion":
413
+ return item.text;
414
+ }
415
+ }
416
+
417
+ export function nativeSessionStatusLabel(
418
+ status: SessionStatus,
419
+ labels: OpenGeniNativeLabels,
420
+ ): string {
421
+ switch (status) {
422
+ case "queued":
423
+ return labels.statusQueued;
424
+ case "running":
425
+ return labels.statusRunning;
426
+ case "recovering":
427
+ return labels.statusRecovering;
428
+ case "waiting_capacity":
429
+ return labels.statusWaitingCapacity;
430
+ case "idle":
431
+ return labels.statusIdle;
432
+ case "requires_action":
433
+ return labels.statusRequiresAction;
434
+ case "failed":
435
+ return labels.statusFailed;
436
+ case "cancelled":
437
+ return labels.statusCancelled;
438
+ }
439
+ }
440
+
441
+ export function validateHumanInputAnswers(
442
+ questions: readonly HumanInputQuestion[],
443
+ drafts: Readonly<Record<string, NativeHumanInputDraft>>,
444
+ ): { answers: HumanInputAnswer[]; valid: boolean } {
445
+ const answers: HumanInputAnswer[] = [];
446
+ let valid = true;
447
+
448
+ for (const question of questions) {
449
+ const draft = drafts[question.id] ?? emptyHumanInputDraft();
450
+ const values =
451
+ question.kind === "text"
452
+ ? draft.values.filter((value) => value.trim().length > 0)
453
+ : draft.values;
454
+ const other = draft.otherSelected ? draft.other.trim() : "";
455
+ const supplied = values.length + (other.length > 0 ? 1 : 0);
456
+
457
+ if (question.kind !== "text" && draft.otherSelected && other.length === 0) valid = false;
458
+ if (question.required && supplied === 0) valid = false;
459
+ if (question.kind !== "text") {
460
+ const minimum = question.validation?.minSelections;
461
+ const maximum = question.kind === "single_select" ? 1 : question.validation?.maxSelections;
462
+ if (minimum != null && supplied < minimum) valid = false;
463
+ if (maximum != null && supplied > maximum) valid = false;
464
+ }
465
+ if (supplied > 0) {
466
+ answers.push({
467
+ questionId: question.id,
468
+ values,
469
+ ...(other ? { other } : {}),
470
+ });
471
+ }
472
+ }
473
+
474
+ return { answers, valid };
475
+ }
476
+
477
+ export interface NativeHumanInputDraft {
478
+ values: string[];
479
+ other: string;
480
+ otherSelected: boolean;
481
+ }
482
+
483
+ export function emptyHumanInputDraft(): NativeHumanInputDraft {
484
+ return { values: [], other: "", otherSelected: false };
485
+ }