@opengeni/react 0.29.0 → 0.32.1

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 (207) hide show
  1. package/dist/approvals.d.ts +14 -0
  2. package/dist/{chunk-M7X4JZOD.js → chunk-4QOQ2DSR.js} +8 -1
  3. package/dist/chunk-4QOQ2DSR.js.map +1 -0
  4. package/dist/{chunk-HQFPO7VX.js → chunk-7CJOAW3V.js} +24 -3
  5. package/dist/{chunk-HQFPO7VX.js.map → chunk-7CJOAW3V.js.map} +1 -1
  6. package/dist/{chunk-CSJBF3QJ.js → chunk-A2WITZTI.js} +377 -113
  7. package/dist/chunk-A2WITZTI.js.map +1 -0
  8. package/dist/{chunk-BC6RKIHS.js → chunk-GZ4KR67U.js} +11 -11
  9. package/dist/{chunk-AVLMIWYO.js → chunk-KZRUYCL4.js} +12 -6
  10. package/dist/chunk-KZRUYCL4.js.map +1 -0
  11. package/dist/{chunk-OKXCECLG.js → chunk-TCCE5ZU5.js} +171 -22
  12. package/dist/chunk-TCCE5ZU5.js.map +1 -0
  13. package/dist/chunk-U255M5EZ.js +178 -0
  14. package/dist/chunk-U255M5EZ.js.map +1 -0
  15. package/dist/{chunk-IPVOVOW6.js → chunk-WY2QN2CX.js} +2539 -2174
  16. package/dist/chunk-WY2QN2CX.js.map +1 -0
  17. package/dist/chunk-ZNNE2VES.js +6413 -0
  18. package/dist/chunk-ZNNE2VES.js.map +1 -0
  19. package/dist/client.d.ts +33 -0
  20. package/dist/commands/index.d.ts +3 -0
  21. package/dist/commands/registry.d.ts +41 -0
  22. package/dist/commands/types.d.ts +85 -0
  23. package/dist/components/approval-surface.d.ts +28 -0
  24. package/dist/components/chat-composer.d.ts +51 -0
  25. package/dist/components/code-editor.d.ts +43 -0
  26. package/dist/components/command-palette.d.ts +25 -0
  27. package/dist/components/composer-transcription-control.d.ts +36 -0
  28. package/dist/components/composer.d.ts +277 -0
  29. package/dist/components/copy-button.d.ts +26 -0
  30. package/dist/components/desktop-viewer.d.ts +85 -0
  31. package/dist/components/diff-view.d.ts +38 -0
  32. package/dist/components/enrollment-consent.d.ts +33 -0
  33. package/dist/components/enrollment-device-flow.d.ts +27 -0
  34. package/dist/components/file-browser.d.ts +47 -0
  35. package/dist/components/fleet-tile.d.ts +18 -0
  36. package/dist/components/human-input-form.d.ts +47 -0
  37. package/dist/components/machine-card.d.ts +21 -0
  38. package/dist/components/machine-dock-bar.d.ts +31 -0
  39. package/dist/components/machine-health-pill.d.ts +9 -0
  40. package/dist/components/machine-metrics.d.ts +15 -0
  41. package/dist/components/machine-status-pill.d.ts +54 -0
  42. package/dist/components/machines/health.d.ts +25 -0
  43. package/dist/components/machines/machine-detail.d.ts +14 -0
  44. package/dist/components/machines/metric-history-chart.d.ts +24 -0
  45. package/dist/components/machines/metric-sparkline.d.ts +9 -0
  46. package/dist/components/machines/series.d.ts +24 -0
  47. package/dist/components/machines-dashboard.d.ts +30 -0
  48. package/dist/components/markdown.d.ts +31 -0
  49. package/dist/components/message-timeline.d.ts +98 -0
  50. package/dist/components/model-picker.d.ts +19 -0
  51. package/dist/components/pierre-diff.d.ts +34 -0
  52. package/dist/components/pierre-file.d.ts +27 -0
  53. package/dist/components/queue-draft-policy.d.ts +7 -0
  54. package/dist/components/queue-surface-implementation.d.ts +20 -0
  55. package/dist/components/queue-surface-state.d.ts +12 -0
  56. package/dist/components/queue-surface.d.ts +12 -0
  57. package/dist/components/sandbox-files.d.ts +49 -0
  58. package/dist/components/sandbox-terminal.d.ts +123 -0
  59. package/dist/components/sandbox-workspace.d.ts +128 -0
  60. package/dist/components/session-chrome.d.ts +70 -0
  61. package/dist/components/session-status.d.ts +25 -0
  62. package/dist/components/soften-streaming-markdown.d.ts +13 -0
  63. package/dist/components/stream-reveal.d.ts +37 -0
  64. package/dist/components/tip-follow.d.ts +139 -0
  65. package/dist/components/tooltip.d.ts +16 -0
  66. package/dist/components/workbench-changes.d.ts +34 -0
  67. package/dist/components/workspace-dock.d.ts +35 -0
  68. package/dist/composer.d.ts +11 -7
  69. package/dist/composer.js +9 -6
  70. package/dist/hooks/internal.d.ts +46 -0
  71. package/dist/hooks/use-available-models.d.ts +37 -0
  72. package/dist/hooks/use-billing-usage.d.ts +26 -0
  73. package/dist/hooks/use-codex-accounts.d.ts +69 -0
  74. package/dist/{use-composer-ClHy1rkL.d.ts → hooks/use-composer.d.ts} +16 -24
  75. package/dist/hooks/use-desktop-stream.d.ts +43 -0
  76. package/dist/hooks/use-environments.d.ts +35 -0
  77. package/dist/{use-file-attachments-BmT8WtqO.d.ts → hooks/use-file-attachments.d.ts} +6 -9
  78. package/dist/hooks/use-goal.d.ts +46 -0
  79. package/dist/hooks/use-human-input.d.ts +26 -0
  80. package/dist/hooks/use-last-started-turn-policy.d.ts +27 -0
  81. package/dist/hooks/use-machine-chip.d.ts +54 -0
  82. package/dist/hooks/use-machines.d.ts +81 -0
  83. package/dist/hooks/use-packs.d.ts +27 -0
  84. package/dist/hooks/use-relay-frame-stream.d.ts +53 -0
  85. package/dist/hooks/use-rigs.d.ts +87 -0
  86. package/dist/hooks/use-sandbox-files.d.ts +108 -0
  87. package/dist/hooks/use-sandbox-git.d.ts +58 -0
  88. package/dist/hooks/use-sandbox-terminal.d.ts +62 -0
  89. package/dist/hooks/use-scheduled-tasks.d.ts +15 -0
  90. package/dist/hooks/use-session-capabilities.d.ts +83 -0
  91. package/dist/hooks/use-session-control.d.ts +22 -0
  92. package/dist/hooks/use-session-events.d.ts +92 -0
  93. package/dist/hooks/use-session-lineage.d.ts +17 -0
  94. package/dist/hooks/use-session-mcp-approval-policy.d.ts +23 -0
  95. package/dist/hooks/use-session.d.ts +23 -0
  96. package/dist/hooks/use-slash-commands.d.ts +66 -0
  97. package/dist/hooks/use-terminal-stream.d.ts +46 -0
  98. package/dist/hooks/use-transcription.d.ts +73 -0
  99. package/dist/hooks/use-turn-queue.d.ts +44 -0
  100. package/dist/hooks/use-voice-input.d.ts +25 -0
  101. package/dist/hooks/use-windowed-sections.d.ts +42 -0
  102. package/dist/hooks/use-workspace-capture.d.ts +49 -0
  103. package/dist/hooks/use-workspace-edit.d.ts +73 -0
  104. package/dist/hooks/use-workspace-sessions.d.ts +30 -0
  105. package/dist/hooks/use-workspaces.d.ts +22 -0
  106. package/dist/human-input.d.ts +18 -0
  107. package/dist/index.d.ts +144 -2119
  108. package/dist/index.js +421 -324
  109. package/dist/index.js.map +1 -1
  110. package/dist/lib/clipboard.d.ts +7 -0
  111. package/dist/lib/cn.d.ts +3 -0
  112. package/dist/lib/format.d.ts +48 -0
  113. package/dist/lib/git-patch.d.ts +8 -0
  114. package/dist/lib/motion-inspect.d.ts +5 -0
  115. package/dist/lib/motion.d.ts +14 -0
  116. package/dist/lib/noto-sans-arabic-loader.d.ts +1 -0
  117. package/dist/lib/noto-sans-jp-loader.generated.d.ts +1 -0
  118. package/dist/lib/relay-wire.d.ts +30 -0
  119. package/dist/lib/use-portal-token-style.d.ts +9 -0
  120. package/dist/lib/use-theme-type.d.ts +5 -0
  121. package/dist/lib/use-unicode-fonts.d.ts +12 -0
  122. package/dist/lib/xterm-renderer.d.ts +19 -0
  123. package/dist/lib/xterm-theme.d.ts +75 -0
  124. package/dist/machines.d.ts +30 -413
  125. package/dist/machines.js +3 -3
  126. package/dist/model-policy.d.ts +32 -0
  127. package/dist/model-policy.js +33 -0
  128. package/dist/model-policy.js.map +1 -0
  129. package/dist/provider.d.ts +16 -0
  130. package/dist/{queue-surface-implementation-UQ535OKQ.js → queue-surface-implementation-THPAKGMD.js} +2 -2
  131. package/dist/{queue-surface-implementation-UQ535OKQ.js.map → queue-surface-implementation-THPAKGMD.js.map} +1 -1
  132. package/dist/session-context.d.ts +82 -0
  133. package/dist/session-ui.d.ts +10 -7
  134. package/dist/session-ui.js +9 -5
  135. package/dist/session.d.ts +28 -6
  136. package/dist/session.js +11 -11
  137. package/dist/timeline/activity-rail.d.ts +19 -0
  138. package/dist/timeline/disclosure-context.d.ts +14 -0
  139. package/dist/timeline/entrance.d.ts +14 -0
  140. package/dist/timeline/fleet-decision-projection.d.ts +9 -0
  141. package/dist/timeline/fleet-decision-row.d.ts +11 -0
  142. package/dist/timeline/fold-memory.d.ts +15 -0
  143. package/dist/timeline/index.d.ts +18 -0
  144. package/dist/timeline/parsers.d.ts +96 -0
  145. package/dist/timeline/projection.d.ts +38 -0
  146. package/dist/timeline/registry.d.ts +40 -0
  147. package/dist/timeline/screenshot-lightbox.d.ts +26 -0
  148. package/dist/timeline/seen-activity-ids.d.ts +2 -0
  149. package/dist/timeline/shared.d.ts +125 -0
  150. package/dist/timeline/tool-diff.d.ts +8 -0
  151. package/dist/timeline/tool-display-name.d.ts +10 -0
  152. package/dist/timeline/tool-renderers.d.ts +5 -0
  153. package/dist/timeline/turn-summary.d.ts +116 -0
  154. package/dist/{use-turn-queue-D_1a8W_l.d.ts → timeline/types.d.ts} +41 -69
  155. package/dist/types/machines.d.ts +11 -0
  156. package/package.json +8 -4
  157. package/src/client.ts +1 -0
  158. package/src/components/composer-transcription-control.tsx +278 -130
  159. package/src/components/composer.tsx +20 -15
  160. package/src/components/copy-button.tsx +127 -0
  161. package/src/components/markdown.tsx +231 -32
  162. package/src/components/message-timeline.tsx +1304 -378
  163. package/src/components/model-picker.tsx +83 -43
  164. package/src/components/queue-surface-implementation.tsx +1 -1
  165. package/src/components/session-chrome.tsx +942 -0
  166. package/src/components/soften-streaming-markdown.ts +205 -0
  167. package/src/components/stream-reveal.ts +195 -0
  168. package/src/components/tip-follow.ts +459 -0
  169. package/src/components/tooltip.tsx +103 -0
  170. package/src/composer.ts +6 -0
  171. package/src/hooks/internal.ts +30 -4
  172. package/src/hooks/use-available-models.ts +51 -4
  173. package/src/hooks/use-composer.ts +23 -3
  174. package/src/hooks/use-last-started-turn-policy.ts +95 -0
  175. package/src/hooks/use-session-events.ts +359 -5
  176. package/src/hooks/use-voice-input.ts +232 -0
  177. package/src/index.ts +47 -1
  178. package/src/lib/clipboard.ts +58 -0
  179. package/src/lib/format.ts +18 -0
  180. package/src/lib/motion-inspect.ts +5 -0
  181. package/src/lib/motion.ts +80 -0
  182. package/src/model-policy.ts +210 -0
  183. package/src/session-ui.ts +7 -0
  184. package/src/timeline/activity-rail.tsx +46 -7
  185. package/src/timeline/entrance.tsx +10 -0
  186. package/src/timeline/fold-memory.ts +52 -0
  187. package/src/timeline/index.ts +9 -1
  188. package/src/timeline/projection.ts +226 -41
  189. package/src/timeline/seen-activity-ids.ts +19 -0
  190. package/src/timeline/shared.tsx +1 -1
  191. package/src/timeline/tool-renderers.tsx +75 -6
  192. package/src/timeline/turn-summary.tsx +387 -97
  193. package/src/timeline/types.ts +21 -0
  194. package/styles/index.css +99 -7
  195. package/styles/tokens.css +44 -3
  196. package/dist/chunk-AVLMIWYO.js.map +0 -1
  197. package/dist/chunk-CSJBF3QJ.js.map +0 -1
  198. package/dist/chunk-IPVOVOW6.js.map +0 -1
  199. package/dist/chunk-M7X4JZOD.js.map +0 -1
  200. package/dist/chunk-OKXCECLG.js.map +0 -1
  201. package/dist/chunk-ZMNPFZWL.js +0 -3918
  202. package/dist/chunk-ZMNPFZWL.js.map +0 -1
  203. package/dist/composer-BMzimGTO.d.ts +0 -528
  204. package/dist/session-DaONt-dY.d.ts +0 -264
  205. package/dist/session-context-CAjlaeZb.d.ts +0 -84
  206. package/dist/session-ui-B5_roA95.d.ts +0 -259
  207. /package/dist/{chunk-BC6RKIHS.js.map → chunk-GZ4KR67U.js.map} +0 -0
@@ -10,6 +10,7 @@ import type {
10
10
  AgentMessageItem,
11
11
  ActivityItem,
12
12
  AuthNeededItem,
13
+ ContextCompactionItem,
13
14
  GoalItem,
14
15
  MachineInputBatchItem,
15
16
  MemoryItem,
@@ -165,12 +166,17 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
165
166
  // Reconcile the most recent same-turn agent message — even when
166
167
  // activity (tool calls, reasoning) landed after its deltas — so the
167
168
  // completed text never duplicates the streamed one.
168
- const open = [...items]
169
- .reverse()
170
- .find(
171
- (item): item is AgentMessageItem =>
172
- item.kind === "agent-message" && item.turnId === turnId,
173
- );
169
+ let openIndex = -1;
170
+ for (let index = items.length - 1; index >= 0; index -= 1) {
171
+ const candidate = items[index];
172
+ if (candidate?.kind === "agent-message" && candidate.turnId === turnId) {
173
+ openIndex = index;
174
+ break;
175
+ }
176
+ }
177
+ const candidate = openIndex >= 0 ? items[openIndex] : undefined;
178
+ const open: AgentMessageItem | undefined =
179
+ candidate?.kind === "agent-message" ? candidate : undefined;
174
180
  if (
175
181
  open &&
176
182
  (open.streaming || !open.text || text === open.text || text.startsWith(open.text))
@@ -180,6 +186,15 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
180
186
  open.text = text || open.text;
181
187
  }
182
188
  open.streaming = false;
189
+ // The SDK can emit a hosted-tool item only after its provider-native
190
+ // operation has completed, even though answer deltas were already
191
+ // streamed. The completed message event is the durable ordering
192
+ // authority, so move the reconciled row after any intervening tool
193
+ // activity instead of leaving completed web searches below the answer.
194
+ if (openIndex < items.length - 1) {
195
+ items.splice(openIndex, 1);
196
+ items.push(open);
197
+ }
183
198
  break;
184
199
  }
185
200
  if (text) {
@@ -236,6 +251,26 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
236
251
  });
237
252
  break;
238
253
  }
254
+ // Live Responses `web_search_call` events and the later SDK
255
+ // `RunToolCallItem` share the same item id. Merge so mid-stream cards
256
+ // do not duplicate when the step finally materializes.
257
+ if (callId) {
258
+ const existing = [...items]
259
+ .reverse()
260
+ .find(
261
+ (item): item is ToolCallItem => item.kind === "tool-call" && item.callId === callId,
262
+ );
263
+ if (existing) {
264
+ if (args != null) {
265
+ existing.arguments = args;
266
+ }
267
+ if (payload.raw !== undefined) {
268
+ existing.raw = mergeToolCallRaw(existing.raw, payload.raw);
269
+ }
270
+ existing.status = providerNativeToolStatus(existing.raw);
271
+ break;
272
+ }
273
+ }
239
274
  items.push({
240
275
  kind: "tool-call",
241
276
  id: event.id,
@@ -247,7 +282,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
247
282
  // The provider-native item drives the per-tool renderers (apply_patch
248
283
  // operation, computer_call action, web_search providerData, …).
249
284
  raw: payload.raw,
250
- status: "running",
285
+ status: providerNativeToolStatus(payload.raw),
251
286
  occurredAt: event.occurredAt,
252
287
  });
253
288
  break;
@@ -383,7 +418,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
383
418
  if (previous?.status === status) {
384
419
  break;
385
420
  }
386
- items.push({ kind: "session-status", id: event.id, status, occurredAt: event.occurredAt });
421
+ items.push({
422
+ kind: "session-status",
423
+ id: event.id,
424
+ status,
425
+ occurredAt: event.occurredAt,
426
+ });
387
427
  break;
388
428
  }
389
429
 
@@ -399,24 +439,36 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
399
439
  break;
400
440
  }
401
441
 
442
+ case "session.context.compaction.requested":
443
+ case "session.context.compaction.started": {
444
+ closeStreamingTail();
445
+ settleOrPushContextCompaction(items, {
446
+ id: event.id,
447
+ turnId,
448
+ phase: "started",
449
+ trigger: compactionTrigger(payload),
450
+ estimatedTokensBefore: numberOrNull(payload.estimatedTokensBefore),
451
+ estimatedTokensAfter: null,
452
+ skipReason: null,
453
+ implementation:
454
+ typeof payload.implementation === "string" ? payload.implementation : null,
455
+ occurredAt: event.occurredAt,
456
+ });
457
+ break;
458
+ }
459
+
402
460
  case "session.context.compacted": {
403
461
  closeStreamingTail();
404
- const before =
405
- typeof payload.estimatedTokensBefore === "number"
406
- ? Math.round(payload.estimatedTokensBefore).toLocaleString("en-US")
407
- : null;
408
- const after =
409
- typeof payload.estimatedTokensAfter === "number"
410
- ? Math.round(payload.estimatedTokensAfter).toLocaleString("en-US")
411
- : null;
412
- items.push({
413
- kind: "notice",
462
+ settleOrPushContextCompaction(items, {
414
463
  id: event.id,
415
- tone: "waiting",
416
- text:
417
- before && after
418
- ? `Active conversation history compacted from approximately ${before} to ${after} tokens.`
419
- : "Context compacted so the turn could continue.",
464
+ turnId,
465
+ phase: "compacted",
466
+ trigger: compactionTrigger(payload),
467
+ estimatedTokensBefore: numberOrNull(payload.estimatedTokensBefore),
468
+ estimatedTokensAfter: numberOrNull(payload.estimatedTokensAfter),
469
+ skipReason: null,
470
+ implementation:
471
+ typeof payload.implementation === "string" ? payload.implementation : null,
420
472
  occurredAt: event.occurredAt,
421
473
  });
422
474
  break;
@@ -424,21 +476,16 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
424
476
 
425
477
  case "session.context.compaction.skipped": {
426
478
  closeStreamingTail();
427
- const reason = typeof payload.reason === "string" ? payload.reason : null;
428
- items.push({
429
- kind: "notice",
479
+ settleOrPushContextCompaction(items, {
430
480
  id: event.id,
431
- tone: reason === "summarization_failed" ? "failed" : "waiting",
432
- text:
433
- reason === "no_history"
434
- ? "Context compaction skipped because there is no active history to compact."
435
- : reason === "replacement_not_smaller"
436
- ? "Context compaction skipped because the generated checkpoint would not reduce the context."
437
- : reason === "replacement_unchanged"
438
- ? "Context compaction stopped because it reproduced the same checkpoint without making progress."
439
- : reason === "summarization_failed"
440
- ? "Context compaction failed without replacing the active conversation history. Request it again to retry."
441
- : "Context compaction was not needed.",
481
+ turnId,
482
+ phase: "skipped",
483
+ trigger: compactionTrigger(payload),
484
+ estimatedTokensBefore: numberOrNull(payload.estimatedTokensBefore),
485
+ estimatedTokensAfter: null,
486
+ skipReason: typeof payload.reason === "string" ? payload.reason : null,
487
+ implementation:
488
+ typeof payload.implementation === "string" ? payload.implementation : null,
442
489
  occurredAt: event.occurredAt,
443
490
  });
444
491
  break;
@@ -485,8 +532,8 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
485
532
  case "turn.completed": {
486
533
  // A standalone manual compaction uses the turn ledger for fencing and
487
534
  // recovery, but it is maintenance rather than a conversational turn.
488
- // The dedicated session.context.compacted notice is the complete UI
489
- // truth; adding a generic turn chip would falsely make it look like an
535
+ // The dedicated context-compaction landmark is the complete UI truth;
536
+ // adding a generic turn chip would falsely make it look like an
490
537
  // extra agent response.
491
538
  if (payload.maintenance === "context_compaction") {
492
539
  finalizeOpen(turnId);
@@ -607,9 +654,74 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
607
654
  }
608
655
  }
609
656
 
657
+ for (const item of items) {
658
+ if (item.kind === "agent-message") {
659
+ item.text = stripOpaqueCitationTokens(item.text);
660
+ }
661
+ }
610
662
  return items;
611
663
  }
612
664
 
665
+ function providerNativeToolStatus(rawValue: unknown): ToolCallItem["status"] {
666
+ const raw = asRecord(rawValue);
667
+ if (raw.type !== "hosted_tool_call") {
668
+ return "running";
669
+ }
670
+ switch (raw.status) {
671
+ case "completed":
672
+ return "complete";
673
+ case "failed":
674
+ case "incomplete":
675
+ return "failed";
676
+ case "cancelled":
677
+ return "cancelled";
678
+ default:
679
+ return "running";
680
+ }
681
+ }
682
+
683
+ /**
684
+ * Prefer newer status/fields, but keep earlier providerData.action when a
685
+ * progress-only Responses event arrives without the search query payload.
686
+ */
687
+ function mergeToolCallRaw(existingValue: unknown, nextValue: unknown): unknown {
688
+ const existing = asRecord(existingValue);
689
+ const next = asRecord(nextValue);
690
+ if (Object.keys(next).length === 0) {
691
+ return existingValue;
692
+ }
693
+ const existingProvider = asRecord(existing.providerData);
694
+ const nextProvider = asRecord(next.providerData);
695
+ const providerData = {
696
+ ...existingProvider,
697
+ ...nextProvider,
698
+ };
699
+ if (
700
+ existingProvider.action != null &&
701
+ (nextProvider.action == null ||
702
+ (typeof nextProvider.action === "object" &&
703
+ nextProvider.action !== null &&
704
+ Object.keys(nextProvider.action as object).length === 0))
705
+ ) {
706
+ providerData.action = existingProvider.action;
707
+ }
708
+ return {
709
+ ...existing,
710
+ ...next,
711
+ ...(Object.keys(providerData).length > 0 ? { providerData } : {}),
712
+ };
713
+ }
714
+
715
+ /**
716
+ * Codex subscription web search can return private citation handles without
717
+ * the URL annotation table that would make them resolvable. Keep the canonical
718
+ * model-history item untouched, but never expose those unusable handles in the
719
+ * human timeline. Ordinary markdown links and structured URL citations remain.
720
+ */
721
+ export function stripOpaqueCitationTokens(text: string): string {
722
+ return text.replace(/\s*cite(?:[^]+)+/gu, "");
723
+ }
724
+
613
725
  /** The turn-end payload shape, as `isCreditExhaustion` wants it. */
614
726
  function isCreditExhaustionPayload(payload: Record<string, unknown>): boolean {
615
727
  return isCreditExhaustion({
@@ -689,7 +801,11 @@ export function groupTimeline(items: TimelineItem[]): TimelineGroup[] {
689
801
  if (open?.kind === "activity" && open.outcome === undefined) {
690
802
  open.items.push(item);
691
803
  } else {
692
- groups.push({ kind: "activity", id: `activity-${item.id}`, items: [item] });
804
+ groups.push({
805
+ kind: "activity",
806
+ id: `activity-${item.id}`,
807
+ items: [item],
808
+ });
693
809
  }
694
810
  continue;
695
811
  }
@@ -776,7 +892,12 @@ function prescanTurnAnchors(events: SessionEvent[]): TurnAnchorPrescan {
776
892
  }
777
893
  }
778
894
 
779
- return { queuedTurnByTrigger, startSeqByTrigger, cancelledBeforeStartTriggers, startedTurnIds };
895
+ return {
896
+ queuedTurnByTrigger,
897
+ startSeqByTrigger,
898
+ cancelledBeforeStartTriggers,
899
+ startedTurnIds,
900
+ };
780
901
  }
781
902
 
782
903
  function orderTimelineEvents(events: SessionEvent[], prescan: TurnAnchorPrescan): SessionEvent[] {
@@ -963,6 +1084,13 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
963
1084
  }
964
1085
 
965
1086
  const firstOccurredAt = groupStartedAt(body[0]) ?? turnEnd.occurredAt;
1087
+ const prior = startIndex > 0 ? groups[startIndex - 1] : undefined;
1088
+ const contextCompactionCount =
1089
+ prior?.kind === "item" &&
1090
+ prior.item.kind === "context-compaction" &&
1091
+ prior.item.phase === "compacted"
1092
+ ? 1
1093
+ : 0;
966
1094
  const turnGroup: TimelineGroup = {
967
1095
  kind: "turn",
968
1096
  id: `turn-${turnEnd.turnId ?? turnEnd.id}`,
@@ -970,6 +1098,7 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
970
1098
  startedAt: firstOccurredAt,
971
1099
  endedAt: turnEnd.occurredAt,
972
1100
  groups: body,
1101
+ ...(contextCompactionCount > 0 ? { contextCompactionCount } : {}),
973
1102
  };
974
1103
  if (turnEnd.failureText) {
975
1104
  turnGroup.failureText = turnEnd.failureText;
@@ -987,6 +1116,7 @@ function isTurnBoundary(group: TimelineGroup | undefined): boolean {
987
1116
  group?.kind === "turn" ||
988
1117
  (group?.kind === "item" &&
989
1118
  (group.item.kind === "user-message" ||
1119
+ group.item.kind === "context-compaction" ||
990
1120
  (group.item.kind === "notice" && group.item.tone === "input")))
991
1121
  );
992
1122
  }
@@ -1053,6 +1183,61 @@ function stringValue(value: unknown): string {
1053
1183
  return typeof value === "string" ? value : "";
1054
1184
  }
1055
1185
 
1186
+ function numberOrNull(value: unknown): number | null {
1187
+ return typeof value === "number" && Number.isFinite(value) ? value : null;
1188
+ }
1189
+
1190
+ function compactionTrigger(payload: Record<string, unknown>): ContextCompactionItem["trigger"] {
1191
+ const trigger = payload.trigger;
1192
+ return trigger === "auto" ||
1193
+ trigger === "operator" ||
1194
+ trigger === "proactive" ||
1195
+ trigger === "overflow"
1196
+ ? trigger
1197
+ : null;
1198
+ }
1199
+
1200
+ /**
1201
+ * Keep one landmark per turn: a later started/compacted/skipped settles the
1202
+ * open started row instead of stacking notices.
1203
+ */
1204
+ function settleOrPushContextCompaction(
1205
+ items: TimelineItem[],
1206
+ next: Omit<ContextCompactionItem, "kind">,
1207
+ ): void {
1208
+ const openIndex = findOpenContextCompactionIndex(items, next.turnId);
1209
+ if (openIndex >= 0) {
1210
+ const open = items[openIndex];
1211
+ if (open?.kind === "context-compaction") {
1212
+ items[openIndex] = {
1213
+ ...open,
1214
+ ...next,
1215
+ // Prefer the settled event id so keys stay stable with the finish row.
1216
+ id: next.phase === "started" ? open.id : next.id,
1217
+ trigger: next.trigger ?? open.trigger,
1218
+ estimatedTokensBefore: next.estimatedTokensBefore ?? open.estimatedTokensBefore,
1219
+ implementation: next.implementation ?? open.implementation,
1220
+ };
1221
+ return;
1222
+ }
1223
+ }
1224
+ items.push({ kind: "context-compaction", ...next });
1225
+ }
1226
+
1227
+ function findOpenContextCompactionIndex(items: TimelineItem[], turnId: string | null): number {
1228
+ for (let index = items.length - 1; index >= 0; index -= 1) {
1229
+ const item = items[index];
1230
+ if (item?.kind !== "context-compaction" || item.phase !== "started") {
1231
+ continue;
1232
+ }
1233
+ if (turnId && item.turnId && item.turnId !== turnId) {
1234
+ continue;
1235
+ }
1236
+ return index;
1237
+ }
1238
+ return -1;
1239
+ }
1240
+
1056
1241
  function machineInputMembers(value: unknown): MachineInputBatchItem["members"] {
1057
1242
  const kinds = new Set<MachineInputBatchItem["members"][number]["kind"]>([
1058
1243
  "scheduled_occurrence",
@@ -0,0 +1,19 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ /* ----------------------------------------------------------------------------
4
+ Seen activity ids
5
+
6
+ ActivityRails remount across fold wraps / settle key flips. A per-rail
7
+ previousIds set resets on remount, so either every row re-fades (flash) or
8
+ the first live tool never animates (pop). This map lives on MessageTimeline
9
+ and records every activity id that has already painted — live appends of
10
+ unknown ids earn row-enter; remounts of known ids stay quiet.
11
+ -------------------------------------------------------------------------- */
12
+
13
+ const SeenActivityIdsContext = createContext<Set<string> | null>(null);
14
+
15
+ export const SeenActivityIdsProvider = SeenActivityIdsContext.Provider;
16
+
17
+ export function useSeenActivityIds(): Set<string> | null {
18
+ return useContext(SeenActivityIdsContext);
19
+ }
@@ -164,7 +164,7 @@ export function ActivityDisclosure({
164
164
  const inner = (
165
165
  <>
166
166
  {hasBody ? (
167
- <ChevronRightIcon className="size-3.5 shrink-0 text-og-fg-subtle transition-transform duration-150 ease-og-in-out group-data-[state=open]/disclosure:rotate-90" />
167
+ <ChevronRightIcon className="size-3.5 shrink-0 text-og-fg-subtle transition-transform duration-[var(--og-duration-disclose)] ease-og-in-out group-data-[state=open]/disclosure:rotate-90" />
168
168
  ) : (
169
169
  <span className="size-3.5 shrink-0" />
170
170
  )}
@@ -698,15 +698,84 @@ function ComputerCallRenderer({ item }: ToolRendererProps) {
698
698
 
699
699
  type WebSearchResult = { title: string; domain: string; snippet: string };
700
700
 
701
+ /** Pull a search string from tool-call arguments when providerData.action is sparse. */
702
+ function webSearchQueryFromArguments(args: unknown): string | null {
703
+ if (typeof args === "string") {
704
+ const trimmed = args.trim();
705
+ if (!trimmed) {
706
+ return null;
707
+ }
708
+ try {
709
+ return webSearchQueryFromArguments(JSON.parse(trimmed));
710
+ } catch {
711
+ return trimmed;
712
+ }
713
+ }
714
+ if (!args || typeof args !== "object") {
715
+ return null;
716
+ }
717
+ const record = args as Record<string, unknown>;
718
+ if (typeof record.query === "string" && record.query.trim().length > 0) {
719
+ return record.query;
720
+ }
721
+ if (Array.isArray(record.queries)) {
722
+ const first = record.queries.find(
723
+ (value): value is string => typeof value === "string" && value.trim().length > 0,
724
+ );
725
+ if (first) {
726
+ return first;
727
+ }
728
+ }
729
+ return null;
730
+ }
731
+
701
732
  function WebSearchRenderer({ item }: ToolRendererProps) {
702
733
  const raw = (item.raw ?? {}) as {
703
- providerData?: { action?: { query?: string; queries?: string[] } };
734
+ providerData?: {
735
+ action?: {
736
+ type?: string;
737
+ query?: string;
738
+ queries?: string[];
739
+ url?: string;
740
+ pattern?: string;
741
+ };
742
+ };
704
743
  };
705
744
  const action = raw.providerData?.action ?? {};
706
- const query = action.query ?? "(query unavailable)";
707
- const queries = action.queries ?? [];
708
- const variants = queries.length > 1 ? ` +${queries.length - 1} variants` : "";
745
+ const actionType = action.type ?? "search";
746
+ // Responses API deprecated singular `query` in favor of `queries[]`.
747
+ // Codex/current OpenAI often only populate the array.
748
+ const queries = (action.queries ?? []).filter(
749
+ (value): value is string => typeof value === "string" && value.trim().length > 0,
750
+ );
751
+ const searchQuery =
752
+ (typeof action.query === "string" && action.query.trim().length > 0 ? action.query : null) ??
753
+ queries[0] ??
754
+ webSearchQueryFromArguments(item.arguments);
709
755
  const running = item.status === "running";
756
+ const query =
757
+ actionType === "open_page"
758
+ ? (action.url ?? "(page unavailable)")
759
+ : actionType === "find_in_page"
760
+ ? action.pattern && action.url
761
+ ? `"${action.pattern}" in ${action.url}`
762
+ : (action.pattern ?? action.url ?? "(page unavailable)")
763
+ : // Codex often emits the live card before action.query/queries land;
764
+ // don't flash the scary unavailable copy while still searching.
765
+ (searchQuery ?? (running ? "…" : "(query unavailable)"));
766
+ const variants = queries.length > 1 ? ` +${queries.length - 1} variants` : "";
767
+ const runningTitle =
768
+ actionType === "open_page"
769
+ ? "Opening web page"
770
+ : actionType === "find_in_page"
771
+ ? "Searching within page"
772
+ : "Searching the web";
773
+ const completedTitle =
774
+ actionType === "open_page"
775
+ ? "Opened web page"
776
+ : actionType === "find_in_page"
777
+ ? "Searched within page"
778
+ : "Searched the web";
710
779
  // web_search may surface a results array on the output when the host enriches it.
711
780
  // Filter out null/undefined/non-object entries before casting: host-provided
712
781
  // data is untrusted and a null element would throw on result.title access.
@@ -727,7 +796,7 @@ function WebSearchRenderer({ item }: ToolRendererProps) {
727
796
  <ActivityDisclosure
728
797
  icon={<SearchIcon className={ICON_SIZE} />}
729
798
  iconTone="running"
730
- title="Searching the web"
799
+ title={runningTitle}
731
800
  running
732
801
  preview={<RunningPreview>{`${query}${variants}`}</RunningPreview>}
733
802
  >
@@ -740,7 +809,7 @@ function WebSearchRenderer({ item }: ToolRendererProps) {
740
809
  <ActivityDisclosure
741
810
  icon={<SearchIcon className={ICON_SIZE} />}
742
811
  iconTone="muted"
743
- title="Searched the web"
812
+ title={completedTitle}
744
813
  preview={`${query}${variants}`}
745
814
  failed={item.status === "failed"}
746
815
  cancelled={item.status === "cancelled"}