@opengeni/react 3.8.0-canary.1 → 4.0.1-canary.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.
- package/README.md +10 -24
- package/dist/artifacts.js +7 -7
- package/dist/{browser-viewer-SS46LUAC.js → browser-viewer-NTD6UIPR.js} +3 -3
- package/dist/{chunk-VEPNNDNG.js → chunk-24M4YBCL.js} +4 -4
- package/dist/{chunk-ELIYWBZR.js → chunk-BV5A4OTE.js} +4 -4
- package/dist/{chunk-7W6PVVTK.js → chunk-CQG7YUJB.js} +2582 -1037
- package/dist/chunk-CQG7YUJB.js.map +1 -0
- package/dist/{chunk-BXJOQFCD.js → chunk-HUMIRRYI.js} +84 -221
- package/dist/chunk-HUMIRRYI.js.map +1 -0
- package/dist/{chunk-7BWFU6H4.js → chunk-Z7WBDB4E.js} +395 -287
- package/dist/chunk-Z7WBDB4E.js.map +1 -0
- package/dist/components/human-input-form.d.ts +3 -1
- package/dist/components/human-input-surface.d.ts +3 -2
- package/dist/components/markdown-table-layout.d.ts +9 -0
- package/dist/components/session-conversation.d.ts +2 -0
- package/dist/composer.js +2 -3
- package/dist/{computer-viewer-FWEZISW4.js → computer-viewer-7TEZC6Y2.js} +3 -3
- package/dist/hooks/use-codex-accounts.d.ts +11 -1
- package/dist/hooks/use-session-events.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +44 -34
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +3 -3
- package/dist/markdown-table-layout-3Z3AWBY7.js +71 -0
- package/dist/markdown-table-layout-3Z3AWBY7.js.map +1 -0
- package/dist/older-history.d.ts +3 -2
- package/dist/{platform-activity-row-BJHUHXAT.js → platform-activity-row-27LKAWZD.js} +2 -2
- package/dist/platform-activity-row-27LKAWZD.js.map +1 -0
- package/dist/realtime.js +3 -3
- package/dist/session-ui.js +6 -9
- package/dist/session.js +1 -1
- package/dist/timeline/types.d.ts +3 -0
- package/package.json +2 -6
- package/src/components/human-input-form.tsx +185 -53
- package/src/components/human-input-surface.tsx +3 -0
- package/src/components/markdown-table-layout.ts +85 -0
- package/src/components/markdown.tsx +20 -2
- package/src/components/message-timeline.tsx +47 -7
- package/src/components/session-commands-panel.tsx +7 -1
- package/src/components/session-conversation.tsx +4 -1
- package/src/hooks/use-codex-accounts.ts +32 -5
- package/src/hooks/use-session-events.ts +16 -277
- package/src/index.ts +0 -1
- package/src/older-history.ts +22 -4
- package/src/timeline/platform-activity-row.tsx +4 -1
- package/src/timeline/projection.ts +99 -9
- package/src/timeline/types.ts +3 -0
- package/dist/chat.d.ts +0 -37
- package/dist/chat.js +0 -315
- package/dist/chat.js.map +0 -1
- package/dist/chunk-7BWFU6H4.js.map +0 -1
- package/dist/chunk-7W6PVVTK.js.map +0 -1
- package/dist/chunk-BXJOQFCD.js.map +0 -1
- package/dist/chunk-HQV2I5HV.js +0 -124
- package/dist/chunk-HQV2I5HV.js.map +0 -1
- package/dist/chunk-YAIPFO5D.js +0 -1416
- package/dist/chunk-YAIPFO5D.js.map +0 -1
- package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
- package/src/chat.tsx +0 -393
- /package/dist/{browser-viewer-SS46LUAC.js.map → browser-viewer-NTD6UIPR.js.map} +0 -0
- /package/dist/{chunk-VEPNNDNG.js.map → chunk-24M4YBCL.js.map} +0 -0
- /package/dist/{chunk-ELIYWBZR.js.map → chunk-BV5A4OTE.js.map} +0 -0
- /package/dist/{computer-viewer-FWEZISW4.js.map → computer-viewer-7TEZC6Y2.js.map} +0 -0
|
@@ -102,34 +102,8 @@ const FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS = 16;
|
|
|
102
102
|
const FOREGROUND_COMPACT_CATCHUP_MAX_BYTES = 512 * 1024;
|
|
103
103
|
const EMPTY_EVENTS: SessionEvent[] = [];
|
|
104
104
|
const encoder = new TextEncoder();
|
|
105
|
-
const decoder = new TextDecoder();
|
|
106
|
-
|
|
107
|
-
const BROWSER_EVENT_TYPE_MAX_BYTES = 256;
|
|
108
|
-
const BROWSER_EVENT_ID_MAX_BYTES = 256;
|
|
109
|
-
const BROWSER_EVENT_CLIENT_ID_MAX_BYTES = 4 * 1024;
|
|
110
|
-
const BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES = 4 * 1024;
|
|
111
|
-
const BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES = 48 * 1024;
|
|
112
|
-
const BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS = [
|
|
113
|
-
"id",
|
|
114
|
-
"callId",
|
|
115
|
-
"call_id",
|
|
116
|
-
"name",
|
|
117
|
-
"toolName",
|
|
118
|
-
"status",
|
|
119
|
-
"code",
|
|
120
|
-
"isError",
|
|
121
|
-
"stream",
|
|
122
|
-
"commandId",
|
|
123
|
-
"sequence",
|
|
124
|
-
"coalescedUntil",
|
|
125
|
-
"coalescedCount",
|
|
126
|
-
"firstSequence",
|
|
127
|
-
"lastSequence",
|
|
128
|
-
] as const;
|
|
129
|
-
|
|
130
105
|
export const SESSION_EVENT_BROWSER_MAX_BYTES = 8 * 1024 * 1024;
|
|
131
106
|
export const SESSION_EVENT_BROWSER_MAX_COUNT = 10_000;
|
|
132
|
-
export const SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES = 96 * 1024;
|
|
133
107
|
export const SESSION_EVENT_BROWSER_PENDING_MAX_BYTES = 1024 * 1024;
|
|
134
108
|
export const SESSION_EVENT_BROWSER_PENDING_MAX_COUNT = 256;
|
|
135
109
|
|
|
@@ -482,7 +456,7 @@ export function useSessionEvents(
|
|
|
482
456
|
});
|
|
483
457
|
for await (const event of stream) {
|
|
484
458
|
if (!isCurrent()) break;
|
|
485
|
-
const boundedEvent =
|
|
459
|
+
const boundedEvent = event;
|
|
486
460
|
const boundedEventBytes = browserJsonBytes(boundedEvent);
|
|
487
461
|
const separatorBytes = pending.length === 0 ? 0 : 1;
|
|
488
462
|
if (
|
|
@@ -556,7 +530,7 @@ export function useSessionEvents(
|
|
|
556
530
|
|
|
557
531
|
const loadOlder = useCallback(
|
|
558
532
|
(): OlderHistoryLoadReceipt =>
|
|
559
|
-
createOlderHistoryLoadReceipt(async (markCommitted) => {
|
|
533
|
+
createOlderHistoryLoadReceipt(async (markCommitted, preserveTail, markTailPreserved) => {
|
|
560
534
|
if (!sessionId || navigationBusy() || !hasOlderRef.current) {
|
|
561
535
|
return false;
|
|
562
536
|
}
|
|
@@ -592,11 +566,17 @@ export function useSessionEvents(
|
|
|
592
566
|
// Freeze the live iterator before replacing its in-memory window. Rows
|
|
593
567
|
// pending in the aborted iterator were never cursor-committed and will
|
|
594
568
|
// be replayed from the retained high-water mark below.
|
|
595
|
-
streamAbortRef.current?.abort();
|
|
596
|
-
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
597
569
|
const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
|
|
598
570
|
direction: "oldest",
|
|
599
571
|
});
|
|
572
|
+
if (preserveTail && maxResumeSequence(next.events) < maxResumeSequence(current.events)) {
|
|
573
|
+
// Automatic viewport filling must not navigate away from the
|
|
574
|
+
// reader's retained tail. Explicit history navigation may do so.
|
|
575
|
+
markTailPreserved();
|
|
576
|
+
return false;
|
|
577
|
+
}
|
|
578
|
+
streamAbortRef.current?.abort();
|
|
579
|
+
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
600
580
|
const retained = {
|
|
601
581
|
...next,
|
|
602
582
|
truncated: current.truncated || next.truncated,
|
|
@@ -904,7 +884,8 @@ export function useSessionEvents(
|
|
|
904
884
|
* deliberately separate from durable history and transport paging: when a
|
|
905
885
|
* backward page evicts the live tail, the hook enters bounded history mode and
|
|
906
886
|
* preserves the highest-ever-observed sequence separately for forward paging.
|
|
907
|
-
* The source event remains durable in PostgreSQL throughout.
|
|
887
|
+
* The source event remains durable in PostgreSQL throughout. An event larger
|
|
888
|
+
* than the byte target is retained alone, never replaced by a lossy preview.
|
|
908
889
|
*/
|
|
909
890
|
export function boundBrowserSessionEventWindow(
|
|
910
891
|
events: readonly SessionEvent[],
|
|
@@ -916,7 +897,7 @@ export function boundBrowserSessionEventWindow(
|
|
|
916
897
|
): BrowserSessionEventWindow {
|
|
917
898
|
const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
|
|
918
899
|
const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
|
|
919
|
-
const safe = events
|
|
900
|
+
const safe = events;
|
|
920
901
|
const selected: SessionEvent[] = [];
|
|
921
902
|
let bytes = 2; // []
|
|
922
903
|
const direction = options.direction ?? "newest";
|
|
@@ -927,7 +908,7 @@ export function boundBrowserSessionEventWindow(
|
|
|
927
908
|
const event = safe[index]!;
|
|
928
909
|
const eventBytes = browserJsonBytes(event);
|
|
929
910
|
const separator = selected.length === 0 ? 0 : 1;
|
|
930
|
-
if (bytes + separator + eventBytes > maxBytes) break;
|
|
911
|
+
if (selected.length > 0 && bytes + separator + eventBytes > maxBytes) break;
|
|
931
912
|
selected.push(event);
|
|
932
913
|
bytes += separator + eventBytes;
|
|
933
914
|
}
|
|
@@ -939,248 +920,8 @@ export function boundBrowserSessionEventWindow(
|
|
|
939
920
|
};
|
|
940
921
|
}
|
|
941
922
|
|
|
942
|
-
function boundBrowserLegacyEvent(event: SessionEvent): SessionEvent {
|
|
943
|
-
// The server-side canonical projection lives in @opengeni/contracts. The
|
|
944
|
-
// publishable React package may depend only on the zero-runtime-dependency
|
|
945
|
-
// SDK, so this is intentionally a last-resort client guard rather than a
|
|
946
|
-
// second durable representation. Reconstructing the SDK wire shape prevents
|
|
947
|
-
// legacy/malformed extra properties from bypassing the browser byte cap.
|
|
948
|
-
const serialized = browserSerialize(event);
|
|
949
|
-
const originalBytes = serialized.serializable
|
|
950
|
-
? encoder.encode(serialized.value).byteLength
|
|
951
|
-
: null;
|
|
952
|
-
const typeIsSafe =
|
|
953
|
-
browserUtf8Bytes(event.type) <= BROWSER_EVENT_TYPE_MAX_BYTES &&
|
|
954
|
-
!event.type.includes("\n") &&
|
|
955
|
-
!event.type.includes("\r");
|
|
956
|
-
const clientEventId = boundBrowserOptionalText(
|
|
957
|
-
event.clientEventId,
|
|
958
|
-
BROWSER_EVENT_CLIENT_ID_MAX_BYTES,
|
|
959
|
-
);
|
|
960
|
-
const duplicateReason = boundBrowserOptionalText(
|
|
961
|
-
event.duplicateReason,
|
|
962
|
-
BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES,
|
|
963
|
-
);
|
|
964
|
-
|
|
965
|
-
if (
|
|
966
|
-
serialized.serializable &&
|
|
967
|
-
originalBytes !== null &&
|
|
968
|
-
originalBytes <= SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES &&
|
|
969
|
-
typeIsSafe &&
|
|
970
|
-
clientEventId === event.clientEventId &&
|
|
971
|
-
duplicateReason === event.duplicateReason
|
|
972
|
-
) {
|
|
973
|
-
return event;
|
|
974
|
-
}
|
|
975
|
-
|
|
976
|
-
const envelopeProjection = [
|
|
977
|
-
!typeIsSafe
|
|
978
|
-
? browserEnvelopeFieldProjection("type", event.type, "session.event.envelope_omitted")
|
|
979
|
-
: null,
|
|
980
|
-
clientEventId !== event.clientEventId
|
|
981
|
-
? browserEnvelopeFieldProjection("clientEventId", event.clientEventId, clientEventId)
|
|
982
|
-
: null,
|
|
983
|
-
duplicateReason !== event.duplicateReason
|
|
984
|
-
? browserEnvelopeFieldProjection("duplicateReason", event.duplicateReason, duplicateReason)
|
|
985
|
-
: null,
|
|
986
|
-
].filter((field) => field !== null);
|
|
987
|
-
const payloadSerialization = browserSerialize(event.payload);
|
|
988
|
-
const payloadBytes = payloadSerialization.serializable
|
|
989
|
-
? encoder.encode(payloadSerialization.value).byteLength
|
|
990
|
-
: null;
|
|
991
|
-
const preview = truncateBrowserUtf8Middle(
|
|
992
|
-
payloadSerialization.value,
|
|
993
|
-
BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES,
|
|
994
|
-
);
|
|
995
|
-
const truncation = {
|
|
996
|
-
truncated: true as const,
|
|
997
|
-
surface: "browser_legacy_guard" as const,
|
|
998
|
-
reason: serialized.serializable ? "event_envelope_bytes_exceeded" : "event_not_serializable",
|
|
999
|
-
originalBytes,
|
|
1000
|
-
deliveredBytes: 0,
|
|
1001
|
-
omittedBytes: originalBytes,
|
|
1002
|
-
estimatedOriginalTokens: originalBytes === null ? null : Math.ceil(originalBytes / 4),
|
|
1003
|
-
estimatedDeliveredTokens: 0,
|
|
1004
|
-
fullEvidence: { available: false as const, reason: "not_retained" as const },
|
|
1005
|
-
details: [
|
|
1006
|
-
{
|
|
1007
|
-
path: "$.payload",
|
|
1008
|
-
kind: "payload_preview",
|
|
1009
|
-
originalBytes: payloadBytes,
|
|
1010
|
-
deliveredBytes: browserUtf8Bytes(preview),
|
|
1011
|
-
},
|
|
1012
|
-
],
|
|
1013
|
-
};
|
|
1014
|
-
const payload: Record<string, unknown> = {
|
|
1015
|
-
...browserPayloadIdentity(event.payload),
|
|
1016
|
-
preview,
|
|
1017
|
-
...(envelopeProjection.length > 0
|
|
1018
|
-
? {
|
|
1019
|
-
originalType: boundBrowserText(event.type, BROWSER_EVENT_TYPE_MAX_BYTES),
|
|
1020
|
-
envelopeProjection: {
|
|
1021
|
-
truncated: true,
|
|
1022
|
-
surface: "browser_legacy_guard",
|
|
1023
|
-
fields: envelopeProjection,
|
|
1024
|
-
},
|
|
1025
|
-
}
|
|
1026
|
-
: {}),
|
|
1027
|
-
truncation,
|
|
1028
|
-
};
|
|
1029
|
-
const bounded: SessionEvent = {
|
|
1030
|
-
id: boundBrowserText(event.id, BROWSER_EVENT_ID_MAX_BYTES),
|
|
1031
|
-
workspaceId: boundBrowserText(event.workspaceId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
1032
|
-
sessionId: boundBrowserText(event.sessionId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
1033
|
-
sequence: event.sequence,
|
|
1034
|
-
type: typeIsSafe ? event.type : "session.event.envelope_omitted",
|
|
1035
|
-
payload,
|
|
1036
|
-
occurredAt: boundBrowserText(event.occurredAt, BROWSER_EVENT_ID_MAX_BYTES),
|
|
1037
|
-
clientEventId,
|
|
1038
|
-
turnId: boundBrowserOptionalText(event.turnId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
1039
|
-
turnGeneration: event.turnGeneration,
|
|
1040
|
-
turnAttemptId: boundBrowserOptionalText(event.turnAttemptId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
1041
|
-
turnAssociation: event.turnAssociation,
|
|
1042
|
-
duplicateOfEventId: boundBrowserOptionalText(
|
|
1043
|
-
event.duplicateOfEventId,
|
|
1044
|
-
BROWSER_EVENT_ID_MAX_BYTES,
|
|
1045
|
-
),
|
|
1046
|
-
duplicateReason,
|
|
1047
|
-
};
|
|
1048
|
-
|
|
1049
|
-
settleBrowserEventTruncation(bounded, truncation);
|
|
1050
|
-
if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
|
|
1051
|
-
payload.preview = truncateBrowserUtf8Middle(String(payload.preview), 4 * 1024);
|
|
1052
|
-
truncation.details = truncation.details.slice(0, 1);
|
|
1053
|
-
settleBrowserEventTruncation(bounded, truncation);
|
|
1054
|
-
}
|
|
1055
|
-
if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
|
|
1056
|
-
bounded.clientEventId = null;
|
|
1057
|
-
bounded.duplicateReason = null;
|
|
1058
|
-
payload.preview = "[legacy event omitted at the browser byte boundary]";
|
|
1059
|
-
settleBrowserEventTruncation(bounded, truncation);
|
|
1060
|
-
}
|
|
1061
|
-
return bounded;
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
923
|
function browserJsonBytes(value: unknown): number {
|
|
1065
|
-
return encoder.encode(
|
|
1066
|
-
}
|
|
1067
|
-
|
|
1068
|
-
function browserSerialize(value: unknown): { value: string; serializable: boolean } {
|
|
1069
|
-
try {
|
|
1070
|
-
const serialized = JSON.stringify(value);
|
|
1071
|
-
return { value: serialized === undefined ? "null" : serialized, serializable: true };
|
|
1072
|
-
} catch {
|
|
1073
|
-
return {
|
|
1074
|
-
value: '"[unserializable event payload omitted at browser boundary]"',
|
|
1075
|
-
serializable: false,
|
|
1076
|
-
};
|
|
1077
|
-
}
|
|
1078
|
-
}
|
|
1079
|
-
|
|
1080
|
-
function browserPayloadIdentity(payload: unknown): Record<string, unknown> {
|
|
1081
|
-
if (!payload || typeof payload !== "object" || Array.isArray(payload)) return {};
|
|
1082
|
-
const record = payload as Record<string, unknown>;
|
|
1083
|
-
const identity: Record<string, unknown> = {};
|
|
1084
|
-
for (const field of BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS) {
|
|
1085
|
-
const value = record[field];
|
|
1086
|
-
if (typeof value === "string") {
|
|
1087
|
-
identity[field] = boundBrowserText(value, BROWSER_EVENT_ID_MAX_BYTES);
|
|
1088
|
-
} else if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
1089
|
-
identity[field] = value;
|
|
1090
|
-
}
|
|
1091
|
-
}
|
|
1092
|
-
return identity;
|
|
1093
|
-
}
|
|
1094
|
-
|
|
1095
|
-
function browserEnvelopeFieldProjection(
|
|
1096
|
-
field: string,
|
|
1097
|
-
original: string | null | undefined,
|
|
1098
|
-
delivered: string | null | undefined,
|
|
1099
|
-
): { field: string; originalBytes: number; deliveredBytes: number } {
|
|
1100
|
-
return {
|
|
1101
|
-
field,
|
|
1102
|
-
originalBytes: typeof original === "string" ? browserUtf8Bytes(original) : 0,
|
|
1103
|
-
deliveredBytes: typeof delivered === "string" ? browserUtf8Bytes(delivered) : 0,
|
|
1104
|
-
};
|
|
1105
|
-
}
|
|
1106
|
-
|
|
1107
|
-
function boundBrowserOptionalText<T extends string | null | undefined>(
|
|
1108
|
-
value: T,
|
|
1109
|
-
maxBytes: number,
|
|
1110
|
-
): T {
|
|
1111
|
-
return (typeof value === "string" ? boundBrowserText(value, maxBytes) : value) as T;
|
|
1112
|
-
}
|
|
1113
|
-
|
|
1114
|
-
function boundBrowserText(value: string, maxBytes: number): string {
|
|
1115
|
-
const bytes = encoder.encode(value);
|
|
1116
|
-
if (bytes.byteLength <= maxBytes) return value;
|
|
1117
|
-
const marker = "…[truncated]";
|
|
1118
|
-
const prefixBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
|
|
1119
|
-
let prefixEnd = Math.min(prefixBudget, bytes.byteLength);
|
|
1120
|
-
while (
|
|
1121
|
-
prefixEnd > 0 &&
|
|
1122
|
-
prefixEnd < bytes.byteLength &&
|
|
1123
|
-
isBrowserUtf8Continuation(bytes[prefixEnd]!)
|
|
1124
|
-
) {
|
|
1125
|
-
prefixEnd -= 1;
|
|
1126
|
-
}
|
|
1127
|
-
return `${decoder.decode(bytes.subarray(0, prefixEnd))}${marker}`;
|
|
1128
|
-
}
|
|
1129
|
-
|
|
1130
|
-
function truncateBrowserUtf8Middle(value: string, maxBytes: number): string {
|
|
1131
|
-
const bytes = encoder.encode(value);
|
|
1132
|
-
if (bytes.byteLength <= maxBytes) return value;
|
|
1133
|
-
const marker = `…[${bytes.byteLength - maxBytes} bytes omitted]…`;
|
|
1134
|
-
const contentBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
|
|
1135
|
-
const leftBudget = Math.floor(contentBudget / 2);
|
|
1136
|
-
const rightBudget = contentBudget - leftBudget;
|
|
1137
|
-
let leftEnd = Math.min(leftBudget, bytes.byteLength);
|
|
1138
|
-
while (leftEnd > 0 && leftEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[leftEnd]!)) {
|
|
1139
|
-
leftEnd -= 1;
|
|
1140
|
-
}
|
|
1141
|
-
let rightStart = Math.max(0, bytes.byteLength - rightBudget);
|
|
1142
|
-
while (rightStart < bytes.byteLength && isBrowserUtf8Continuation(bytes[rightStart]!)) {
|
|
1143
|
-
rightStart += 1;
|
|
1144
|
-
}
|
|
1145
|
-
return `${decoder.decode(bytes.subarray(0, leftEnd))}${marker}${decoder.decode(bytes.subarray(rightStart))}`;
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
|
-
function settleBrowserEventTruncation(
|
|
1149
|
-
event: SessionEvent,
|
|
1150
|
-
truncation: {
|
|
1151
|
-
originalBytes: number | null;
|
|
1152
|
-
deliveredBytes: number;
|
|
1153
|
-
omittedBytes: number | null;
|
|
1154
|
-
estimatedDeliveredTokens: number;
|
|
1155
|
-
},
|
|
1156
|
-
): void {
|
|
1157
|
-
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
1158
|
-
const deliveredBytes = browserJsonBytes(event);
|
|
1159
|
-
const omittedBytes =
|
|
1160
|
-
truncation.originalBytes === null
|
|
1161
|
-
? null
|
|
1162
|
-
: Math.max(0, truncation.originalBytes - deliveredBytes);
|
|
1163
|
-
const estimatedDeliveredTokens = Math.ceil(deliveredBytes / 4);
|
|
1164
|
-
if (
|
|
1165
|
-
truncation.deliveredBytes === deliveredBytes &&
|
|
1166
|
-
truncation.omittedBytes === omittedBytes &&
|
|
1167
|
-
truncation.estimatedDeliveredTokens === estimatedDeliveredTokens
|
|
1168
|
-
) {
|
|
1169
|
-
return;
|
|
1170
|
-
}
|
|
1171
|
-
truncation.deliveredBytes = deliveredBytes;
|
|
1172
|
-
truncation.omittedBytes = omittedBytes;
|
|
1173
|
-
truncation.estimatedDeliveredTokens = estimatedDeliveredTokens;
|
|
1174
|
-
}
|
|
1175
|
-
throw new RangeError("Browser event byte accounting did not converge");
|
|
1176
|
-
}
|
|
1177
|
-
|
|
1178
|
-
function browserUtf8Bytes(value: string): number {
|
|
1179
|
-
return encoder.encode(value).byteLength;
|
|
1180
|
-
}
|
|
1181
|
-
|
|
1182
|
-
function isBrowserUtf8Continuation(value: number): boolean {
|
|
1183
|
-
return (value & 0xc0) === 0x80;
|
|
924
|
+
return encoder.encode(JSON.stringify(value)).byteLength;
|
|
1184
925
|
}
|
|
1185
926
|
|
|
1186
927
|
function observeSessionStatus(
|
|
@@ -1240,9 +981,7 @@ async function planForegroundCatchup(
|
|
|
1240
981
|
pageSize: rawGap,
|
|
1241
982
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1242
983
|
});
|
|
1243
|
-
const events = page
|
|
1244
|
-
.filter((event) => eventResumeSequence(event) > cursor)
|
|
1245
|
-
.map(boundBrowserLegacyEvent);
|
|
984
|
+
const events = page.filter((event) => eventResumeSequence(event) > cursor);
|
|
1246
985
|
if (events.length === 0) {
|
|
1247
986
|
return { kind: "reload" };
|
|
1248
987
|
}
|
package/src/index.ts
CHANGED
|
@@ -48,7 +48,6 @@ export {
|
|
|
48
48
|
SESSION_EVENT_BROWSER_MAX_COUNT,
|
|
49
49
|
SESSION_EVENT_BROWSER_PENDING_MAX_BYTES,
|
|
50
50
|
SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
|
|
51
|
-
SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
|
|
52
51
|
boundBrowserSessionEventWindow,
|
|
53
52
|
useSessionEvents,
|
|
54
53
|
} from "./hooks/use-session-events";
|
package/src/older-history.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
export type OlderHistoryLoadReceipt = Promise<boolean> & {
|
|
10
10
|
readonly committed: boolean;
|
|
11
|
+
readonly tailPreserved?: boolean;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
/**
|
|
@@ -22,23 +23,29 @@ export type OlderHistoryLoader = () => unknown;
|
|
|
22
23
|
|
|
23
24
|
type MutableOlderHistoryLoadReceipt = Promise<boolean> & {
|
|
24
25
|
committed: boolean;
|
|
26
|
+
tailPreserved: boolean;
|
|
25
27
|
};
|
|
26
28
|
|
|
27
29
|
type OlderHistoryReceiptCapture = (receipt: OlderHistoryLoadReceipt) => void;
|
|
28
30
|
|
|
29
31
|
let activeReceiptCapture: OlderHistoryReceiptCapture | undefined;
|
|
32
|
+
let preserveTailForAutomaticLoad = false;
|
|
30
33
|
|
|
31
34
|
/** Bind a receipt before a synchronous forwarding stack can publish state. */
|
|
32
35
|
export function invokeOlderHistoryLoaderWithReceiptCapture(
|
|
33
36
|
load: OlderHistoryLoader,
|
|
34
37
|
capture: OlderHistoryReceiptCapture,
|
|
38
|
+
preserveTail = false,
|
|
35
39
|
): unknown {
|
|
36
40
|
const previousCapture = activeReceiptCapture;
|
|
41
|
+
const previousPreserveTail = preserveTailForAutomaticLoad;
|
|
37
42
|
activeReceiptCapture = capture;
|
|
43
|
+
preserveTailForAutomaticLoad = preserveTail;
|
|
38
44
|
try {
|
|
39
45
|
return load();
|
|
40
46
|
} finally {
|
|
41
47
|
activeReceiptCapture = previousCapture;
|
|
48
|
+
preserveTailForAutomaticLoad = previousPreserveTail;
|
|
42
49
|
}
|
|
43
50
|
}
|
|
44
51
|
|
|
@@ -47,7 +54,11 @@ export function invokeOlderHistoryLoaderWithReceiptCapture(
|
|
|
47
54
|
* Call `markCommitted` immediately before publishing an accepted older window.
|
|
48
55
|
*/
|
|
49
56
|
export function createOlderHistoryLoadReceipt(
|
|
50
|
-
load: (
|
|
57
|
+
load: (
|
|
58
|
+
markCommitted: () => void,
|
|
59
|
+
preserveTail: boolean,
|
|
60
|
+
markTailPreserved: () => void,
|
|
61
|
+
) => boolean | Promise<boolean>,
|
|
51
62
|
): OlderHistoryLoadReceipt {
|
|
52
63
|
let resolveResult!: (value: boolean | PromiseLike<boolean>) => void;
|
|
53
64
|
let rejectResult!: (reason?: unknown) => void;
|
|
@@ -56,11 +67,18 @@ export function createOlderHistoryLoadReceipt(
|
|
|
56
67
|
rejectResult = reject;
|
|
57
68
|
}) as MutableOlderHistoryLoadReceipt;
|
|
58
69
|
result.committed = false;
|
|
70
|
+
result.tailPreserved = false;
|
|
59
71
|
activeReceiptCapture?.(result);
|
|
60
72
|
try {
|
|
61
|
-
const loaded = load(
|
|
62
|
-
|
|
63
|
-
|
|
73
|
+
const loaded = load(
|
|
74
|
+
() => {
|
|
75
|
+
result.committed = true;
|
|
76
|
+
},
|
|
77
|
+
preserveTailForAutomaticLoad,
|
|
78
|
+
() => {
|
|
79
|
+
result.tailPreserved = true;
|
|
80
|
+
},
|
|
81
|
+
);
|
|
64
82
|
Promise.resolve(loaded).then(resolveResult, rejectResult);
|
|
65
83
|
} catch (error) {
|
|
66
84
|
rejectResult(error);
|
|
@@ -65,7 +65,10 @@ export default function PlatformActivityRow({
|
|
|
65
65
|
return j(ActivityDisclosure, {
|
|
66
66
|
icon: j(BotIcon, { className: "size-3.5" }),
|
|
67
67
|
iconTone: failed ? "failed" : running ? "running" : "muted",
|
|
68
|
-
title:
|
|
68
|
+
title:
|
|
69
|
+
item.blockedReason === "rotation_in_progress"
|
|
70
|
+
? "Waiting for sandbox rotation"
|
|
71
|
+
: startupPhaseTitle(item.phase, item.status, item.outcome),
|
|
69
72
|
preview:
|
|
70
73
|
item.phase === "model_preparation"
|
|
71
74
|
? "Includes overlapping sandbox, rig, repository, and runtime setup shown below."
|
|
@@ -135,6 +135,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
135
135
|
const ordered = orderTimelineEvents(events, prescan);
|
|
136
136
|
const pendingWaitOutcomeByTurn = new Map<string | null, PendingWaitOutcome>();
|
|
137
137
|
const latestAgentResponseByTurn = new Map<string | null, TrackedAgentResponse>();
|
|
138
|
+
const identifiedMessages = new Map<string, AgentMessageItem>();
|
|
138
139
|
const humanInputRequests = humanInputRequestsById(events);
|
|
139
140
|
const humanInputToolCallIds = new Set(
|
|
140
141
|
[...humanInputRequests.values()]
|
|
@@ -157,6 +158,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
157
158
|
mode = 0,
|
|
158
159
|
explicitStartedAt?: string,
|
|
159
160
|
explicitId?: string,
|
|
161
|
+
blockedReason?: StartupPhaseItem["blockedReason"],
|
|
160
162
|
): void => {
|
|
161
163
|
const key = `${startupTurnId}:${phase}`;
|
|
162
164
|
const priorState = startupPhases.get(key);
|
|
@@ -183,6 +185,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
183
185
|
? elapsedDurationMs(open.startedAt, startupEvent.occurredAt)
|
|
184
186
|
: Math.max(0, duration);
|
|
185
187
|
open.outcome = outcome ?? open.outcome;
|
|
188
|
+
open.blockedReason = blockedReason;
|
|
186
189
|
return;
|
|
187
190
|
}
|
|
188
191
|
const running = status === "running";
|
|
@@ -201,6 +204,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
201
204
|
completedAt: running ? null : startupEvent.occurredAt,
|
|
202
205
|
durationMs,
|
|
203
206
|
outcome,
|
|
207
|
+
blockedReason,
|
|
204
208
|
occurredAt: startedAt,
|
|
205
209
|
} satisfies Partial<StartupPhaseItem>);
|
|
206
210
|
startupPhases.set(key, [prior, startupAttemptId, startupRecoveryRevision]);
|
|
@@ -216,6 +220,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
216
220
|
completedAt: running ? null : startupEvent.occurredAt,
|
|
217
221
|
durationMs,
|
|
218
222
|
outcome,
|
|
223
|
+
blockedReason,
|
|
219
224
|
occurredAt: startedAt,
|
|
220
225
|
};
|
|
221
226
|
items.push(item);
|
|
@@ -408,8 +413,43 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
408
413
|
if (!text) {
|
|
409
414
|
break;
|
|
410
415
|
}
|
|
416
|
+
const messageId = stringValue(payload.messageId);
|
|
417
|
+
const messageKey = messageId ? JSON.stringify([turnId, messageId]) : null;
|
|
418
|
+
const identified = messageKey ? identifiedMessages.get(messageKey) : undefined;
|
|
419
|
+
if (identified) {
|
|
420
|
+
if (identified.annotationSource?.eventType !== "agent.message.completed") {
|
|
421
|
+
identified.text += text;
|
|
422
|
+
rememberAgentResponse(turnId, identified, false);
|
|
423
|
+
}
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
411
426
|
const open = last();
|
|
412
|
-
if (open?.kind === "
|
|
427
|
+
if (!messageKey && open?.kind === "tool-call" && open.status === "running") {
|
|
428
|
+
const previous = latestAgentResponseByTurn.get(turnId);
|
|
429
|
+
const previousIndex = previous ? items.indexOf(previous.item) : -1;
|
|
430
|
+
if (
|
|
431
|
+
previous &&
|
|
432
|
+
!previous.completed &&
|
|
433
|
+
previousIndex >= 0 &&
|
|
434
|
+
items
|
|
435
|
+
.slice(previousIndex + 1)
|
|
436
|
+
.every(
|
|
437
|
+
(item) =>
|
|
438
|
+
item.kind === "tool-call" && item.turnId === turnId && item.status === "running",
|
|
439
|
+
)
|
|
440
|
+
) {
|
|
441
|
+
// Legacy deltas have no provider message identity. Tool creation
|
|
442
|
+
// alone is not a text boundary; completed output still is.
|
|
443
|
+
previous.item.text += text;
|
|
444
|
+
break;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
if (
|
|
448
|
+
!messageKey &&
|
|
449
|
+
open?.kind === "agent-message" &&
|
|
450
|
+
open.streaming &&
|
|
451
|
+
open.turnId === turnId
|
|
452
|
+
) {
|
|
413
453
|
open.text += text;
|
|
414
454
|
rememberAgentResponse(turnId, open, false);
|
|
415
455
|
break;
|
|
@@ -424,18 +464,21 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
424
464
|
occurredAt: event.occurredAt,
|
|
425
465
|
};
|
|
426
466
|
items.push(item);
|
|
467
|
+
if (messageKey) identifiedMessages.set(messageKey, item);
|
|
427
468
|
rememberAgentResponse(turnId, item, false);
|
|
428
469
|
break;
|
|
429
470
|
}
|
|
430
471
|
|
|
431
472
|
case "agent.message.completed": {
|
|
432
473
|
const text = stringValue(payload.text);
|
|
474
|
+
const messageId = stringValue(payload.messageId);
|
|
475
|
+
const messageKey = messageId ? JSON.stringify([turnId, messageId]) : null;
|
|
433
476
|
const phase = assistantMessagePhase(payload.phase);
|
|
434
477
|
// Reconcile the most recent same-turn agent message — even when
|
|
435
478
|
// activity (tool calls, reasoning) landed after its deltas — so the
|
|
436
479
|
// completed text never duplicates the streamed one.
|
|
437
|
-
let openIndex = -1;
|
|
438
|
-
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
480
|
+
let openIndex = messageKey ? items.indexOf(identifiedMessages.get(messageKey)!) : -1;
|
|
481
|
+
for (let index = messageKey ? -1 : items.length - 1; index >= 0; index -= 1) {
|
|
439
482
|
const candidate = items[index];
|
|
440
483
|
if (candidate?.kind === "agent-message" && candidate.turnId === turnId) {
|
|
441
484
|
openIndex = index;
|
|
@@ -447,10 +490,15 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
447
490
|
candidate?.kind === "agent-message" ? candidate : undefined;
|
|
448
491
|
if (
|
|
449
492
|
open &&
|
|
450
|
-
(
|
|
493
|
+
(messageKey ||
|
|
494
|
+
open.streaming ||
|
|
495
|
+
!open.text ||
|
|
496
|
+
text === open.text ||
|
|
497
|
+
text.startsWith(open.text))
|
|
451
498
|
) {
|
|
452
|
-
//
|
|
453
|
-
|
|
499
|
+
// Identity makes the final text authoritative even if it corrects the
|
|
500
|
+
// draft; legacy receipts still require an extension match.
|
|
501
|
+
if (!open.text || (text && (messageKey || text.startsWith(open.text)))) {
|
|
454
502
|
open.text = text || open.text;
|
|
455
503
|
}
|
|
456
504
|
open.streaming = false;
|
|
@@ -502,6 +550,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
502
550
|
},
|
|
503
551
|
};
|
|
504
552
|
items.push(item);
|
|
553
|
+
if (messageKey) identifiedMessages.set(messageKey, item);
|
|
505
554
|
rememberAgentResponse(turnId, item, true);
|
|
506
555
|
}
|
|
507
556
|
break;
|
|
@@ -693,7 +742,18 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
693
742
|
const startupPhase = startupPhaseForSandboxOperation(name);
|
|
694
743
|
if (startupPhase) {
|
|
695
744
|
closeStreamingTail();
|
|
696
|
-
settleStartupPhase(
|
|
745
|
+
settleStartupPhase(
|
|
746
|
+
startupPhase,
|
|
747
|
+
status,
|
|
748
|
+
numberOrNull(payload.durationMs),
|
|
749
|
+
origin,
|
|
750
|
+
0,
|
|
751
|
+
undefined,
|
|
752
|
+
undefined,
|
|
753
|
+
status === "cancelled" && payload.failureCode === "rotation_in_progress"
|
|
754
|
+
? "rotation_in_progress"
|
|
755
|
+
: undefined,
|
|
756
|
+
);
|
|
697
757
|
break;
|
|
698
758
|
}
|
|
699
759
|
// Routine per-turn platform plumbing that runs before EVERY turn to
|
|
@@ -1039,11 +1099,17 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
1039
1099
|
}
|
|
1040
1100
|
}
|
|
1041
1101
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
1042
|
-
|
|
1102
|
+
const completedTurn = turnEndItem(event, "complete", null);
|
|
1103
|
+
items.push(completedTurn);
|
|
1043
1104
|
const hasCompletedFinalResponse =
|
|
1044
1105
|
latestAgentResponse?.completed === true &&
|
|
1045
1106
|
latestAgentResponse.item.phase !== "commentary" &&
|
|
1046
1107
|
Boolean(visibleTrackedResponse);
|
|
1108
|
+
if (!hasAuthoritativeFinalOutput && pendingWaitOutcome) {
|
|
1109
|
+
// Preserve existing prose, including delta-only streams, when a
|
|
1110
|
+
// trailing wait yields without a terminal output receipt.
|
|
1111
|
+
completedTurn.preserveWaitResponse = true;
|
|
1112
|
+
}
|
|
1047
1113
|
if (!hasAuthoritativeFinalOutput && !hasCompletedFinalResponse && pendingWaitOutcome) {
|
|
1048
1114
|
items.push({
|
|
1049
1115
|
kind: "notice",
|
|
@@ -1808,7 +1874,31 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
|
|
|
1808
1874
|
return;
|
|
1809
1875
|
}
|
|
1810
1876
|
|
|
1811
|
-
|
|
1877
|
+
// Resolve only inside this turn's collected suffix. A user/machine-input
|
|
1878
|
+
// boundary or an interleaved foreign turn must never donate its answer.
|
|
1879
|
+
const waitMessages = turnEnd.preserveWaitResponse
|
|
1880
|
+
? collected.filter(
|
|
1881
|
+
(group): group is Extract<TimelineGroup, { kind: "item" }> =>
|
|
1882
|
+
group.kind === "item" &&
|
|
1883
|
+
group.item.kind === "agent-message" &&
|
|
1884
|
+
!group.item.streaming &&
|
|
1885
|
+
group.item.text.trim().length > 0 &&
|
|
1886
|
+
belongsToTurn(group.item, turnEnd.turnId),
|
|
1887
|
+
)
|
|
1888
|
+
: [];
|
|
1889
|
+
// A completed answer outranks later commentary. Otherwise use the latest
|
|
1890
|
+
// visible existing prose, ignoring whitespace and opaque-only stream tails.
|
|
1891
|
+
const waitResponse =
|
|
1892
|
+
waitMessages
|
|
1893
|
+
.slice()
|
|
1894
|
+
.reverse()
|
|
1895
|
+
.find(
|
|
1896
|
+
(group) =>
|
|
1897
|
+
group.item.kind === "agent-message" &&
|
|
1898
|
+
group.item.phase !== "commentary" &&
|
|
1899
|
+
group.item.annotationSource?.eventType === "agent.message.completed",
|
|
1900
|
+
) ?? waitMessages.at(-1);
|
|
1901
|
+
const finalMessage = waitResponse ?? extractFinalAgentMessage(collected, turnEnd);
|
|
1812
1902
|
const fallbackMessage =
|
|
1813
1903
|
finalMessage || hasOrdinaryFinalAgentMessage(collected, turnEnd)
|
|
1814
1904
|
? null
|
package/src/timeline/types.ts
CHANGED
|
@@ -215,6 +215,7 @@ export type StartupPhaseItem = {
|
|
|
215
215
|
durationMs: number | null;
|
|
216
216
|
/** Sandbox origin and rig marker outcomes refine the settled label only. */
|
|
217
217
|
outcome: "created" | "restored" | "resumed" | "skipped" | null;
|
|
218
|
+
blockedReason?: "rotation_in_progress" | undefined;
|
|
218
219
|
occurredAt: string;
|
|
219
220
|
};
|
|
220
221
|
|
|
@@ -451,6 +452,8 @@ export type TurnOutcome = "complete" | "failed" | "cancelled";
|
|
|
451
452
|
|
|
452
453
|
export type TurnEndItem = {
|
|
453
454
|
kind: "turn-end";
|
|
455
|
+
/** Keep an existing answer visible when a recorded input wait ends without final output. */
|
|
456
|
+
preserveWaitResponse?: true;
|
|
454
457
|
id: string;
|
|
455
458
|
turnId: string | null;
|
|
456
459
|
outcome: TurnOutcome;
|