@opengeni/react 2.2.0-canary.0 → 2.3.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5RZN3DD5.js → chunk-LS4POUTY.js} +45 -8
- package/dist/chunk-LS4POUTY.js.map +1 -0
- package/dist/{chunk-XSA2QAKP.js → chunk-RSXJN73N.js} +32 -3
- package/dist/chunk-RSXJN73N.js.map +1 -0
- package/dist/{chunk-UOLJWT5Y.js → chunk-SE4W4WQ3.js} +2 -2
- package/dist/index.js +3 -3
- package/dist/session-ui.js +2 -2
- package/dist/session.js +2 -2
- package/dist/timeline/types.d.ts +2 -0
- package/package.json +2 -2
- package/src/components/human-input-form.tsx +14 -6
- package/src/components/message-timeline.tsx +53 -1
- package/src/timeline/projection.ts +53 -2
- package/src/timeline/types.ts +2 -0
- package/dist/chunk-5RZN3DD5.js.map +0 -1
- package/dist/chunk-XSA2QAKP.js.map +0 -1
- /package/dist/{chunk-UOLJWT5Y.js.map → chunk-SE4W4WQ3.js.map} +0 -0
|
@@ -371,6 +371,11 @@ export function MessageTimeline({
|
|
|
371
371
|
// leaves that band (scrolls down / sentinel exits) — never from scrolling
|
|
372
372
|
// further toward y=0 (that was the batch-top load loop).
|
|
373
373
|
const olderLoadGateRef = useRef<"armed" | "cooling">("armed");
|
|
374
|
+
// A collapsed history tail can be shorter than the viewport. In that state
|
|
375
|
+
// there is no upward scroll range, so reader intent can never arm the top
|
|
376
|
+
// sentinel. Request one older page per loaded window until history either
|
|
377
|
+
// fills the viewport or the host reports that no older rows remain.
|
|
378
|
+
const underfillLoadWindowRef = useRef<string | null>(null);
|
|
374
379
|
const resizeFollowRafRef = useRef<number | null>(null);
|
|
375
380
|
const firstGroupKey = allGroups[0] ? timelineGroupKey(allGroups[0]) : null;
|
|
376
381
|
// Content stays invisible until the tip is hard-parked across a short
|
|
@@ -447,6 +452,8 @@ export function MessageTimeline({
|
|
|
447
452
|
const groupKeyByItemIdRef = useRef<Map<string, string>>(new Map());
|
|
448
453
|
const groupOffsetByKeyRef = useRef<Map<string, number>>(new Map());
|
|
449
454
|
const firstItemId = resolvedItems[0]?.id ?? null;
|
|
455
|
+
const lastItemId = resolvedItems.at(-1)?.id ?? null;
|
|
456
|
+
const loadedWindowKey = JSON.stringify([firstItemId, lastItemId, resolvedItems.length]);
|
|
450
457
|
// Bulk paints (the initial tail window, a prepended older window — detected
|
|
451
458
|
// by the first group key changing) must not run per-row entrance animations.
|
|
452
459
|
const firstKeyChangedForBulk =
|
|
@@ -722,6 +729,39 @@ export function MessageTimeline({
|
|
|
722
729
|
[beginUserMessageDisclosureChange],
|
|
723
730
|
);
|
|
724
731
|
|
|
732
|
+
const requestOlderIfUnderfilled = useCallback(
|
|
733
|
+
(node: HTMLElement) => {
|
|
734
|
+
if (
|
|
735
|
+
underfillLoadWindowRef.current !== null &&
|
|
736
|
+
underfillLoadWindowRef.current !== loadedWindowKey
|
|
737
|
+
) {
|
|
738
|
+
// The requested page landed. Ordinary sentinel prefetch may own the
|
|
739
|
+
// next approach to the top once this window has a usable scroll range.
|
|
740
|
+
olderLoadGateRef.current = "armed";
|
|
741
|
+
}
|
|
742
|
+
// clientHeight=0 is pre-layout/headless, not evidence that the rendered
|
|
743
|
+
// history underfills a real viewport.
|
|
744
|
+
if (
|
|
745
|
+
node.clientHeight <= 1 ||
|
|
746
|
+
maxScrollOf(node) > 1 ||
|
|
747
|
+
!hasOlder ||
|
|
748
|
+
loadingOlder ||
|
|
749
|
+
!onLoadOlder ||
|
|
750
|
+
underfillLoadWindowRef.current === loadedWindowKey
|
|
751
|
+
) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
underfillLoadWindowRef.current = loadedWindowKey;
|
|
755
|
+
// If the reader had already armed ordinary prefetch, do not let its
|
|
756
|
+
// sentinel duplicate this request while the same short page is loading.
|
|
757
|
+
if (olderPrefetchArmedRef.current) {
|
|
758
|
+
olderLoadGateRef.current = "cooling";
|
|
759
|
+
}
|
|
760
|
+
onLoadOlder();
|
|
761
|
+
},
|
|
762
|
+
[hasOlder, loadedWindowKey, loadingOlder, onLoadOlder],
|
|
763
|
+
);
|
|
764
|
+
|
|
725
765
|
const driveFollowRef = useRef<(node: HTMLElement, now?: number) => void>(() => undefined);
|
|
726
766
|
const driveFollow = useCallback(
|
|
727
767
|
(node: HTMLElement, nowMs?: number) => {
|
|
@@ -1018,10 +1058,21 @@ export function MessageTimeline({
|
|
|
1018
1058
|
foldMemoryRef.current.clear();
|
|
1019
1059
|
userMessageDisclosureMemoryRef.current.clear();
|
|
1020
1060
|
disclosureKeepsUnpinnedRef.current = false;
|
|
1061
|
+
underfillLoadWindowRef.current = null;
|
|
1021
1062
|
seenActivityIdsRef.current.clear();
|
|
1022
1063
|
applyPinned(true);
|
|
1023
1064
|
}, [allGroups.length, revealed, applyPinned]);
|
|
1024
1065
|
|
|
1066
|
+
// Parent commits cover the initial/history-loading cases. Disclosure state
|
|
1067
|
+
// changes are child-local, so the ResizeObserver below owns dynamic collapse
|
|
1068
|
+
// and expansion after mount.
|
|
1069
|
+
useEffect(() => {
|
|
1070
|
+
const node = scrollRef.current;
|
|
1071
|
+
if (node) {
|
|
1072
|
+
requestOlderIfUnderfilled(node);
|
|
1073
|
+
}
|
|
1074
|
+
});
|
|
1075
|
+
|
|
1025
1076
|
// Clear the bulk-paint marker a frame after it renders, so rows appended
|
|
1026
1077
|
// live (streams, new turns) animate exactly as before.
|
|
1027
1078
|
useLayoutEffect(() => {
|
|
@@ -1119,6 +1170,7 @@ export function MessageTimeline({
|
|
|
1119
1170
|
if (!current) {
|
|
1120
1171
|
return;
|
|
1121
1172
|
}
|
|
1173
|
+
requestOlderIfUnderfilled(current);
|
|
1122
1174
|
if (!autoFollow || !pinnedRef.current || hasNewerRef.current) {
|
|
1123
1175
|
return;
|
|
1124
1176
|
}
|
|
@@ -1141,7 +1193,7 @@ export function MessageTimeline({
|
|
|
1141
1193
|
resizeFollowRafRef.current = null;
|
|
1142
1194
|
}
|
|
1143
1195
|
};
|
|
1144
|
-
}, [autoFollow, driveFollow, snapToBottom]);
|
|
1196
|
+
}, [autoFollow, driveFollow, requestOlderIfUnderfilled, snapToBottom]);
|
|
1145
1197
|
|
|
1146
1198
|
// Entering a non-tip history window: drop any live pin so the page bottom
|
|
1147
1199
|
// cannot re-stick follow across loadNewer. Leaving it (the tip window
|
|
@@ -397,6 +397,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
397
397
|
|
|
398
398
|
case "agent.message.completed": {
|
|
399
399
|
const text = stringValue(payload.text);
|
|
400
|
+
const phase = assistantMessagePhase(payload.phase);
|
|
400
401
|
// Reconcile the most recent same-turn agent message — even when
|
|
401
402
|
// activity (tool calls, reasoning) landed after its deltas — so the
|
|
402
403
|
// completed text never duplicates the streamed one.
|
|
@@ -420,6 +421,9 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
420
421
|
open.text = text || open.text;
|
|
421
422
|
}
|
|
422
423
|
open.streaming = false;
|
|
424
|
+
// A phase-less settlement mirror (the worker's final-output receipt)
|
|
425
|
+
// must not erase the provider-declared phase captured by the SDK item.
|
|
426
|
+
if (phase) open.phase = phase;
|
|
423
427
|
// Completion time is what the footer shows ("finished at"); keep the
|
|
424
428
|
// first-delta stamp only until this event arrives.
|
|
425
429
|
open.occurredAt = event.occurredAt;
|
|
@@ -451,6 +455,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
451
455
|
id: event.id,
|
|
452
456
|
turnId,
|
|
453
457
|
text,
|
|
458
|
+
...(phase ? { phase } : {}),
|
|
454
459
|
streaming: false,
|
|
455
460
|
occurredAt: event.occurredAt,
|
|
456
461
|
annotationSource: {
|
|
@@ -1675,7 +1680,12 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
|
|
|
1675
1680
|
}
|
|
1676
1681
|
|
|
1677
1682
|
const finalMessage = extractFinalAgentMessage(collected, turnEnd);
|
|
1678
|
-
const
|
|
1683
|
+
const fallbackMessage =
|
|
1684
|
+
finalMessage || hasOrdinaryFinalAgentMessage(collected, turnEnd)
|
|
1685
|
+
? null
|
|
1686
|
+
: extractLatestCompletedCommentary(collected, turnEnd);
|
|
1687
|
+
const visibleMessage = finalMessage ?? fallbackMessage;
|
|
1688
|
+
const body = visibleMessage ? collected.filter((group) => group !== visibleMessage) : collected;
|
|
1679
1689
|
if (body.length === 0) {
|
|
1680
1690
|
return;
|
|
1681
1691
|
}
|
|
@@ -1704,7 +1714,7 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
|
|
|
1704
1714
|
groups.splice(
|
|
1705
1715
|
startIndex,
|
|
1706
1716
|
collected.length,
|
|
1707
|
-
...(
|
|
1717
|
+
...(visibleMessage ? [turnGroup, visibleMessage] : [turnGroup]),
|
|
1708
1718
|
);
|
|
1709
1719
|
}
|
|
1710
1720
|
|
|
@@ -1761,6 +1771,43 @@ function extractFinalAgentMessage(
|
|
|
1761
1771
|
return tail;
|
|
1762
1772
|
}
|
|
1763
1773
|
|
|
1774
|
+
function hasOrdinaryFinalAgentMessage(groups: TimelineGroup[], turnEnd: TurnEndItem): boolean {
|
|
1775
|
+
return groups.some((group) => {
|
|
1776
|
+
if (group.kind !== "item" || group.item.kind !== "agent-message") return false;
|
|
1777
|
+
const message = group.item;
|
|
1778
|
+
return (
|
|
1779
|
+
!message.streaming &&
|
|
1780
|
+
message.phase !== "commentary" &&
|
|
1781
|
+
message.text.trim().length > 0 &&
|
|
1782
|
+
belongsToTurn(message, turnEnd.turnId)
|
|
1783
|
+
);
|
|
1784
|
+
});
|
|
1785
|
+
}
|
|
1786
|
+
|
|
1787
|
+
function extractLatestCompletedCommentary(
|
|
1788
|
+
groups: TimelineGroup[],
|
|
1789
|
+
turnEnd: TurnEndItem,
|
|
1790
|
+
): Extract<TimelineGroup, { kind: "item" }> | null {
|
|
1791
|
+
for (let index = groups.length - 1; index >= 0; index -= 1) {
|
|
1792
|
+
const group = groups[index];
|
|
1793
|
+
if (group?.kind !== "item" || group.item.kind !== "agent-message") continue;
|
|
1794
|
+
const message = group.item;
|
|
1795
|
+
if (
|
|
1796
|
+
!message.streaming &&
|
|
1797
|
+
message.phase === "commentary" &&
|
|
1798
|
+
message.text.trim().length > 0 &&
|
|
1799
|
+
belongsToTurn(message, turnEnd.turnId)
|
|
1800
|
+
) {
|
|
1801
|
+
return group;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
return null;
|
|
1805
|
+
}
|
|
1806
|
+
|
|
1807
|
+
function belongsToTurn(message: AgentMessageItem, turnId: string | null): boolean {
|
|
1808
|
+
return !message.turnId || !turnId || message.turnId === turnId;
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1764
1811
|
function groupStartedAt(group: TimelineGroup | undefined): string | undefined {
|
|
1765
1812
|
if (!group) {
|
|
1766
1813
|
return undefined;
|
|
@@ -1783,6 +1830,10 @@ function stringValue(value: unknown): string {
|
|
|
1783
1830
|
return typeof value === "string" ? value : "";
|
|
1784
1831
|
}
|
|
1785
1832
|
|
|
1833
|
+
function assistantMessagePhase(value: unknown): AgentMessageItem["phase"] {
|
|
1834
|
+
return value === "commentary" || value === "final_answer" ? value : undefined;
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1786
1837
|
function numberOrNull(value: unknown): number | null {
|
|
1787
1838
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1788
1839
|
}
|
package/src/timeline/types.ts
CHANGED
|
@@ -88,6 +88,8 @@ export type AgentMessageItem = {
|
|
|
88
88
|
id: string;
|
|
89
89
|
turnId: string | null;
|
|
90
90
|
text: string;
|
|
91
|
+
/** Provider-declared assistant channel. Absent on legacy and non-Responses events. */
|
|
92
|
+
phase?: "commentary" | "final_answer" | undefined;
|
|
91
93
|
/** Still receiving deltas (no completed/turn-end seen yet). */
|
|
92
94
|
streaming: boolean;
|
|
93
95
|
occurredAt: string;
|