@opengeni/react 3.8.0-canary.0 → 4.0.1-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/README.md +11 -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-5M6VTFJ5.js +171 -0
- package/dist/chunk-5M6VTFJ5.js.map +1 -0
- package/dist/{chunk-IOLVN74D.js → chunk-6J56T3MG.js} +15 -1
- package/dist/chunk-6J56T3MG.js.map +1 -0
- package/dist/{chunk-ELIYWBZR.js → chunk-BV5A4OTE.js} +4 -4
- package/dist/{chunk-3HIF5IFC.js → chunk-HO6X4FHG.js} +2597 -1030
- package/dist/chunk-HO6X4FHG.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-ZFOFY5ST.js → chunk-NLTS7JHI.js} +16 -31
- package/dist/chunk-NLTS7JHI.js.map +1 -0
- package/dist/chunk-NOSYBS2D.js +208 -0
- package/dist/chunk-NOSYBS2D.js.map +1 -0
- package/dist/chunk-YBM6CQRU.js +292 -0
- package/dist/chunk-YBM6CQRU.js.map +1 -0
- package/dist/{chunk-BQGIXRKV.js → chunk-Z7WBDB4E.js} +416 -290
- package/dist/chunk-Z7WBDB4E.js.map +1 -0
- package/dist/chunk-ZYRGEKWS.js +216 -0
- package/dist/chunk-ZYRGEKWS.js.map +1 -0
- package/dist/components/composer.d.ts +4 -2
- package/dist/components/human-input-form.d.ts +3 -1
- package/dist/components/human-input-surface.d.ts +3 -2
- package/dist/components/message-timeline.d.ts +9 -2
- package/dist/components/session-conversation.d.ts +2 -0
- package/dist/components/timeline-annotation-chrome.d.ts +25 -0
- package/dist/components/timeline-annotation-layout.d.ts +42 -0
- package/dist/components/timeline-annotation-markers.d.ts +8 -0
- package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
- package/dist/components/timeline-annotation-shared.d.ts +31 -0
- package/dist/components/timeline-annotations-dialog.d.ts +1 -1
- package/dist/components/timeline-annotations.d.ts +8 -2
- package/dist/composer.js +3 -4
- 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-composer.d.ts +2 -0
- package/dist/hooks/use-session-events.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +50 -37
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +3 -3
- package/dist/machines.js +1 -1
- 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/{queue-surface-implementation-XL2A6PBM.js → queue-surface-implementation-LK3S4BIW.js} +5 -2
- package/dist/{queue-surface-implementation-XL2A6PBM.js.map → queue-surface-implementation-LK3S4BIW.js.map} +1 -1
- package/dist/realtime.js +3 -3
- package/dist/session-ui.js +11 -11
- package/dist/session.js +2 -2
- package/dist/timeline/types.d.ts +3 -0
- package/dist/timeline-annotation-markers-DRSTLEM6.js +165 -0
- package/dist/timeline-annotation-markers-DRSTLEM6.js.map +1 -0
- package/dist/timeline-annotation-selection-IHLUGVND.js +236 -0
- package/dist/timeline-annotation-selection-IHLUGVND.js.map +1 -0
- package/dist/timeline-annotations-dialog-6Q53N5FG.js +262 -0
- package/dist/timeline-annotations-dialog-6Q53N5FG.js.map +1 -0
- package/package.json +2 -6
- package/src/components/chat-composer.tsx +5 -2
- package/src/components/composer.tsx +40 -5
- package/src/components/copy-button.tsx +1 -0
- package/src/components/human-input-form.tsx +185 -53
- package/src/components/human-input-surface.tsx +3 -0
- package/src/components/machines-dashboard.tsx +1 -16
- package/src/components/message-timeline.tsx +397 -302
- package/src/components/session-commands-panel.tsx +7 -1
- package/src/components/session-conversation.tsx +4 -1
- package/src/components/timeline-annotation-chrome.tsx +183 -0
- package/src/components/timeline-annotation-layout.ts +268 -0
- package/src/components/timeline-annotation-markers.tsx +185 -0
- package/src/components/timeline-annotation-reveal-context.ts +9 -0
- package/src/components/timeline-annotation-selection.tsx +99 -54
- package/src/components/timeline-annotation-shared.ts +245 -0
- package/src/components/timeline-annotations-dialog.tsx +172 -128
- package/src/components/timeline-annotations.tsx +178 -45
- package/src/hooks/use-codex-accounts.ts +32 -5
- package/src/hooks/use-composer.ts +17 -0
- 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/styles/compiled.css +86 -22
- package/dist/chat.d.ts +0 -37
- package/dist/chat.js +0 -315
- package/dist/chat.js.map +0 -1
- package/dist/chunk-3HIF5IFC.js.map +0 -1
- package/dist/chunk-BQGIXRKV.js.map +0 -1
- package/dist/chunk-BXJOQFCD.js.map +0 -1
- package/dist/chunk-F2CFHN3Y.js +0 -1415
- package/dist/chunk-F2CFHN3Y.js.map +0 -1
- package/dist/chunk-HQV2I5HV.js +0 -124
- package/dist/chunk-HQV2I5HV.js.map +0 -1
- package/dist/chunk-IOLVN74D.js.map +0 -1
- package/dist/chunk-W3OUAH6M.js +0 -170
- package/dist/chunk-W3OUAH6M.js.map +0 -1
- package/dist/chunk-ZFOFY5ST.js.map +0 -1
- package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
- package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
- package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
- package/dist/timeline-annotations-dialog-U7EVLR75.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
|
@@ -15,7 +15,16 @@ import {
|
|
|
15
15
|
|
|
16
16
|
/** Events that change which Codex account a session runs on (or just ran). */
|
|
17
17
|
export function isCodexAccountEvent(event: Pick<SessionEvent, "type">): boolean {
|
|
18
|
-
return
|
|
18
|
+
return [
|
|
19
|
+
"codex.account.switched",
|
|
20
|
+
"codex.account.selection.changed",
|
|
21
|
+
"codex.capacity.waiting",
|
|
22
|
+
"codex.capacity.resumed",
|
|
23
|
+
"codex.capacity.superseded",
|
|
24
|
+
"turn.completed",
|
|
25
|
+
"turn.failed",
|
|
26
|
+
"turn.cancelled",
|
|
27
|
+
].includes(event.type);
|
|
19
28
|
}
|
|
20
29
|
|
|
21
30
|
/**
|
|
@@ -30,12 +39,16 @@ export type CodexAccountsClientLike = {
|
|
|
30
39
|
getSession?: (
|
|
31
40
|
workspaceId: string,
|
|
32
41
|
sessionId: string,
|
|
33
|
-
) => Promise<{
|
|
42
|
+
) => Promise<{
|
|
43
|
+
codexPinnedCredentialId?: string | null;
|
|
44
|
+
codexLastCredentialId?: string | null;
|
|
45
|
+
codexCurrentSelection?: { credentialId: string | null; waiting: boolean } | null;
|
|
46
|
+
}>;
|
|
34
47
|
pinSessionCodexAccount?: (
|
|
35
48
|
workspaceId: string,
|
|
36
49
|
sessionId: string,
|
|
37
50
|
target: string,
|
|
38
|
-
) => Promise<{ pinned: string }>;
|
|
51
|
+
) => Promise<{ pinned: string; appliedTo?: "waiting_turn" | "next_turn" }>;
|
|
39
52
|
/** Optional (absent ⇒ the card hides live refresh): batched live /wham/usage refresh. */
|
|
40
53
|
refreshCodexUsage?: (workspaceId: string) => Promise<{ usage: Record<string, unknown> }>;
|
|
41
54
|
};
|
|
@@ -55,8 +68,10 @@ export type UseCodexAccountsResult = {
|
|
|
55
68
|
activeAccountId: string | null;
|
|
56
69
|
/** The session's PINNED account (null ⇒ following workspace active). */
|
|
57
70
|
pinnedAccountId: string | null;
|
|
58
|
-
/**
|
|
71
|
+
/** Current preference only; automatic allocation can choose another account. */
|
|
59
72
|
effectiveAccountId: string | null;
|
|
73
|
+
currentSelection: { credentialId: string | null; waiting: boolean } | null;
|
|
74
|
+
switchAppliedTo: "waiting_turn" | "next_turn" | null;
|
|
60
75
|
/** The account the session's last turn ACTUALLY ran on (the "Running on:" source). */
|
|
61
76
|
lastAccountId: string | null;
|
|
62
77
|
settings: CodexRotationSettings;
|
|
@@ -85,6 +100,7 @@ const EMPTY_SETTINGS: CodexRotationSettings = {
|
|
|
85
100
|
};
|
|
86
101
|
|
|
87
102
|
type CodexAccountsState = {
|
|
103
|
+
currentSelection: { credentialId: string | null; waiting: boolean } | null;
|
|
88
104
|
accounts: CodexAccount[];
|
|
89
105
|
activeAccountId: string | null;
|
|
90
106
|
settings: CodexRotationSettings;
|
|
@@ -93,6 +109,7 @@ type CodexAccountsState = {
|
|
|
93
109
|
};
|
|
94
110
|
|
|
95
111
|
const EMPTY_STATE: CodexAccountsState = {
|
|
112
|
+
currentSelection: null,
|
|
96
113
|
accounts: [],
|
|
97
114
|
activeAccountId: null,
|
|
98
115
|
settings: EMPTY_SETTINGS,
|
|
@@ -121,6 +138,7 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
|
|
|
121
138
|
: Promise.resolve(null);
|
|
122
139
|
const [acc, session] = await Promise.all([accountsP, sessionP]);
|
|
123
140
|
return {
|
|
141
|
+
currentSelection: session?.codexCurrentSelection ?? null,
|
|
124
142
|
accounts: acc.accounts,
|
|
125
143
|
activeAccountId: acc.activeAccountId,
|
|
126
144
|
settings: acc.settings,
|
|
@@ -140,6 +158,10 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
|
|
|
140
158
|
const { run: runMutation, mutating: pinning, mutationError } = useMutationRunner();
|
|
141
159
|
const { run: runUsageMutation, mutating: refreshingUsage } = useMutationRunner();
|
|
142
160
|
const [pinningTarget, setPinningTarget] = useState<string | null>(null);
|
|
161
|
+
const [switchReceipt, setSwitchReceipt] = useState<{
|
|
162
|
+
sessionId: string;
|
|
163
|
+
appliedTo: "waiting_turn" | "next_turn";
|
|
164
|
+
} | null>(null);
|
|
143
165
|
|
|
144
166
|
// Refresh only after the durable post-selection event. `turn.started` is
|
|
145
167
|
// emitted before account selection settles and races this authoritative read.
|
|
@@ -161,8 +183,10 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
|
|
|
161
183
|
return false;
|
|
162
184
|
}
|
|
163
185
|
setPinningTarget(target);
|
|
186
|
+
setSwitchReceipt(null);
|
|
164
187
|
const result = await runMutation(async () => {
|
|
165
|
-
await codexClient.pinSessionCodexAccount!(workspaceId, sessionId, target);
|
|
188
|
+
const receipt = await codexClient.pinSessionCodexAccount!(workspaceId, sessionId, target);
|
|
189
|
+
setSwitchReceipt({ sessionId, appliedTo: receipt.appliedTo ?? "next_turn" });
|
|
166
190
|
return true;
|
|
167
191
|
});
|
|
168
192
|
setPinningTarget(null);
|
|
@@ -191,6 +215,9 @@ export function useCodexAccounts(options: UseCodexAccountsOptions = {}): UseCode
|
|
|
191
215
|
const effectiveAccountId = data.pinnedAccountId ?? data.activeAccountId;
|
|
192
216
|
|
|
193
217
|
return {
|
|
218
|
+
currentSelection: data.currentSelection,
|
|
219
|
+
switchAppliedTo:
|
|
220
|
+
switchReceipt?.sessionId === sessionId ? (switchReceipt?.appliedTo ?? null) : null,
|
|
194
221
|
accounts: data.accounts,
|
|
195
222
|
activeAccountId: data.activeAccountId,
|
|
196
223
|
pinnedAccountId: data.pinnedAccountId,
|
|
@@ -424,6 +424,8 @@ export type ComposerState = {
|
|
|
424
424
|
/** Newly captured annotation the review surface should focus. */
|
|
425
425
|
annotationReviewTargetId?: string | null | undefined;
|
|
426
426
|
clearAnnotationReviewTarget?: (() => void) | undefined;
|
|
427
|
+
/** Focus an annotation, or the first incomplete note when omitted. */
|
|
428
|
+
requestAnnotationReview?: ((id?: string) => void) | undefined;
|
|
427
429
|
/** Read the current draft synchronously before a destructive replacement. */
|
|
428
430
|
hasDraftContent: () => boolean;
|
|
429
431
|
/** Append the draft behind prompts already visible in the queue. */
|
|
@@ -2458,6 +2460,20 @@ export function useComposer(
|
|
|
2458
2460
|
setAnnotationReviewTargetId(null);
|
|
2459
2461
|
}, []);
|
|
2460
2462
|
|
|
2463
|
+
const requestAnnotationReview = useCallback((id?: string) => {
|
|
2464
|
+
const current = annotationsRef.current;
|
|
2465
|
+
if (current.length === 0) {
|
|
2466
|
+
setAnnotationReviewTargetId(null);
|
|
2467
|
+
return;
|
|
2468
|
+
}
|
|
2469
|
+
if (id && current.some((annotation) => annotation.id === id)) {
|
|
2470
|
+
setAnnotationReviewTargetId(id);
|
|
2471
|
+
return;
|
|
2472
|
+
}
|
|
2473
|
+
const incomplete = current.find((annotation) => annotation.note.trim().length === 0);
|
|
2474
|
+
setAnnotationReviewTargetId(incomplete?.id ?? current[0]!.id);
|
|
2475
|
+
}, []);
|
|
2476
|
+
|
|
2461
2477
|
const updatePolicy = useCallback(
|
|
2462
2478
|
(next: ComposerPolicy): void => {
|
|
2463
2479
|
if (targetKeyRef.current !== targetKey) return;
|
|
@@ -2643,6 +2659,7 @@ export function useComposer(
|
|
|
2643
2659
|
removeAnnotation,
|
|
2644
2660
|
annotationReviewTargetId: identityMatches ? annotationReviewTargetId : null,
|
|
2645
2661
|
clearAnnotationReviewTarget,
|
|
2662
|
+
requestAnnotationReview,
|
|
2646
2663
|
hasDraftContent,
|
|
2647
2664
|
send,
|
|
2648
2665
|
optimisticMessages: visibleOptimisticMessages,
|
|
@@ -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."
|