@opengeni/react 3.1.0 → 3.1.3-canary.38890816741001
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 +769 -22
- package/dist/accounts.js +89 -11
- package/dist/accounts.js.map +1 -1
- package/dist/approvals.d.ts +2 -1
- package/dist/artifacts-document.js +5 -3
- package/dist/artifacts-presentation.js +5 -3
- package/dist/artifacts-spreadsheet.d.ts +1 -1
- package/dist/artifacts-spreadsheet.js +5 -3
- package/dist/artifacts.d.ts +5 -1
- package/dist/artifacts.js +536 -42
- package/dist/artifacts.js.map +1 -1
- package/dist/browser-viewer-ZWR6MWJ7.js +19 -0
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/capability-logo-fallback.d.ts +3 -0
- package/dist/chat-interactive-block-BO2IWXVK.js +14 -0
- package/dist/chunk-2DDJ6YH6.js +41 -0
- package/dist/chunk-2DDJ6YH6.js.map +1 -0
- package/dist/chunk-2HVLQ26H.js +183 -0
- package/dist/chunk-2HVLQ26H.js.map +1 -0
- package/dist/{chunk-RGALCTWO.js → chunk-2I6R3GCZ.js} +4285 -3980
- package/dist/chunk-2I6R3GCZ.js.map +1 -0
- package/dist/{chunk-D3UIAJKY.js → chunk-2SFFYIH3.js} +1179 -166
- package/dist/chunk-2SFFYIH3.js.map +1 -0
- package/dist/chunk-3PVDYTTB.js +24 -0
- package/dist/chunk-3PVDYTTB.js.map +1 -0
- package/dist/chunk-3UZCWK46.js +298 -0
- package/dist/chunk-3UZCWK46.js.map +1 -0
- package/dist/chunk-47CKUXTW.js +293 -0
- package/dist/chunk-47CKUXTW.js.map +1 -0
- package/dist/chunk-4QYP6QUI.js +109 -0
- package/dist/chunk-4QYP6QUI.js.map +1 -0
- package/dist/chunk-5EY26VZ7.js +1053 -0
- package/dist/chunk-5EY26VZ7.js.map +1 -0
- package/dist/chunk-5I4VRWBB.js +56 -0
- package/dist/chunk-5I4VRWBB.js.map +1 -0
- package/dist/chunk-5WOR7UCL.js +191 -0
- package/dist/chunk-5WOR7UCL.js.map +1 -0
- package/dist/chunk-5YJLGOM5.js +51 -0
- package/dist/chunk-5YJLGOM5.js.map +1 -0
- package/dist/chunk-6JFN3OWG.js +386 -0
- package/dist/chunk-6JFN3OWG.js.map +1 -0
- package/dist/chunk-7QJT5O4S.js +35 -0
- package/dist/chunk-7QJT5O4S.js.map +1 -0
- package/dist/chunk-A55KV72M.js +402 -0
- package/dist/chunk-A55KV72M.js.map +1 -0
- package/dist/chunk-ADWCLI4G.js +2028 -0
- package/dist/chunk-ADWCLI4G.js.map +1 -0
- package/dist/chunk-B27E3V37.js +29 -0
- package/dist/chunk-B27E3V37.js.map +1 -0
- package/dist/chunk-B7TC2A33.js +83 -0
- package/dist/chunk-B7TC2A33.js.map +1 -0
- package/dist/chunk-BLL56F7R.js +385 -0
- package/dist/chunk-BLL56F7R.js.map +1 -0
- package/dist/chunk-BOFEQC6K.js +162 -0
- package/dist/chunk-BOFEQC6K.js.map +1 -0
- package/dist/chunk-CM7LVUKC.js +35 -0
- package/dist/chunk-CM7LVUKC.js.map +1 -0
- package/dist/chunk-CORUP6JB.js +122 -0
- package/dist/chunk-CORUP6JB.js.map +1 -0
- package/dist/chunk-CSX5FFKZ.js +35 -0
- package/dist/chunk-CSX5FFKZ.js.map +1 -0
- package/dist/chunk-EGVHJKGJ.js +3125 -0
- package/dist/chunk-EGVHJKGJ.js.map +1 -0
- package/dist/{chunk-NEXLLTP4.js → chunk-G76LSA76.js} +6 -5
- package/dist/chunk-G76LSA76.js.map +1 -0
- package/dist/{chunk-PEI6RANI.js → chunk-GQMRL52F.js} +311 -75
- package/dist/chunk-GQMRL52F.js.map +1 -0
- package/dist/{chunk-ELIYWBZR.js → chunk-GUUXOISA.js} +1113 -457
- package/dist/chunk-GUUXOISA.js.map +1 -0
- package/dist/{chunk-HUIKIDM5.js → chunk-I7X2P4FU.js} +225 -116
- package/dist/chunk-I7X2P4FU.js.map +1 -0
- package/dist/chunk-IGFZFLI2.js +1390 -0
- package/dist/chunk-IGFZFLI2.js.map +1 -0
- package/dist/{chunk-4MDLEHXL.js → chunk-IMSUVYQB.js} +26 -13
- package/dist/chunk-IMSUVYQB.js.map +1 -0
- package/dist/chunk-IOYHDEP3.js +38 -0
- package/dist/chunk-IOYHDEP3.js.map +1 -0
- package/dist/chunk-IZV5FTJC.js +55 -0
- package/dist/chunk-IZV5FTJC.js.map +1 -0
- package/dist/chunk-KF33GTWN.js +343 -0
- package/dist/chunk-KF33GTWN.js.map +1 -0
- package/dist/chunk-KSGQ5ODA.js +457 -0
- package/dist/chunk-KSGQ5ODA.js.map +1 -0
- package/dist/{chunk-OAM4WAAM.js → chunk-KZC6QRNR.js} +1513 -697
- package/dist/chunk-KZC6QRNR.js.map +1 -0
- package/dist/chunk-LJUMYUBO.js +218 -0
- package/dist/chunk-LJUMYUBO.js.map +1 -0
- package/dist/chunk-LSW2OJVV.js +380 -0
- package/dist/chunk-LSW2OJVV.js.map +1 -0
- package/dist/chunk-M357MHH3.js +2 -0
- package/dist/chunk-M357MHH3.js.map +1 -0
- package/dist/chunk-MK2TQO4Y.js +24 -0
- package/dist/chunk-MK2TQO4Y.js.map +1 -0
- package/dist/chunk-N7S72EXK.js +25 -0
- package/dist/chunk-N7S72EXK.js.map +1 -0
- package/dist/{chunk-XXWH2JLC.js → chunk-NKMYZFHA.js} +616 -621
- package/dist/chunk-NKMYZFHA.js.map +1 -0
- package/dist/chunk-NX4ADH73.js +14 -0
- package/dist/chunk-NX4ADH73.js.map +1 -0
- package/dist/chunk-O7F6UJO4.js +71 -0
- package/dist/chunk-O7F6UJO4.js.map +1 -0
- package/dist/chunk-OVB5WIG5.js +14064 -0
- package/dist/chunk-OVB5WIG5.js.map +1 -0
- package/dist/chunk-OYQQJL3J.js +16 -0
- package/dist/chunk-OYQQJL3J.js.map +1 -0
- package/dist/{chunk-H5YGYNXS.js → chunk-P4BNUO57.js} +60 -17
- package/dist/chunk-P4BNUO57.js.map +1 -0
- package/dist/{chunk-JVYQRRC4.js → chunk-PEHTX3V7.js} +21 -1
- package/dist/chunk-PEHTX3V7.js.map +1 -0
- package/dist/chunk-PMV53IMM.js +26 -0
- package/dist/chunk-PMV53IMM.js.map +1 -0
- package/dist/{chunk-KCGXPP5V.js → chunk-PYEOPA4M.js} +13 -7
- package/dist/chunk-PYEOPA4M.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-QHUE7E4N.js} +8 -3
- package/dist/chunk-QHUE7E4N.js.map +1 -0
- package/dist/chunk-QV3DJVRZ.js +11 -0
- package/dist/chunk-QV3DJVRZ.js.map +1 -0
- package/dist/chunk-R5SBNG7S.js +810 -0
- package/dist/chunk-R5SBNG7S.js.map +1 -0
- package/dist/chunk-RI42H5IU.js +326 -0
- package/dist/chunk-RI42H5IU.js.map +1 -0
- package/dist/chunk-S2QH6EZ5.js +211 -0
- package/dist/chunk-S2QH6EZ5.js.map +1 -0
- package/dist/chunk-SHLZMAJN.js +304 -0
- package/dist/chunk-SHLZMAJN.js.map +1 -0
- package/dist/chunk-TGXK4BAZ.js +293 -0
- package/dist/chunk-TGXK4BAZ.js.map +1 -0
- package/dist/{chunk-5YK2ZDIG.js → chunk-TNEZNMSN.js} +4 -10
- package/dist/chunk-TNEZNMSN.js.map +1 -0
- package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
- package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
- package/dist/chunk-UNJPZGNK.js +63 -0
- package/dist/chunk-UNJPZGNK.js.map +1 -0
- package/dist/{chunk-MZ2G2XBA.js → chunk-V5ZUJT2T.js} +205 -45
- package/dist/chunk-V5ZUJT2T.js.map +1 -0
- package/dist/chunk-VIUEOHXY.js +394 -0
- package/dist/chunk-VIUEOHXY.js.map +1 -0
- package/dist/chunk-WIUKB3LD.js +1911 -0
- package/dist/chunk-WIUKB3LD.js.map +1 -0
- package/dist/{chunk-ICA6R6TW.js → chunk-WNN7NO2N.js} +1451 -355
- package/dist/chunk-WNN7NO2N.js.map +1 -0
- package/dist/{chunk-YJFGAFBD.js → chunk-X5UVUIMF.js} +1892 -1223
- package/dist/chunk-X5UVUIMF.js.map +1 -0
- package/dist/chunk-XDA5QJE7.js +402 -0
- package/dist/chunk-XDA5QJE7.js.map +1 -0
- package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
- package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
- package/dist/chunk-XHF7OYAN.js +1150 -0
- package/dist/chunk-XHF7OYAN.js.map +1 -0
- package/dist/chunk-YSRBFJMQ.js +43 -0
- package/dist/chunk-YSRBFJMQ.js.map +1 -0
- package/dist/chunk-ZOZAPAUO.js +174 -0
- package/dist/chunk-ZOZAPAUO.js.map +1 -0
- package/dist/chunk-ZQV5W7YC.js +809 -0
- package/dist/chunk-ZQV5W7YC.js.map +1 -0
- package/dist/chunk-ZUWF4NF7.js +13 -0
- package/dist/chunk-ZUWF4NF7.js.map +1 -0
- package/dist/client.d.ts +16 -7
- package/dist/clipboard.js +3 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/anthropic-mark.d.ts +3 -0
- package/dist/components/approval-surface.d.ts +19 -6
- package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
- package/dist/components/artifacts/artifact-sandbox.d.ts +30 -0
- package/dist/components/artifacts/artifact-surface.d.ts +4 -2
- package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
- package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
- package/dist/components/artifacts/document-editor.d.ts +4 -2
- package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
- package/dist/components/artifacts/editable-artifact-view.d.ts +48 -0
- package/dist/components/artifacts/editable-document.d.ts +1 -1
- package/dist/components/artifacts/editable-presentation.d.ts +2 -2
- package/dist/components/artifacts/editable-spreadsheet.d.ts +8 -3
- package/dist/components/artifacts/index.d.ts +9 -2
- package/dist/components/artifacts/inline-html-document.d.ts +2 -0
- package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
- package/dist/components/artifacts/presentation-editor.d.ts +6 -2
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +23 -3
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +26 -0
- package/dist/components/artifacts/spreadsheet-command-queue.d.ts +20 -0
- package/dist/components/artifacts/spreadsheet-download.d.ts +7 -0
- package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
- package/dist/components/artifacts/spreadsheet-grid.d.ts +10 -3
- package/dist/components/artifacts/spreadsheet-resize.d.ts +50 -0
- package/dist/components/artifacts/spreadsheet-submissions.d.ts +9 -0
- package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
- package/dist/components/browser-select-control.d.ts +16 -0
- package/dist/components/browser-viewer.d.ts +11 -2
- package/dist/components/chat-composer.d.ts +21 -3
- package/dist/components/child-session-link.d.ts +9 -0
- package/dist/components/claude-mark.d.ts +3 -0
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/composer-transcription-control.d.ts +7 -0
- package/dist/components/composer.d.ts +34 -67
- package/dist/components/computer-viewer.d.ts +1 -1
- package/dist/components/copy-button.d.ts +3 -1
- package/dist/components/desktop-viewer.d.ts +1 -1
- package/dist/components/fleet-tile.d.ts +2 -2
- package/dist/components/grok-mark.d.ts +26 -0
- package/dist/components/human-input-form.d.ts +28 -32
- package/dist/components/human-input-surface.d.ts +7 -2
- package/dist/components/linkified-text.d.ts +18 -0
- package/dist/components/machine-input-display.d.ts +22 -1
- package/dist/components/machines/agent-status-icon.d.ts +6 -0
- package/dist/components/machines/machine-detail.d.ts +7 -1
- package/dist/components/machines/machine-status.d.ts +48 -0
- package/dist/components/markdown-table-layout.d.ts +18 -0
- package/dist/components/markdown.d.ts +37 -4
- package/dist/components/message-timeline.d.ts +86 -7
- package/dist/components/model-mark.d.ts +29 -0
- package/dist/components/model-policy-picker-menu.d.ts +9 -0
- package/dist/components/model-policy-picker.d.ts +41 -25
- package/dist/components/new-conversation.d.ts +32 -0
- package/dist/components/open-geni-chat.d.ts +73 -0
- package/dist/components/open-geni-links.d.ts +68 -0
- package/dist/components/openrouter-mark.d.ts +6 -0
- package/dist/components/opper-mark.d.ts +7 -0
- package/dist/components/preview-loading.d.ts +2 -0
- package/dist/components/provider-recovery-notice.d.ts +16 -0
- package/dist/components/queue-item-content.d.ts +9 -0
- package/dist/components/rehype-secret-refs.d.ts +23 -0
- package/dist/components/remark-soft-line-breaks.d.ts +38 -0
- package/dist/components/sandbox-files.d.ts +3 -1
- package/dist/components/sandbox-workspace.d.ts +18 -0
- package/dist/components/secret-question-field.d.ts +31 -0
- package/dist/components/secret-ref-chip.d.ts +35 -0
- package/dist/components/session-capability-frame.d.ts +37 -0
- package/dist/components/session-chrome.d.ts +57 -8
- package/dist/components/session-commands-panel.d.ts +6 -0
- package/dist/components/session-commands.d.ts +6 -0
- package/dist/components/session-connection-request.d.ts +8 -0
- package/dist/components/session-conversation.d.ts +157 -0
- package/dist/components/session-list.d.ts +39 -0
- package/dist/components/session-mcp-capability-card.d.ts +41 -0
- package/dist/components/session-proxy-scope.d.ts +35 -0
- package/dist/components/session-status.d.ts +4 -1
- package/dist/components/session-usage-limit-notice.d.ts +39 -0
- package/dist/components/timeline-anchor.d.ts +24 -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 +3 -2
- package/dist/components/timeline-annotations.d.ts +13 -3
- package/dist/components/timeline-reading-progress.d.ts +11 -0
- package/dist/components/timeline-search.d.ts +34 -0
- package/dist/components/timeline-settlement.d.ts +16 -0
- package/dist/components/tip-follow.d.ts +16 -0
- package/dist/components/tool-action-review.d.ts +47 -0
- package/dist/components/tool-review-history.d.ts +25 -0
- package/dist/components/usage-limit-notice.d.ts +49 -0
- package/dist/components/usage-member-list.d.ts +91 -0
- package/dist/components/usage-meter.d.ts +73 -0
- package/dist/components/use-viewer-menu-dismiss.d.ts +2 -0
- package/dist/components/user-message-body.d.ts +10 -1
- package/dist/composer-messages.d.ts +74 -0
- package/dist/composer-messages.js +9 -0
- package/dist/composer-messages.js.map +1 -0
- package/dist/composer-voice-events.d.ts +8 -0
- package/dist/composer.d.ts +5 -4
- package/dist/composer.js +26 -13
- package/dist/computer-viewer-GMLDXPON.js +20 -0
- package/dist/computer-viewer-GMLDXPON.js.map +1 -0
- package/dist/connect-account-identity.d.ts +8 -0
- package/dist/connect-accounts.d.ts +15 -0
- package/dist/connect-chooser.d.ts +18 -0
- package/dist/connect-panel.d.ts +18 -0
- package/dist/connect-setup.d.ts +18 -0
- package/dist/connect.d.ts +21 -0
- package/dist/connect.js +2149 -0
- package/dist/connect.js.map +1 -0
- package/dist/connection-catalog.d.ts +38 -0
- package/dist/connection-discovery.d.ts +12 -0
- package/dist/connection-installed.d.ts +17 -0
- package/dist/connection-logo.d.ts +9 -0
- package/dist/connection-service-logo.d.ts +8 -0
- package/dist/connection-service-presentation.d.ts +13 -0
- package/dist/connection-skeleton.d.ts +5 -0
- package/dist/connection-type-picker.d.ts +6 -0
- package/dist/conversation-timeline.d.ts +5 -0
- package/dist/desktop.d.ts +7 -0
- package/dist/desktop.js +27 -0
- package/dist/desktop.js.map +1 -0
- package/dist/device-authorization.d.ts +23 -0
- package/dist/diffs.d.ts +8 -0
- package/dist/diffs.js +15 -0
- package/dist/diffs.js.map +1 -0
- package/dist/discovery-cache.d.ts +5 -0
- package/dist/editor.d.ts +7 -0
- package/dist/editor.js +22 -0
- package/dist/editor.js.map +1 -0
- package/dist/embedded-new-voice-OCRNT6U5.js +18 -0
- package/dist/embedded-new-voice-OCRNT6U5.js.map +1 -0
- package/dist/embedded-voice-RPHPYVBT.js +18 -0
- package/dist/embedded-voice-RPHPYVBT.js.map +1 -0
- package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-SI63O5V4.js} +4 -1
- package/dist/fleet-decision-row-SI63O5V4.js.map +1 -0
- package/dist/hooks/latest-question.d.ts +25 -0
- package/dist/hooks/shared-machines-list.d.ts +4 -0
- package/dist/hooks/shared-query.d.ts +113 -0
- package/dist/hooks/use-attached-browsers.d.ts +1 -1
- package/dist/hooks/use-auth-runs.d.ts +1 -4
- package/dist/hooks/use-available-models.d.ts +1 -1
- package/dist/hooks/use-browser-frame-stream.d.ts +1 -1
- package/dist/hooks/use-browser-session.d.ts +12 -1
- package/dist/hooks/use-client-config-flags.d.ts +49 -0
- package/dist/hooks/use-codex-accounts.d.ts +59 -10
- package/dist/hooks/use-composer.d.ts +24 -2
- package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
- package/dist/hooks/use-computer-input-posture.d.ts +13 -0
- package/dist/hooks/use-computer-session.d.ts +4 -0
- package/dist/hooks/use-connect.d.ts +23 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +12 -0
- package/dist/hooks/use-goal.d.ts +2 -1
- package/dist/hooks/use-last-started-turn-policy.d.ts +5 -1
- package/dist/hooks/use-new-conversation.d.ts +56 -0
- package/dist/hooks/use-new-session-draft.d.ts +74 -0
- package/dist/hooks/use-realtime-voice-models.d.ts +7 -0
- package/dist/hooks/use-sandbox-files.d.ts +4 -1
- package/dist/hooks/use-session-conversation.d.ts +49 -0
- package/dist/hooks/use-session-events.d.ts +28 -7
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session-model-account.d.ts +48 -0
- package/dist/hooks/use-supergrok-accounts.d.ts +7 -1
- package/dist/hooks/use-terminal-stream.d.ts +11 -5
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/hooks/use-vaults.d.ts +53 -0
- package/dist/hooks/use-voice-input.d.ts +8 -1
- package/dist/hooks/use-workspace-edit.d.ts +4 -0
- package/dist/hooks/use-workspace-sessions.d.ts +22 -5
- package/dist/human-input-model.d.ts +122 -0
- package/dist/identity-link-accounts.d.ts +11 -0
- package/dist/identity-link-consent.d.ts +18 -0
- package/dist/index.d.ts +82 -20
- package/dist/index.js +2350 -6605
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +14 -24
- package/dist/interaction.js.map +1 -1
- package/dist/latest-question-R77KWSUP.js +118 -0
- package/dist/latest-question-R77KWSUP.js.map +1 -0
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +58 -4
- package/dist/lib/host-theme.d.ts +77 -0
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/lib/menu-styles.d.ts +22 -0
- package/dist/lib/notify-observer.d.ts +2 -0
- package/dist/lib/pierre-diffs-loader.d.ts +20 -0
- package/dist/lib/provider-recovery.d.ts +55 -0
- package/dist/lib/scrollbars.d.ts +11 -0
- package/dist/lib/terminal-input-readiness.d.ts +11 -0
- package/dist/lib/transient-retry.d.ts +25 -0
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/lib/xterm-theme.d.ts +7 -5
- package/dist/machines.js +9 -5
- package/dist/markdown-table-layout-3PV2UAAH.js +113 -0
- package/dist/markdown-table-layout-3PV2UAAH.js.map +1 -0
- package/dist/mcp-connection-status.d.ts +14 -0
- package/dist/model-mark-paths.d.ts +34 -0
- package/dist/model-mark-paths.js +16 -0
- package/dist/model-mark-paths.js.map +1 -0
- package/dist/model-picker-model.d.ts +111 -0
- package/dist/model-picker-model.js +25 -0
- package/dist/model-picker-model.js.map +1 -0
- package/dist/model-policy-picker-menu-7GJ5URPR.js +17 -0
- package/dist/model-policy-picker-menu-7GJ5URPR.js.map +1 -0
- package/dist/model-policy.d.ts +30 -3
- package/dist/model-policy.js +14 -1
- package/dist/native-previews.d.ts +28 -0
- package/dist/native-previews.js +82 -0
- package/dist/native-previews.js.map +1 -0
- package/dist/new-session-draft.d.ts +7 -0
- package/dist/new-session-draft.js +639 -0
- package/dist/new-session-draft.js.map +1 -0
- package/dist/notification-text.d.ts +27 -0
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
- package/dist/older-history.d.ts +3 -2
- package/dist/organization-model.d.ts +32 -0
- package/dist/organization-model.js +65 -0
- package/dist/organization-model.js.map +1 -0
- package/dist/{platform-activity-row-BJHUHXAT.js → platform-activity-row-5OCKOQWZ.js} +16 -78
- package/dist/platform-activity-row-5OCKOQWZ.js.map +1 -0
- package/dist/plugin-details.d.ts +14 -0
- package/dist/plugin-discovery.d.ts +25 -0
- package/dist/preview-loading-paint.d.ts +20 -0
- package/dist/provider.d.ts +25 -2
- package/dist/queue-presentation-model.d.ts +72 -0
- package/dist/queue-presentation-model.js +38 -0
- package/dist/queue-presentation-model.js.map +1 -0
- package/dist/{queue-surface-implementation-BSKUWHRI.js → queue-surface-implementation-GJBH7NKO.js} +65 -265
- package/dist/queue-surface-implementation-GJBH7NKO.js.map +1 -0
- package/dist/realtime/embedded-new-voice.d.ts +1 -0
- package/dist/realtime/embedded-voice.d.ts +1 -0
- package/dist/realtime/realtime-control.d.ts +4 -65
- package/dist/realtime/session-realtime.d.ts +78 -0
- package/dist/realtime.js +25 -1294
- package/dist/realtime.js.map +1 -1
- package/dist/sandbox-files-O27IPOZW.js +21 -0
- package/dist/sandbox-files-O27IPOZW.js.map +1 -0
- package/dist/sandbox-label-model.d.ts +30 -0
- package/dist/sandbox-label-model.js +20 -0
- package/dist/sandbox-label-model.js.map +1 -0
- package/dist/scrollbars.d.ts +1 -0
- package/dist/scrollbars.js +10 -0
- package/dist/scrollbars.js.map +1 -0
- package/dist/secret-reference-label.d.ts +14 -0
- package/dist/secrets-ui.d.ts +57 -0
- package/dist/session-agents-model.d.ts +10 -0
- package/dist/session-agents-model.js +25 -0
- package/dist/session-agents-model.js.map +1 -0
- package/dist/session-attention-model.d.ts +8 -0
- package/dist/session-attention-model.js +47 -0
- package/dist/session-attention-model.js.map +1 -0
- package/dist/session-auth-recommendation.d.ts +5 -0
- package/dist/session-capability-policy.d.ts +31 -0
- package/dist/session-feedback-model.d.ts +20 -0
- package/dist/session-feedback-model.js +56 -0
- package/dist/session-feedback-model.js.map +1 -0
- package/dist/session-list-model.d.ts +138 -0
- package/dist/session-list-model.js +274 -0
- package/dist/session-list-model.js.map +1 -0
- package/dist/session-realtime-7DJOIHQW.js +21 -0
- package/dist/session-realtime-7DJOIHQW.js.map +1 -0
- package/dist/session-realtime.d.ts +7 -0
- package/dist/session-realtime.js +19 -0
- package/dist/session-realtime.js.map +1 -0
- package/dist/session-status-model.d.ts +36 -0
- package/dist/session-ui.d.ts +39 -1
- package/dist/session-ui.js +126 -12
- package/dist/session-ui.js.map +1 -1
- package/dist/session.d.ts +8 -2
- package/dist/session.js +35 -20
- package/dist/site-snapshot.d.ts +14 -0
- package/dist/sites-ui.d.ts +24 -0
- package/dist/sites.d.ts +3 -0
- package/dist/sites.js +14 -0
- package/dist/sites.js.map +1 -0
- package/dist/skill-discovery.d.ts +47 -0
- package/dist/terminal.d.ts +8 -0
- package/dist/terminal.js +32 -0
- package/dist/terminal.js.map +1 -0
- package/dist/testing/session-scenarios.d.ts +10 -0
- package/dist/testing/session-scenarios.js +367 -0
- package/dist/testing/session-scenarios.js.map +1 -0
- package/dist/timeline/activity-rail.d.ts +11 -2
- package/dist/timeline/agent-identity.d.ts +30 -0
- package/dist/timeline/agent-row.d.ts +31 -0
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/compaction-copy.d.ts +46 -0
- package/dist/timeline/entrance.d.ts +13 -1
- package/dist/timeline/focus-sequence.d.ts +9 -0
- package/dist/timeline/genie-copy.d.ts +3 -0
- package/dist/timeline/genie-loading.d.ts +47 -0
- package/dist/timeline/index.d.ts +10 -5
- package/dist/timeline/knowledge-receipt.d.ts +17 -0
- package/dist/timeline/message-sender.d.ts +19 -0
- package/dist/timeline/notice-presentation.d.ts +13 -0
- package/dist/timeline/parsers.d.ts +11 -0
- package/dist/timeline/patch-apply-command.d.ts +18 -0
- package/dist/timeline/platform-activity-presentation.d.ts +21 -0
- package/dist/timeline/platform-activity-row.d.ts +1 -2
- package/dist/timeline/presented-image.d.ts +11 -0
- package/dist/timeline/projection.d.ts +31 -2
- package/dist/timeline/provider-logo.d.ts +15 -0
- package/dist/timeline/question-nav-model.d.ts +23 -0
- package/dist/timeline/registry.d.ts +12 -1
- package/dist/timeline/retained-image.d.ts +18 -0
- package/dist/timeline/retained-loaders.d.ts +27 -0
- package/dist/timeline/rolling-activity.d.ts +11 -0
- package/dist/timeline/screenshot-lightbox.d.ts +23 -1
- package/dist/timeline/session-usage-limit-row.d.ts +11 -0
- package/dist/timeline/shared.d.ts +4 -1
- package/dist/timeline/startup-dispatch-details.d.ts +5 -0
- package/dist/timeline/startup-preference.d.ts +3 -0
- package/dist/timeline/startup-timings.d.ts +4 -0
- package/dist/timeline/tool-display-name.d.ts +5 -2
- package/dist/timeline/tool-presentation.d.ts +172 -0
- package/dist/timeline/turn-summary-model.d.ts +95 -0
- package/dist/timeline/turn-summary.d.ts +20 -54
- package/dist/timeline/types.d.ts +179 -7
- package/dist/timeline/work-presentation.d.ts +60 -0
- package/dist/timeline-annotation-markers-U3G66KUD.js +166 -0
- package/dist/timeline-annotation-markers-U3G66KUD.js.map +1 -0
- package/dist/timeline-annotation-selection-BI7QTPTZ.js +242 -0
- package/dist/timeline-annotation-selection-BI7QTPTZ.js.map +1 -0
- package/dist/timeline-annotations-dialog-FOMHSDTE.js +274 -0
- package/dist/timeline-annotations-dialog-FOMHSDTE.js.map +1 -0
- package/dist/timeline-model.d.ts +29 -0
- package/dist/timeline-model.js +254 -0
- package/dist/timeline-model.js.map +1 -0
- package/dist/usage/allowance-copy.d.ts +25 -0
- package/dist/usage/session-usage-limit.d.ts +88 -0
- package/dist/usage/summary.d.ts +35 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.js +1187 -0
- package/dist/usage.js.map +1 -0
- package/dist/voice-recording-owner.d.ts +7 -0
- package/package.json +107 -7
- package/src/accounts.tsx +116 -14
- package/src/approvals.ts +8 -1
- package/src/artifacts-spreadsheet.ts +1 -0
- package/src/artifacts.ts +44 -0
- package/src/assets/README.md +8 -0
- package/src/assets/gmail.ico +0 -0
- package/src/assets/outlook.ico +0 -0
- package/src/capability-catalog-row.tsx +94 -0
- package/src/capability-logo-fallback.ts +22 -0
- package/src/client.ts +20 -31
- package/src/commands/registry.ts +18 -15
- package/src/commands/types.ts +2 -0
- package/src/components/anthropic-mark.tsx +10 -0
- package/src/components/approval-surface.tsx +401 -138
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +189 -0
- package/src/components/artifacts/artifact-surface.tsx +40 -20
- package/src/components/artifacts/chat-interactive-block.tsx +260 -0
- package/src/components/artifacts/deferred-chat-media.tsx +67 -0
- package/src/components/artifacts/document-editor.tsx +141 -16
- package/src/components/artifacts/editable-artifact-ui.tsx +4 -2
- package/src/components/artifacts/editable-artifact-view.tsx +197 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
- package/src/components/artifacts/editable-document.tsx +37 -36
- package/src/components/artifacts/editable-presentation.tsx +36 -18
- package/src/components/artifacts/editable-spreadsheet.tsx +543 -103
- package/src/components/artifacts/index.ts +45 -0
- package/src/components/artifacts/inline-html-document.ts +24 -0
- package/src/components/artifacts/latest-task-runner.ts +49 -0
- package/src/components/artifacts/presentation-editor.tsx +323 -67
- package/src/components/artifacts/published-html-artifact-frame.tsx +122 -5
- package/src/components/artifacts/session-artifact-viewer.tsx +378 -0
- package/src/components/artifacts/site-view.tsx +121 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
- package/src/components/artifacts/spreadsheet-command-queue.ts +184 -0
- package/src/components/artifacts/spreadsheet-download.tsx +109 -0
- package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +344 -144
- package/src/components/artifacts/spreadsheet-resize.tsx +421 -0
- package/src/components/artifacts/spreadsheet-submissions.tsx +46 -0
- package/src/components/artifacts/visualization-assets/base.css +1015 -0
- package/src/components/artifacts/visualization-assets/helpers.html +349 -0
- package/src/components/artifacts/visualization-assets.gen.ts +3 -0
- package/src/components/browser-select-control.tsx +312 -0
- package/src/components/browser-viewer.tsx +778 -318
- package/src/components/chat-composer.tsx +47 -5
- package/src/components/child-session-link.tsx +36 -0
- package/src/components/claude-mark.tsx +11 -0
- package/src/components/code-editor.tsx +13 -16
- package/src/components/composer-transcription-control.tsx +128 -30
- package/src/components/composer.tsx +404 -195
- package/src/components/computer-viewer.tsx +880 -253
- package/src/components/copy-button.tsx +13 -5
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/enrollment-consent.tsx +1 -1
- package/src/components/enrollment-device-flow.tsx +1 -1
- package/src/components/file-browser.tsx +10 -14
- package/src/components/fleet-tile.tsx +9 -18
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/grok-mark.tsx +34 -0
- package/src/components/human-input-form.tsx +590 -283
- package/src/components/human-input-surface.tsx +11 -0
- package/src/components/interaction-intervention-banner.tsx +2 -2
- package/src/components/linkified-text.tsx +79 -0
- package/src/components/machine-card.tsx +35 -78
- package/src/components/machine-dock-bar.tsx +1 -1
- package/src/components/machine-input-display.ts +197 -3
- package/src/components/machines/agent-status-icon.tsx +31 -0
- package/src/components/machines/machine-detail.tsx +200 -31
- package/src/components/machines/machine-operation-policy.tsx +1 -1
- package/src/components/machines/machine-status.ts +241 -0
- package/src/components/machines-dashboard.tsx +2 -17
- package/src/components/markdown-table-layout.ts +143 -0
- package/src/components/markdown.tsx +466 -129
- package/src/components/message-timeline.tsx +2567 -728
- package/src/components/model-mark.tsx +102 -0
- package/src/components/model-picker.tsx +15 -4
- package/src/components/model-policy-picker-menu.tsx +371 -0
- package/src/components/model-policy-picker.tsx +256 -412
- package/src/components/new-conversation.tsx +221 -0
- package/src/components/open-geni-chat.tsx +309 -0
- package/src/components/open-geni-links.tsx +179 -0
- package/src/components/openrouter-mark.tsx +13 -0
- package/src/components/opper-mark.tsx +18 -0
- package/src/components/pierre-diff.tsx +41 -16
- package/src/components/pierre-file.tsx +35 -10
- package/src/components/preview-loading.tsx +109 -0
- package/src/components/provider-recovery-notice.tsx +49 -0
- package/src/components/queue-item-content.ts +14 -0
- package/src/components/queue-surface-implementation.tsx +51 -356
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/rehype-secret-refs.ts +98 -0
- package/src/components/remark-soft-line-breaks.ts +166 -0
- package/src/components/sandbox-files.tsx +91 -15
- package/src/components/sandbox-terminal.tsx +84 -13
- package/src/components/sandbox-workspace.tsx +147 -51
- package/src/components/secret-question-field.tsx +324 -0
- package/src/components/secret-ref-chip.tsx +185 -0
- package/src/components/session-capability-frame.tsx +197 -0
- package/src/components/session-chrome.tsx +842 -251
- package/src/components/session-commands-panel.tsx +126 -0
- package/src/components/session-commands.tsx +18 -0
- package/src/components/session-connection-request.tsx +97 -0
- package/src/components/session-conversation.tsx +876 -0
- package/src/components/session-list.tsx +257 -0
- package/src/components/session-mcp-capability-card.tsx +680 -0
- package/src/components/session-proxy-scope.tsx +153 -0
- package/src/components/session-status.tsx +26 -17
- package/src/components/session-usage-limit-notice.tsx +174 -0
- package/src/components/timeline-anchor.tsx +119 -0
- package/src/components/timeline-annotation-chrome.tsx +196 -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 +106 -54
- package/src/components/timeline-annotation-shared.ts +245 -0
- package/src/components/timeline-annotations-dialog.tsx +185 -128
- package/src/components/timeline-annotations.tsx +189 -45
- package/src/components/timeline-reading-progress.ts +80 -0
- package/src/components/timeline-search.tsx +168 -0
- package/src/components/timeline-settlement.ts +60 -0
- package/src/components/tip-follow.ts +52 -0
- package/src/components/tool-action-review.tsx +650 -0
- package/src/components/tool-review-history.tsx +88 -0
- package/src/components/tooltip.tsx +38 -5
- package/src/components/usage-limit-notice.tsx +179 -0
- package/src/components/usage-member-list.tsx +792 -0
- package/src/components/usage-meter.tsx +382 -0
- package/src/components/use-viewer-menu-dismiss.ts +33 -0
- package/src/components/user-message-body.tsx +27 -2
- package/src/components/workspace-dock.tsx +18 -1
- package/src/composer-messages.ts +150 -0
- package/src/composer-voice-events.ts +12 -0
- package/src/composer.ts +6 -1
- package/src/connect-account-identity.tsx +34 -0
- package/src/connect-accounts.tsx +233 -0
- package/src/connect-chooser.tsx +336 -0
- package/src/connect-panel.tsx +99 -0
- package/src/connect-setup.tsx +355 -0
- package/src/connect.ts +60 -0
- package/src/connection-assets.d.ts +4 -0
- package/src/connection-catalog.tsx +162 -0
- package/src/connection-discovery.tsx +238 -0
- package/src/connection-installed.tsx +51 -0
- package/src/connection-logo.tsx +37 -0
- package/src/connection-service-logo.tsx +55 -0
- package/src/connection-service-presentation.ts +46 -0
- package/src/connection-skeleton.tsx +16 -0
- package/src/connection-type-picker.tsx +36 -0
- package/src/conversation-timeline.ts +92 -0
- package/src/desktop.ts +24 -0
- package/src/device-authorization.tsx +98 -0
- package/src/diffs.ts +16 -0
- package/src/discovery-cache.ts +42 -0
- package/src/editor.ts +13 -0
- package/src/hooks/internal.ts +3 -2
- package/src/hooks/latest-question.ts +164 -0
- package/src/hooks/shared-machines-list.ts +34 -1
- package/src/hooks/shared-query.ts +721 -0
- package/src/hooks/use-attached-browsers.ts +1 -1
- package/src/hooks/use-auth-runs.ts +0 -27
- package/src/hooks/use-available-models.ts +21 -15
- package/src/hooks/use-browser-frame-stream.ts +19 -2
- package/src/hooks/use-browser-session.ts +680 -205
- package/src/hooks/use-browser-sessions.ts +32 -1
- package/src/hooks/use-client-config-flags.ts +118 -0
- package/src/hooks/use-codex-accounts.ts +247 -47
- package/src/hooks/use-composer.ts +276 -47
- package/src/hooks/use-computer-frame-stream.ts +25 -7
- package/src/hooks/use-computer-input-posture.ts +94 -0
- package/src/hooks/use-computer-session.ts +409 -95
- package/src/hooks/use-connect.ts +23 -0
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-file-attachments.ts +80 -4
- package/src/hooks/use-goal.ts +107 -13
- package/src/hooks/use-last-started-turn-policy.ts +46 -14
- package/src/hooks/use-machines.ts +4 -0
- package/src/hooks/use-new-conversation.ts +374 -0
- package/src/hooks/use-new-session-draft.ts +813 -0
- package/src/hooks/use-realtime-voice-models.ts +46 -0
- package/src/hooks/use-sandbox-files.ts +68 -20
- package/src/hooks/use-session-background-commands.ts +22 -11
- package/src/hooks/use-session-capabilities.ts +3 -2
- package/src/hooks/use-session-conversation.ts +150 -0
- package/src/hooks/use-session-events.ts +703 -382
- package/src/hooks/use-session-lineage.ts +20 -16
- package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
- package/src/hooks/use-session-model-account.ts +174 -0
- package/src/hooks/use-session.ts +53 -21
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-supergrok-accounts.ts +22 -8
- package/src/hooks/use-terminal-stream.ts +153 -27
- package/src/hooks/use-transcription.ts +6 -0
- package/src/hooks/use-turn-queue.ts +2 -1
- package/src/hooks/use-usage.ts +100 -0
- package/src/hooks/use-vaults.ts +153 -0
- package/src/hooks/use-video-artifact-playback.ts +1 -1
- package/src/hooks/use-voice-input.ts +127 -19
- package/src/hooks/use-workspace-capture.ts +3 -2
- package/src/hooks/use-workspace-edit.ts +23 -5
- package/src/hooks/use-workspace-sessions.ts +97 -32
- package/src/human-input-model.ts +359 -0
- package/src/identity-link-accounts.tsx +148 -0
- package/src/identity-link-consent.tsx +192 -0
- package/src/index.ts +262 -16
- package/src/interaction.ts +1 -0
- package/src/lib/clipboard.ts +29 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +213 -16
- package/src/lib/host-theme.ts +529 -0
- package/src/lib/interaction-errors.ts +15 -0
- package/src/lib/menu-styles.ts +44 -0
- package/src/lib/notify-observer.ts +15 -0
- package/src/lib/pierre-diffs-loader.ts +53 -0
- package/src/lib/provider-recovery.ts +243 -0
- package/src/lib/scrollbars.ts +30 -0
- package/src/lib/terminal-input-readiness.ts +51 -0
- package/src/lib/transient-retry.ts +99 -0
- package/src/lib/workbench-peers.ts +62 -0
- package/src/lib/xterm-theme.ts +23 -21
- package/src/mcp-connection-status.ts +35 -0
- package/src/model-mark-paths.ts +53 -0
- package/src/model-picker-model.ts +330 -0
- package/src/model-policy.ts +299 -74
- package/src/native-previews.ts +100 -0
- package/src/new-session-draft.ts +22 -0
- package/src/notification-text.ts +81 -0
- package/src/older-history.ts +22 -4
- package/src/organization-model.ts +118 -0
- package/src/plugin-details.tsx +185 -0
- package/src/plugin-discovery.tsx +196 -0
- package/src/preview-loading-paint.ts +68 -0
- package/src/provider.tsx +74 -15
- package/src/queue-presentation-model.ts +464 -0
- package/src/realtime/dropdown-menu.tsx +19 -12
- package/src/realtime/embedded-new-voice.tsx +1 -0
- package/src/realtime/embedded-voice.tsx +3 -0
- package/src/realtime/realtime-control.tsx +138 -501
- package/src/realtime/session-realtime.ts +566 -0
- package/src/sandbox-label-model.ts +53 -0
- package/src/scrollbars.ts +3 -0
- package/src/secret-reference-label.ts +43 -0
- package/src/secrets-ui.tsx +59 -0
- package/src/session-agents-model.ts +33 -0
- package/src/session-attention-model.ts +62 -0
- package/src/session-auth-recommendation.ts +48 -0
- package/src/session-capability-policy.ts +196 -0
- package/src/session-context.ts +3 -4
- package/src/session-feedback-model.ts +95 -0
- package/src/session-list-model.ts +434 -0
- package/src/session-realtime.ts +18 -0
- package/src/session-status-model.ts +99 -0
- package/src/session-ui.ts +98 -1
- package/src/session.ts +17 -1
- package/src/site-snapshot.ts +74 -0
- package/src/sites-ui.tsx +348 -0
- package/src/sites.ts +3 -0
- package/src/skill-discovery.tsx +208 -0
- package/src/terminal.ts +26 -0
- package/src/testing/session-scenarios.ts +404 -0
- package/src/timeline/activity-rail.tsx +242 -101
- package/src/timeline/agent-identity.tsx +84 -0
- package/src/timeline/agent-row.tsx +231 -0
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/compaction-copy.ts +115 -0
- package/src/timeline/entrance.tsx +28 -3
- package/src/timeline/fleet-decision-projection.ts +2 -1
- package/src/timeline/fleet-decision-row.tsx +1 -0
- package/src/timeline/focus-sequence.ts +44 -0
- package/src/timeline/genie-copy.ts +19 -0
- package/src/timeline/genie-loading.tsx +143 -0
- package/src/timeline/index.ts +30 -2
- package/src/timeline/knowledge-receipt.tsx +100 -0
- package/src/timeline/message-sender.tsx +71 -0
- package/src/timeline/notice-presentation.ts +48 -0
- package/src/timeline/parsers.ts +42 -2
- package/src/timeline/patch-apply-command.tsx +135 -0
- package/src/timeline/platform-activity-presentation.ts +166 -0
- package/src/timeline/platform-activity-row.tsx +15 -90
- package/src/timeline/presented-image.ts +63 -0
- package/src/timeline/projection.ts +1746 -76
- package/src/timeline/provider-logo.tsx +53 -0
- package/src/timeline/question-nav-model.ts +28 -0
- package/src/timeline/registry.ts +26 -4
- package/src/timeline/retained-image.ts +97 -0
- package/src/timeline/retained-loaders.ts +91 -0
- package/src/timeline/rolling-activity.tsx +80 -0
- package/src/timeline/screenshot-lightbox.tsx +228 -5
- package/src/timeline/session-usage-limit-row.tsx +42 -0
- package/src/timeline/shared.tsx +42 -15
- package/src/timeline/startup-dispatch-details.tsx +36 -0
- package/src/timeline/startup-preference.ts +33 -0
- package/src/timeline/startup-timings.tsx +145 -0
- package/src/timeline/tool-display-name.ts +19 -7
- package/src/timeline/tool-presentation.ts +1316 -0
- package/src/timeline/tool-renderers.tsx +835 -1138
- package/src/timeline/turn-summary-model.ts +343 -0
- package/src/timeline/turn-summary.tsx +272 -365
- package/src/timeline/types.ts +189 -6
- package/src/timeline/work-presentation.ts +189 -0
- package/src/timeline-model.ts +143 -0
- package/src/usage/allowance-copy.ts +64 -0
- package/src/usage/session-usage-limit.ts +266 -0
- package/src/usage/summary.ts +93 -0
- package/src/usage.ts +70 -0
- package/src/voice-recording-owner.ts +22 -0
- package/styles/compiled.css +2319 -748
- package/styles/connect.css +290 -0
- package/styles/connect.d.ts +1 -0
- package/styles/effective-tokens.css +1 -0
- package/styles/index.css +309 -9
- package/styles/responsive.css +99 -7
- package/styles/session-capability.css +44 -0
- package/styles/tokens.css +134 -66
- package/dist/browser-viewer-AEFHUYZK.js +0 -14
- package/dist/chunk-4MDLEHXL.js.map +0 -1
- package/dist/chunk-5YK2ZDIG.js.map +0 -1
- package/dist/chunk-D3UIAJKY.js.map +0 -1
- package/dist/chunk-ELIYWBZR.js.map +0 -1
- package/dist/chunk-H5YGYNXS.js.map +0 -1
- package/dist/chunk-HQVCKCLG.js +0 -9458
- package/dist/chunk-HQVCKCLG.js.map +0 -1
- package/dist/chunk-HUIKIDM5.js.map +0 -1
- package/dist/chunk-ICA6R6TW.js.map +0 -1
- package/dist/chunk-JVYQRRC4.js.map +0 -1
- package/dist/chunk-KCGXPP5V.js.map +0 -1
- package/dist/chunk-KZ73RL76.js.map +0 -1
- package/dist/chunk-LAKLF4PA.js +0 -141
- package/dist/chunk-LAKLF4PA.js.map +0 -1
- package/dist/chunk-MZ2G2XBA.js.map +0 -1
- package/dist/chunk-NEXLLTP4.js.map +0 -1
- package/dist/chunk-OAM4WAAM.js.map +0 -1
- package/dist/chunk-PEI6RANI.js.map +0 -1
- package/dist/chunk-RGALCTWO.js.map +0 -1
- package/dist/chunk-W6NFJXSA.js +0 -454
- package/dist/chunk-W6NFJXSA.js.map +0 -1
- package/dist/chunk-WSK7G5LE.js +0 -170
- package/dist/chunk-WSK7G5LE.js.map +0 -1
- package/dist/chunk-X4A66EWZ.js +0 -223
- package/dist/chunk-X4A66EWZ.js.map +0 -1
- package/dist/chunk-XXWH2JLC.js.map +0 -1
- package/dist/chunk-YFKJ7VZH.js +0 -268
- package/dist/chunk-YFKJ7VZH.js.map +0 -1
- package/dist/chunk-YJFGAFBD.js.map +0 -1
- package/dist/computer-viewer-FWEZISW4.js +0 -16
- package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
- package/dist/hooks/use-environments.d.ts +0 -41
- package/dist/hooks/use-packs.d.ts +0 -32
- package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
- package/dist/queue-surface-implementation-BSKUWHRI.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/hooks/use-environments.ts +0 -182
- package/src/hooks/use-packs.ts +0 -157
- /package/dist/{browser-viewer-AEFHUYZK.js.map → browser-viewer-ZWR6MWJ7.js.map} +0 -0
- /package/dist/{computer-viewer-FWEZISW4.js.map → chat-interactive-block-BO2IWXVK.js.map} +0 -0
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
1
|
+
import {
|
|
2
|
+
sessionEventStreamCoveredThrough,
|
|
3
|
+
type SessionEvent,
|
|
4
|
+
type SessionStatus,
|
|
5
|
+
type StreamConnectionState,
|
|
6
|
+
} from "@opengeni/sdk";
|
|
7
|
+
import { startTransition, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
3
8
|
import { useEmbeddedSession, type EmbeddedSessionClientOverride } from "../session-context";
|
|
4
9
|
import { createOlderHistoryLoadReceipt, type OlderHistoryLoadReceipt } from "../older-history";
|
|
5
10
|
import { buildTimeline, groupTimeline, sessionStatusFromEvents } from "../timeline/projection";
|
|
6
11
|
import type { TimelineItem } from "../timeline/types";
|
|
7
12
|
import type { EmbeddedSessionClientLike } from "../client";
|
|
13
|
+
import { normalizeError } from "../lib/error-message";
|
|
8
14
|
import { usePageLiveActivity } from "./internal";
|
|
15
|
+
import type { LatestQuestionOptions } from "./latest-question";
|
|
9
16
|
|
|
10
17
|
export type SessionEventsConnectionState = StreamConnectionState | "idle" | "ended" | "error";
|
|
11
18
|
|
|
@@ -21,10 +28,12 @@ export type UseSessionEventsOptions = EmbeddedSessionClientOverride & {
|
|
|
21
28
|
export type UseSessionEventsResult = {
|
|
22
29
|
/** Replayed + live events, ordered by sequence, no gaps, no duplicates. */
|
|
23
30
|
events: SessionEvent[];
|
|
24
|
-
/**
|
|
31
|
+
/** Renderable timeline, including the bounded navigation witness for a distant queued prompt. */
|
|
25
32
|
timeline: TimelineItem[];
|
|
26
33
|
/** Latest session status observed in the event log, if any. */
|
|
27
34
|
sessionStatus: SessionStatus | null;
|
|
35
|
+
/** Sequence of the retained status projection, including events evicted from the window. */
|
|
36
|
+
sessionStatusSequence?: number;
|
|
28
37
|
connectionState: SessionEventsConnectionState;
|
|
29
38
|
/** Highest sequence seen so far (0 before the first event). */
|
|
30
39
|
lastSequence: number;
|
|
@@ -34,6 +43,10 @@ export type UseSessionEventsResult = {
|
|
|
34
43
|
windowTruncated: boolean;
|
|
35
44
|
/** True until the initial tail window has been applied (windowed mode). */
|
|
36
45
|
initialLoading: boolean;
|
|
46
|
+
/** A history window (including an empty tail) succeeded for this session/replay
|
|
47
|
+
* identity. Stays true through later stream errors and navigation/reloads.
|
|
48
|
+
* Full replay has no snapshot-completion watermark and does not set this by itself. */
|
|
49
|
+
initialHistoryReady: boolean;
|
|
37
50
|
/** Whether older durable events are available before the current window. */
|
|
38
51
|
hasOlder: boolean;
|
|
39
52
|
/** True while an older window is being fetched. */
|
|
@@ -66,13 +79,20 @@ export type UseSessionEventsResult = {
|
|
|
66
79
|
* "Jump to latest" when the tip is not in memory (history view).
|
|
67
80
|
*/
|
|
68
81
|
jumpToLatest: () => Promise<void>;
|
|
82
|
+
/** Resolve the newest eligible durable human question. Pending prompts use the
|
|
83
|
+
* optional queue destination and return null; started prompts load their actual
|
|
84
|
+
* turn context (pass `timeline` to MessageTimeline as `items`). */
|
|
85
|
+
jumpToLatestQuestion: (options?: LatestQuestionOptions) => Promise<number | null>;
|
|
86
|
+
/** Replace history with a bounded window containing this exact durable event. */
|
|
87
|
+
jumpToSequence: (sequence: number, options?: { signal?: AbortSignal }) => Promise<boolean>;
|
|
88
|
+
loadingTarget: boolean;
|
|
69
89
|
error: Error | null;
|
|
70
90
|
};
|
|
71
91
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const
|
|
92
|
+
// Keep every browser history read inside one database batch, including the
|
|
93
|
+
// server's one-row continuation lookahead. A large total session must never
|
|
94
|
+
// turn one lazy page into dozens of sequential database round trips.
|
|
95
|
+
const SESSION_HISTORY_PAGE_SIZE = 1000;
|
|
76
96
|
const INITIAL_FETCH_CAP = 1;
|
|
77
97
|
const OLDER_GROUP_TARGET = 32;
|
|
78
98
|
const OLDER_FETCH_CAP = 2;
|
|
@@ -80,37 +100,26 @@ const NEWER_GROUP_TARGET = 32;
|
|
|
80
100
|
const NEWER_FETCH_CAP = 2;
|
|
81
101
|
const OLDEST_GROUP_TARGET = 32;
|
|
82
102
|
const OLDEST_FETCH_CAP = 2;
|
|
83
|
-
|
|
103
|
+
// A tail page may land inside one unusually dense turn. Permit exactly one
|
|
104
|
+
// additional bounded page to find its user/session boundary without turning a
|
|
105
|
+
// fresh open into an unbounded history walk.
|
|
106
|
+
const BOUNDARY_PAGE_CAP = 1;
|
|
107
|
+
// Foreground reconciliation is intentionally semantic, not merely time-based:
|
|
108
|
+
// tiny raw gaps can stay on SSE; medium raw gaps get one compact probe so a
|
|
109
|
+
// token-heavy single answer is not mistaken for hundreds of visible messages;
|
|
110
|
+
// only a large/complex missed window reloads the latest tail.
|
|
111
|
+
const FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES = 16;
|
|
112
|
+
const FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES = SESSION_HISTORY_PAGE_SIZE;
|
|
113
|
+
const FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS = 128;
|
|
114
|
+
const FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS = 16;
|
|
115
|
+
const FOREGROUND_COMPACT_CATCHUP_MAX_BYTES = 512 * 1024;
|
|
84
116
|
const EMPTY_EVENTS: SessionEvent[] = [];
|
|
85
117
|
const encoder = new TextEncoder();
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
const
|
|
91
|
-
const BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES = 4 * 1024;
|
|
92
|
-
const BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES = 48 * 1024;
|
|
93
|
-
const BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS = [
|
|
94
|
-
"id",
|
|
95
|
-
"callId",
|
|
96
|
-
"call_id",
|
|
97
|
-
"name",
|
|
98
|
-
"toolName",
|
|
99
|
-
"status",
|
|
100
|
-
"code",
|
|
101
|
-
"isError",
|
|
102
|
-
"stream",
|
|
103
|
-
"commandId",
|
|
104
|
-
"sequence",
|
|
105
|
-
"coalescedUntil",
|
|
106
|
-
"coalescedCount",
|
|
107
|
-
"firstSequence",
|
|
108
|
-
"lastSequence",
|
|
109
|
-
] as const;
|
|
110
|
-
|
|
111
|
-
export const SESSION_EVENT_BROWSER_MAX_BYTES = 8 * 1024 * 1024;
|
|
112
|
-
export const SESSION_EVENT_BROWSER_MAX_COUNT = 10_000;
|
|
113
|
-
export const SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES = 96 * 1024;
|
|
118
|
+
// Durable history remains on the server and is available through bounded
|
|
119
|
+
// navigation. Keep the live browser working set small across multiple tabs;
|
|
120
|
+
// serialized payloads also expand into objects and timeline projections.
|
|
121
|
+
export const SESSION_EVENT_BROWSER_MAX_BYTES = 16 * 1024 * 1024;
|
|
122
|
+
export const SESSION_EVENT_BROWSER_MAX_COUNT = 20_000;
|
|
114
123
|
export const SESSION_EVENT_BROWSER_PENDING_MAX_BYTES = 1024 * 1024;
|
|
115
124
|
export const SESSION_EVENT_BROWSER_PENDING_MAX_COUNT = 256;
|
|
116
125
|
|
|
@@ -143,20 +152,47 @@ export function useSessionEvents(
|
|
|
143
152
|
const replay = options.replay ?? "windowed";
|
|
144
153
|
const fullReplay = replay === "full" || after !== 0;
|
|
145
154
|
const streamKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${after}\u0000${fullReplay ? "full" : "windowed"}`;
|
|
155
|
+
// Bind callbacks to this read lifetime, including a return to a previous
|
|
156
|
+
// identity. Publish during render so old callbacks are fenced before passive
|
|
157
|
+
// cleanup can advance the navigation generation.
|
|
158
|
+
const navigationIdentityRef = useRef({ client, streamKey, enabled });
|
|
159
|
+
if (
|
|
160
|
+
navigationIdentityRef.current.client !== client ||
|
|
161
|
+
navigationIdentityRef.current.streamKey !== streamKey ||
|
|
162
|
+
navigationIdentityRef.current.enabled !== enabled
|
|
163
|
+
) {
|
|
164
|
+
navigationIdentityRef.current = { client, streamKey, enabled };
|
|
165
|
+
}
|
|
166
|
+
const navigationIdentity = navigationIdentityRef.current;
|
|
167
|
+
const ownsNavigation = useCallback(
|
|
168
|
+
() => navigationIdentityRef.current === navigationIdentity,
|
|
169
|
+
[navigationIdentity],
|
|
170
|
+
);
|
|
146
171
|
|
|
147
172
|
const [eventWindow, setEventWindow] = useState<BrowserSessionEventWindow>(EMPTY_EVENT_WINDOW);
|
|
173
|
+
// One navigation witness, not a second history cache. Raw events stay contiguous.
|
|
174
|
+
const [questionEvidence, setQuestionEvidence] = useState<{
|
|
175
|
+
client: EmbeddedSessionClientLike;
|
|
176
|
+
streamKey: string;
|
|
177
|
+
anchor: number;
|
|
178
|
+
events: SessionEvent[];
|
|
179
|
+
} | null>(null);
|
|
148
180
|
const [connectionState, setConnectionState] = useState<SessionEventsConnectionState>("idle");
|
|
149
181
|
const [error, setError] = useState<Error | null>(null);
|
|
182
|
+
const [newerError, setNewerError] = useState<Error | null>(null);
|
|
150
183
|
const [hasOlder, setHasOlder] = useState(false);
|
|
151
184
|
const [hasNewer, setHasNewer] = useState(false);
|
|
152
185
|
const [sessionStatusProjection, setSessionStatusProjection] = useState<SessionStatus | null>(
|
|
153
186
|
null,
|
|
154
187
|
);
|
|
155
188
|
const [initialLoading, setInitialLoading] = useState(true);
|
|
189
|
+
const [initialHistoryReady, setInitialHistoryReady] = useState(false);
|
|
156
190
|
const [loadingOlder, setLoadingOlder] = useState(false);
|
|
157
191
|
const [loadingNewer, setLoadingNewer] = useState(false);
|
|
158
192
|
const [loadingOldest, setLoadingOldest] = useState(false);
|
|
159
193
|
const [loadingLatest, setLoadingLatest] = useState(false);
|
|
194
|
+
const [loadingTarget, setLoadingTarget] = useState(false);
|
|
195
|
+
const loadingTargetRef = useRef(false);
|
|
160
196
|
const [streamEpoch, setStreamEpoch] = useState(0);
|
|
161
197
|
// "history" = reading a non-tip window; live SSE must not fill the gap.
|
|
162
198
|
const [viewMode, setViewMode] = useState<"live" | "history">("live");
|
|
@@ -172,9 +208,11 @@ export function useSessionEvents(
|
|
|
172
208
|
const loadingLatestRef = useRef(false);
|
|
173
209
|
const viewModeRef = useRef<"live" | "history">("live");
|
|
174
210
|
const initialWindowLoadedRef = useRef(false);
|
|
211
|
+
const reconcileAfterPageResumeRef = useRef(false);
|
|
175
212
|
const streamAbortRef = useRef<AbortController | null>(null);
|
|
176
213
|
const streamKeyRef = useRef<string | null>(null);
|
|
177
214
|
const generationRef = useRef(0);
|
|
215
|
+
const navigationGenerationRef = useRef(0);
|
|
178
216
|
const eventWindowRef = useRef<BrowserSessionEventWindow>(EMPTY_EVENT_WINDOW);
|
|
179
217
|
const sessionStatusRef = useRef<{
|
|
180
218
|
sequence: number;
|
|
@@ -187,6 +225,25 @@ export function useSessionEvents(
|
|
|
187
225
|
// new stream identity cannot expose the previous session's event log.
|
|
188
226
|
const [stateStreamKey, setStateStreamKey] = useState(streamKey);
|
|
189
227
|
|
|
228
|
+
// Navigation belongs to the session/client lifetime, not the transport: an
|
|
229
|
+
// SSE reconnect must never cancel an in-flight history page.
|
|
230
|
+
useEffect(() => {
|
|
231
|
+
navigationGenerationRef.current += 1;
|
|
232
|
+
setNewerError(null);
|
|
233
|
+
loadingOlderRef.current = false;
|
|
234
|
+
loadingNewerRef.current = false;
|
|
235
|
+
loadingOldestRef.current = false;
|
|
236
|
+
loadingTargetRef.current = false;
|
|
237
|
+
setQuestionEvidence(null);
|
|
238
|
+
setLoadingTarget(false);
|
|
239
|
+
setLoadingOlder(false);
|
|
240
|
+
setLoadingNewer(false);
|
|
241
|
+
setLoadingOldest(false);
|
|
242
|
+
return () => {
|
|
243
|
+
navigationGenerationRef.current += 1;
|
|
244
|
+
};
|
|
245
|
+
}, [client, workspaceId, sessionId, after, enabled, fullReplay]);
|
|
246
|
+
|
|
190
247
|
useEffect(() => {
|
|
191
248
|
// Reset the accumulated log only when the stream identity changes —
|
|
192
249
|
// pausing via `enabled: false` keeps the timeline visible.
|
|
@@ -205,6 +262,7 @@ export function useSessionEvents(
|
|
|
205
262
|
setLoadingOldest(false);
|
|
206
263
|
setLoadingLatest(false);
|
|
207
264
|
setInitialLoading(true);
|
|
265
|
+
setInitialHistoryReady(false);
|
|
208
266
|
lastSequenceRef.current = after;
|
|
209
267
|
streamResumeSequenceRef.current = after;
|
|
210
268
|
oldestSequenceRef.current = null;
|
|
@@ -218,29 +276,50 @@ export function useSessionEvents(
|
|
|
218
276
|
viewModeRef.current = "live";
|
|
219
277
|
setViewMode("live");
|
|
220
278
|
initialWindowLoadedRef.current = false;
|
|
279
|
+
reconcileAfterPageResumeRef.current = false;
|
|
221
280
|
}
|
|
222
281
|
// AbortController is advisory: custom SDK clients and async iterators may
|
|
223
282
|
// ignore it and resolve/yield after cleanup. Fence every effect instance so
|
|
224
283
|
// only the newest dependency generation can mutate refs or React state.
|
|
225
284
|
const generation = generationRef.current + 1;
|
|
226
285
|
generationRef.current = generation;
|
|
227
|
-
if (loadingOlderRef.current) {
|
|
228
|
-
loadingOlderRef.current = false;
|
|
229
|
-
setLoadingOlder(false);
|
|
230
|
-
}
|
|
231
286
|
if (!sessionId || !streamEnabled) {
|
|
287
|
+
// A page that stayed hidden beyond the live-activity grace deliberately
|
|
288
|
+
// closed its SSE connection. Replaying from the old cursor on return can
|
|
289
|
+
// drip a large background backlog through React in many fast batches and
|
|
290
|
+
// leave the pinned camera catching up long after the user is foregrounded.
|
|
291
|
+
// Remember that suspension so the next live effect replaces the browser
|
|
292
|
+
// window with one compact durable tail instead.
|
|
293
|
+
if (
|
|
294
|
+
sessionId &&
|
|
295
|
+
enabled &&
|
|
296
|
+
!pageLive &&
|
|
297
|
+
!fullReplay &&
|
|
298
|
+
viewModeRef.current === "live" &&
|
|
299
|
+
initialWindowLoadedRef.current
|
|
300
|
+
) {
|
|
301
|
+
reconcileAfterPageResumeRef.current = true;
|
|
302
|
+
}
|
|
232
303
|
setConnectionState("idle");
|
|
233
304
|
return;
|
|
234
305
|
}
|
|
235
306
|
// History view owns a non-tip window; do not open SSE (it would replay the
|
|
236
307
|
// entire gap into the browser). jumpToLatest / catching loadNewer resume live.
|
|
237
308
|
if (viewMode === "history") {
|
|
309
|
+
reconcileAfterPageResumeRef.current = false;
|
|
238
310
|
setConnectionState("idle");
|
|
239
311
|
setInitialLoading(false);
|
|
240
312
|
return;
|
|
241
313
|
}
|
|
314
|
+
const reconcileForegroundResume = reconcileAfterPageResumeRef.current && !fullReplay;
|
|
315
|
+
reconcileAfterPageResumeRef.current = false;
|
|
242
316
|
const controller = new AbortController();
|
|
243
|
-
const isCurrent = () =>
|
|
317
|
+
const isCurrent = () =>
|
|
318
|
+
generationRef.current === generation &&
|
|
319
|
+
!controller.signal.aborted &&
|
|
320
|
+
navigationIdentityRef.current.client === client &&
|
|
321
|
+
navigationIdentityRef.current.streamKey === streamKey &&
|
|
322
|
+
navigationIdentityRef.current.enabled === enabled;
|
|
244
323
|
streamAbortRef.current = controller;
|
|
245
324
|
// Batch yielded events into one React update per flush window so a long
|
|
246
325
|
// replay (thousands of events) does not render per event. Project every
|
|
@@ -283,9 +362,12 @@ export function useSessionEvents(
|
|
|
283
362
|
batchResumeSequence,
|
|
284
363
|
);
|
|
285
364
|
}
|
|
286
|
-
observeSessionStatus(batch, sessionStatusRef
|
|
365
|
+
const status = observeSessionStatus(batch, sessionStatusRef);
|
|
366
|
+
if (status !== undefined) {
|
|
367
|
+
setSessionStatusProjection(status);
|
|
368
|
+
}
|
|
287
369
|
const current = eventWindowRef.current;
|
|
288
|
-
const next =
|
|
370
|
+
const next = appendBrowserSessionEventWindow(current, batch);
|
|
289
371
|
const retained = {
|
|
290
372
|
...next,
|
|
291
373
|
truncated: current.truncated || next.truncated,
|
|
@@ -293,7 +375,7 @@ export function useSessionEvents(
|
|
|
293
375
|
eventWindowRef.current = retained;
|
|
294
376
|
setEventWindow(retained);
|
|
295
377
|
oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
|
|
296
|
-
newestSequenceRef.current = retained.events
|
|
378
|
+
newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
|
|
297
379
|
if (retained.truncated) {
|
|
298
380
|
hasOlderRef.current = true;
|
|
299
381
|
setHasOlder(true);
|
|
@@ -306,6 +388,56 @@ export function useSessionEvents(
|
|
|
306
388
|
|
|
307
389
|
void (async () => {
|
|
308
390
|
try {
|
|
391
|
+
if (reconcileForegroundResume) {
|
|
392
|
+
setConnectionState("connecting");
|
|
393
|
+
const cursor = streamResumeSequenceRef.current;
|
|
394
|
+
let plan: ForegroundCatchupPlan = { kind: "resume" };
|
|
395
|
+
try {
|
|
396
|
+
plan = await planForegroundCatchup(client, workspaceId, sessionId, cursor, {
|
|
397
|
+
signal: controller.signal,
|
|
398
|
+
});
|
|
399
|
+
} catch {
|
|
400
|
+
if (controller.signal.aborted) return;
|
|
401
|
+
// This read is an optimization gate, not a new availability
|
|
402
|
+
// dependency. If it fails, preserve the SDK's exact cursor replay
|
|
403
|
+
// rather than blanking a usable timeline or failing the stream.
|
|
404
|
+
plan = { kind: "resume" };
|
|
405
|
+
}
|
|
406
|
+
if (!isCurrent()) return;
|
|
407
|
+
if (plan.kind === "append") {
|
|
408
|
+
const current = eventWindowRef.current;
|
|
409
|
+
assertAppendOrder(current.events, plan.events);
|
|
410
|
+
const status = observeSessionStatus(plan.events, sessionStatusRef);
|
|
411
|
+
const next = appendBrowserSessionEventWindow(current, plan.events);
|
|
412
|
+
const retained = {
|
|
413
|
+
...next,
|
|
414
|
+
truncated: current.truncated || next.truncated,
|
|
415
|
+
};
|
|
416
|
+
eventWindowRef.current = retained;
|
|
417
|
+
oldestSequenceRef.current = retained.events[0]?.sequence ?? null;
|
|
418
|
+
newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
|
|
419
|
+
lastSequenceRef.current = Math.max(lastSequenceRef.current, plan.resumeSequence);
|
|
420
|
+
streamResumeSequenceRef.current = Math.max(
|
|
421
|
+
streamResumeSequenceRef.current,
|
|
422
|
+
plan.resumeSequence,
|
|
423
|
+
);
|
|
424
|
+
if (status !== undefined) {
|
|
425
|
+
setSessionStatusProjection(status);
|
|
426
|
+
}
|
|
427
|
+
setEventWindow(retained);
|
|
428
|
+
if (retained.truncated) {
|
|
429
|
+
hasOlderRef.current = true;
|
|
430
|
+
setHasOlder(true);
|
|
431
|
+
}
|
|
432
|
+
} else if (plan.kind === "reload") {
|
|
433
|
+
// A large/complex missed window would make the foreground timeline
|
|
434
|
+
// and pinned camera chase many rapid commits. Keep the last known
|
|
435
|
+
// complete window visible until the bounded latest replacement is
|
|
436
|
+
// ready, then install that replacement atomically below.
|
|
437
|
+
initialWindowLoadedRef.current = false;
|
|
438
|
+
setError(null);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
309
441
|
if (!fullReplay && !initialWindowLoadedRef.current) {
|
|
310
442
|
setConnectionState("connecting");
|
|
311
443
|
// First paint is ONE compact fetch — the newest window, revealed at
|
|
@@ -313,25 +445,42 @@ export function useSessionEvents(
|
|
|
313
445
|
// reader actually scrolls up (the sentinel drives loadOlder).
|
|
314
446
|
const window = await loadEventWindow(client, workspaceId, sessionId, {
|
|
315
447
|
before: Number.MAX_SAFE_INTEGER,
|
|
316
|
-
pageSize:
|
|
448
|
+
pageSize: SESSION_HISTORY_PAGE_SIZE,
|
|
317
449
|
targetGroups: Number.POSITIVE_INFINITY,
|
|
318
450
|
maxFetches: INITIAL_FETCH_CAP,
|
|
451
|
+
boundaryPageCap: BOUNDARY_PAGE_CAP,
|
|
319
452
|
signal: controller.signal,
|
|
320
453
|
});
|
|
321
454
|
if (!isCurrent()) {
|
|
322
455
|
return;
|
|
323
456
|
}
|
|
324
|
-
observeSessionStatus(window.events, sessionStatusRef
|
|
457
|
+
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
325
458
|
const retained = boundBrowserSessionEventWindow(window.events);
|
|
459
|
+
// A replacement tail retires requests against the discarded window.
|
|
460
|
+
// Ordinary SSE reconnects preserve navigation, but splicing an old
|
|
461
|
+
// page into this new tail could leave an inaccessible history gap.
|
|
462
|
+
navigationGenerationRef.current += 1;
|
|
463
|
+
loadingOlderRef.current = false;
|
|
464
|
+
loadingNewerRef.current = false;
|
|
465
|
+
loadingOldestRef.current = false;
|
|
466
|
+
setLoadingOlder(false);
|
|
467
|
+
setLoadingNewer(false);
|
|
468
|
+
setLoadingOldest(false);
|
|
326
469
|
eventWindowRef.current = retained;
|
|
327
|
-
setEventWindow(retained);
|
|
328
470
|
oldestSequenceRef.current = retained.events[0]?.sequence ?? window.oldestSequence;
|
|
329
|
-
newestSequenceRef.current =
|
|
471
|
+
newestSequenceRef.current =
|
|
472
|
+
maxResumeSequenceOrNull(retained.events) ?? window.newestSequence;
|
|
330
473
|
hasOlderRef.current = window.hasOlder || retained.truncated;
|
|
331
474
|
hasNewerRef.current = false;
|
|
332
475
|
lastSequenceRef.current = window.newestSequence;
|
|
333
476
|
streamResumeSequenceRef.current = window.newestSequence;
|
|
334
477
|
initialWindowLoadedRef.current = true;
|
|
478
|
+
setInitialHistoryReady(true);
|
|
479
|
+
setError(null);
|
|
480
|
+
if (status !== undefined) {
|
|
481
|
+
setSessionStatusProjection(status);
|
|
482
|
+
}
|
|
483
|
+
setEventWindow(retained);
|
|
335
484
|
setHasOlder(window.hasOlder || retained.truncated);
|
|
336
485
|
setHasNewer(false);
|
|
337
486
|
setInitialLoading(false);
|
|
@@ -350,7 +499,7 @@ export function useSessionEvents(
|
|
|
350
499
|
.then(() => reconcileSession(sessionId))
|
|
351
500
|
.catch((cause) => {
|
|
352
501
|
if (isCurrent()) {
|
|
353
|
-
setError(
|
|
502
|
+
setError(normalizeError(cause));
|
|
354
503
|
}
|
|
355
504
|
});
|
|
356
505
|
},
|
|
@@ -362,7 +511,7 @@ export function useSessionEvents(
|
|
|
362
511
|
});
|
|
363
512
|
for await (const event of stream) {
|
|
364
513
|
if (!isCurrent()) break;
|
|
365
|
-
const boundedEvent =
|
|
514
|
+
const boundedEvent = event;
|
|
366
515
|
const boundedEventBytes = browserJsonBytes(boundedEvent);
|
|
367
516
|
const separatorBytes = pending.length === 0 ? 0 : 1;
|
|
368
517
|
if (
|
|
@@ -391,8 +540,12 @@ export function useSessionEvents(
|
|
|
391
540
|
} catch (cause) {
|
|
392
541
|
if (isCurrent()) {
|
|
393
542
|
flush();
|
|
394
|
-
setError(
|
|
543
|
+
setError(normalizeError(cause));
|
|
395
544
|
setConnectionState("error");
|
|
545
|
+
// A failed first compact-tail request has no later success path in
|
|
546
|
+
// this effect instance. End the loading gate so hosts can render the
|
|
547
|
+
// retryable error instead of an infinite initial spinner.
|
|
548
|
+
setInitialLoading(false);
|
|
396
549
|
}
|
|
397
550
|
}
|
|
398
551
|
})();
|
|
@@ -414,6 +567,8 @@ export function useSessionEvents(
|
|
|
414
567
|
workspaceId,
|
|
415
568
|
sessionId,
|
|
416
569
|
after,
|
|
570
|
+
enabled,
|
|
571
|
+
pageLive,
|
|
417
572
|
streamEnabled,
|
|
418
573
|
fullReplay,
|
|
419
574
|
streamKey,
|
|
@@ -423,6 +578,7 @@ export function useSessionEvents(
|
|
|
423
578
|
]);
|
|
424
579
|
|
|
425
580
|
const navigationBusy = (): boolean =>
|
|
581
|
+
loadingTargetRef.current ||
|
|
426
582
|
loadingOlderRef.current ||
|
|
427
583
|
loadingNewerRef.current ||
|
|
428
584
|
loadingOldestRef.current ||
|
|
@@ -430,8 +586,8 @@ export function useSessionEvents(
|
|
|
430
586
|
|
|
431
587
|
const loadOlder = useCallback(
|
|
432
588
|
(): OlderHistoryLoadReceipt =>
|
|
433
|
-
createOlderHistoryLoadReceipt(async (markCommitted) => {
|
|
434
|
-
if (!sessionId || navigationBusy() || !hasOlderRef.current) {
|
|
589
|
+
createOlderHistoryLoadReceipt(async (markCommitted, preserveTail, markTailPreserved) => {
|
|
590
|
+
if (!ownsNavigation() || !sessionId || navigationBusy() || !hasOlderRef.current) {
|
|
435
591
|
return false;
|
|
436
592
|
}
|
|
437
593
|
const before = oldestSequenceRef.current;
|
|
@@ -441,17 +597,20 @@ export function useSessionEvents(
|
|
|
441
597
|
setHasOlder(false);
|
|
442
598
|
return false;
|
|
443
599
|
}
|
|
444
|
-
const generation =
|
|
600
|
+
const generation = navigationGenerationRef.current;
|
|
601
|
+
const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
|
|
445
602
|
loadingOlderRef.current = true;
|
|
446
603
|
setLoadingOlder(true);
|
|
604
|
+
let published = false;
|
|
447
605
|
try {
|
|
448
606
|
const window = await loadEventWindow(client, workspaceId, sessionId, {
|
|
449
607
|
before,
|
|
450
|
-
pageSize:
|
|
608
|
+
pageSize: SESSION_HISTORY_PAGE_SIZE,
|
|
451
609
|
targetGroups: OLDER_GROUP_TARGET,
|
|
452
610
|
maxFetches: OLDER_FETCH_CAP,
|
|
611
|
+
isCurrent,
|
|
453
612
|
});
|
|
454
|
-
if (
|
|
613
|
+
if (!isCurrent()) {
|
|
455
614
|
return false;
|
|
456
615
|
}
|
|
457
616
|
if (window.events.length === 0) {
|
|
@@ -462,14 +621,16 @@ export function useSessionEvents(
|
|
|
462
621
|
}
|
|
463
622
|
const current = eventWindowRef.current;
|
|
464
623
|
assertPrependOrder(current.events, window.events);
|
|
465
|
-
// Freeze the live iterator before replacing its in-memory window. Rows
|
|
466
|
-
// pending in the aborted iterator were never cursor-committed and will
|
|
467
|
-
// be replayed from the retained high-water mark below.
|
|
468
|
-
streamAbortRef.current?.abort();
|
|
469
|
-
observeSessionStatus(window.events, sessionStatusRef, setSessionStatusProjection);
|
|
470
624
|
const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
|
|
471
625
|
direction: "oldest",
|
|
472
626
|
});
|
|
627
|
+
if (preserveTail && maxResumeSequence(next.events) < maxResumeSequence(current.events)) {
|
|
628
|
+
// Automatic viewport filling must not navigate away from the
|
|
629
|
+
// reader's retained tail. Explicit history navigation may do so.
|
|
630
|
+
markTailPreserved();
|
|
631
|
+
return false;
|
|
632
|
+
}
|
|
633
|
+
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
473
634
|
const retained = {
|
|
474
635
|
...next,
|
|
475
636
|
truncated: current.truncated || next.truncated,
|
|
@@ -478,54 +639,99 @@ export function useSessionEvents(
|
|
|
478
639
|
if (retainedOldest === null || retainedOldest >= before) {
|
|
479
640
|
throw new Error("@opengeni/react: loadOlder made no durable sequence progress");
|
|
480
641
|
}
|
|
481
|
-
const retainedNewest = retained.events
|
|
642
|
+
const retainedNewest = maxResumeSequenceOrNull(retained.events);
|
|
643
|
+
const previousNewest = maxResumeSequenceOrNull(current.events);
|
|
644
|
+
const evictedLiveTail =
|
|
645
|
+
previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
|
|
646
|
+
if (evictedLiveTail && viewModeRef.current === "live") {
|
|
647
|
+
// The prepend leaves the live tip. Freeze the iterator before the
|
|
648
|
+
// window is replaced so a late flush cannot append live rows onto
|
|
649
|
+
// (and newest-bound away) the history the reader just requested.
|
|
650
|
+
streamAbortRef.current?.abort();
|
|
651
|
+
}
|
|
482
652
|
eventWindowRef.current = retained;
|
|
483
653
|
markCommitted();
|
|
484
|
-
setEventWindow(retained);
|
|
485
654
|
oldestSequenceRef.current = retainedOldest;
|
|
486
655
|
newestSequenceRef.current = retainedNewest;
|
|
487
|
-
|
|
656
|
+
if (evictedLiveTail) {
|
|
657
|
+
streamResumeSequenceRef.current = maxResumeSequence(retained.events);
|
|
658
|
+
}
|
|
488
659
|
// Oldest-directed eviction can discard newer in-memory rows. That fact
|
|
489
660
|
// keeps windowTruncated true, but it does not imply older durable rows
|
|
490
661
|
// exist; only the backward DB page can answer hasOlder truthfully.
|
|
491
662
|
const olderStillAvailable = window.hasOlder;
|
|
492
663
|
hasOlderRef.current = olderStillAvailable;
|
|
493
|
-
|
|
494
|
-
|
|
664
|
+
let newer: boolean | null = null;
|
|
665
|
+
let enterHistory = false;
|
|
666
|
+
if (viewModeRef.current === "history" || evictedLiveTail) {
|
|
495
667
|
const highWater = lastSequenceRef.current;
|
|
496
|
-
|
|
497
|
-
retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
|
|
668
|
+
newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
|
|
498
669
|
hasNewerRef.current = newer;
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
670
|
+
if (evictedLiveTail) {
|
|
671
|
+
// A full oldest-directed browser window cannot also retain the
|
|
672
|
+
// live suffix. Keep the newly loaded history and page forward;
|
|
673
|
+
// reconnecting SSE here immediately newest-bounds the window and
|
|
674
|
+
// evicts every row the reader just requested.
|
|
675
|
+
viewModeRef.current = "history";
|
|
676
|
+
enterHistory = true;
|
|
677
|
+
}
|
|
504
678
|
}
|
|
679
|
+
// Otherwise the merge is still one contiguous live suffix and the open
|
|
680
|
+
// SSE iterator keeps appending to it: every flush reads
|
|
681
|
+
// eventWindowRef, so rows delivered during or after this fetch land
|
|
682
|
+
// once, in order, on the prepended window. Reopening the stream here
|
|
683
|
+
// would flash a connecting state and re-run session reconciliation on
|
|
684
|
+
// every scroll-up for no data benefit.
|
|
685
|
+
loadingOlderRef.current = false;
|
|
686
|
+
startTransition(() => {
|
|
687
|
+
if (status !== undefined) {
|
|
688
|
+
setSessionStatusProjection(status);
|
|
689
|
+
}
|
|
690
|
+
setEventWindow(retained);
|
|
691
|
+
setHasOlder(olderStillAvailable);
|
|
692
|
+
if (newer !== null) {
|
|
693
|
+
setHasNewer(newer);
|
|
694
|
+
}
|
|
695
|
+
if (enterHistory) {
|
|
696
|
+
setViewMode("history");
|
|
697
|
+
}
|
|
698
|
+
setLoadingOlder(false);
|
|
699
|
+
});
|
|
700
|
+
published = true;
|
|
505
701
|
return olderStillAvailable;
|
|
702
|
+
} catch (reason) {
|
|
703
|
+
if (!isCurrent()) return false;
|
|
704
|
+
throw reason;
|
|
506
705
|
} finally {
|
|
507
|
-
|
|
508
|
-
|
|
706
|
+
if (!published && isCurrent()) {
|
|
707
|
+
loadingOlderRef.current = false;
|
|
708
|
+
setLoadingOlder(false);
|
|
709
|
+
}
|
|
509
710
|
}
|
|
510
711
|
}),
|
|
511
|
-
[client, workspaceId, sessionId],
|
|
712
|
+
[client, workspaceId, sessionId, ownsNavigation],
|
|
512
713
|
);
|
|
513
714
|
|
|
514
715
|
const loadOldest = useCallback(async (): Promise<boolean> => {
|
|
515
|
-
if (!sessionId || navigationBusy() || !hasOlderRef.current) {
|
|
716
|
+
if (!ownsNavigation() || !sessionId || navigationBusy() || !hasOlderRef.current) {
|
|
516
717
|
return false;
|
|
517
718
|
}
|
|
518
|
-
|
|
719
|
+
// Explicit window replacement supersedes unresolved question destinations.
|
|
720
|
+
const generation = ++navigationGenerationRef.current;
|
|
721
|
+
const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
|
|
722
|
+
setQuestionEvidence(null);
|
|
519
723
|
loadingOldestRef.current = true;
|
|
520
724
|
setLoadingOldest(true);
|
|
725
|
+
let published = false;
|
|
521
726
|
try {
|
|
522
727
|
const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
|
|
523
728
|
after: 0,
|
|
524
|
-
pageSize:
|
|
729
|
+
pageSize: SESSION_HISTORY_PAGE_SIZE,
|
|
525
730
|
targetGroups: OLDEST_GROUP_TARGET,
|
|
526
731
|
maxFetches: OLDEST_FETCH_CAP,
|
|
732
|
+
isCurrent,
|
|
527
733
|
});
|
|
528
|
-
if (
|
|
734
|
+
if (!isCurrent()) {
|
|
529
735
|
return false;
|
|
530
736
|
}
|
|
531
737
|
if (window.events.length === 0) {
|
|
@@ -534,17 +740,15 @@ export function useSessionEvents(
|
|
|
534
740
|
return false;
|
|
535
741
|
}
|
|
536
742
|
streamAbortRef.current?.abort();
|
|
537
|
-
observeSessionStatus(window.events, sessionStatusRef
|
|
743
|
+
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
538
744
|
// Keep the oldest prefix — never the middle or tip.
|
|
539
745
|
const retained = boundBrowserSessionEventWindow(window.events, { direction: "oldest" });
|
|
540
746
|
const retainedOldest = retained.events[0]?.sequence ?? null;
|
|
541
|
-
const retainedNewest = retained.events
|
|
747
|
+
const retainedNewest = maxResumeSequenceOrNull(retained.events);
|
|
542
748
|
eventWindowRef.current = retained;
|
|
543
|
-
setEventWindow(retained);
|
|
544
749
|
oldestSequenceRef.current = retainedOldest;
|
|
545
750
|
newestSequenceRef.current = retainedNewest;
|
|
546
751
|
hasOlderRef.current = false;
|
|
547
|
-
setHasOlder(false);
|
|
548
752
|
const highWater = Math.max(lastSequenceRef.current, retainedNewest ?? 0);
|
|
549
753
|
lastSequenceRef.current = highWater;
|
|
550
754
|
const newer =
|
|
@@ -552,19 +756,37 @@ export function useSessionEvents(
|
|
|
552
756
|
retained.truncated ||
|
|
553
757
|
(retainedNewest !== null && retainedNewest < highWater);
|
|
554
758
|
hasNewerRef.current = newer;
|
|
555
|
-
setHasNewer(newer);
|
|
556
759
|
initialWindowLoadedRef.current = true;
|
|
760
|
+
setInitialHistoryReady(true);
|
|
761
|
+
setError(null);
|
|
557
762
|
viewModeRef.current = "history";
|
|
558
|
-
|
|
763
|
+
loadingOldestRef.current = false;
|
|
764
|
+
setNewerError(null);
|
|
765
|
+
startTransition(() => {
|
|
766
|
+
if (status !== undefined) {
|
|
767
|
+
setSessionStatusProjection(status);
|
|
768
|
+
}
|
|
769
|
+
setEventWindow(retained);
|
|
770
|
+
setHasOlder(false);
|
|
771
|
+
setHasNewer(newer);
|
|
772
|
+
setViewMode("history");
|
|
773
|
+
setLoadingOldest(false);
|
|
774
|
+
});
|
|
775
|
+
published = true;
|
|
559
776
|
return newer;
|
|
777
|
+
} catch (reason) {
|
|
778
|
+
if (!isCurrent()) return false;
|
|
779
|
+
throw reason;
|
|
560
780
|
} finally {
|
|
561
|
-
|
|
562
|
-
|
|
781
|
+
if (!published && isCurrent()) {
|
|
782
|
+
loadingOldestRef.current = false;
|
|
783
|
+
setLoadingOldest(false);
|
|
784
|
+
}
|
|
563
785
|
}
|
|
564
|
-
}, [client, workspaceId, sessionId]);
|
|
786
|
+
}, [client, workspaceId, sessionId, ownsNavigation]);
|
|
565
787
|
|
|
566
788
|
const loadNewer = useCallback(async (): Promise<boolean> => {
|
|
567
|
-
if (!sessionId || navigationBusy() || !hasNewerRef.current) {
|
|
789
|
+
if (!ownsNavigation() || !sessionId || navigationBusy() || !hasNewerRef.current) {
|
|
568
790
|
return false;
|
|
569
791
|
}
|
|
570
792
|
const afterSequence = newestSequenceRef.current;
|
|
@@ -573,20 +795,24 @@ export function useSessionEvents(
|
|
|
573
795
|
setHasNewer(false);
|
|
574
796
|
return false;
|
|
575
797
|
}
|
|
576
|
-
const generation =
|
|
798
|
+
const generation = navigationGenerationRef.current;
|
|
799
|
+
const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
|
|
577
800
|
loadingNewerRef.current = true;
|
|
578
801
|
setLoadingNewer(true);
|
|
802
|
+
let published = false;
|
|
579
803
|
try {
|
|
580
804
|
const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
|
|
581
805
|
after: afterSequence,
|
|
582
|
-
pageSize:
|
|
806
|
+
pageSize: SESSION_HISTORY_PAGE_SIZE,
|
|
583
807
|
targetGroups: NEWER_GROUP_TARGET,
|
|
584
808
|
maxFetches: NEWER_FETCH_CAP,
|
|
809
|
+
isCurrent,
|
|
585
810
|
});
|
|
586
|
-
if (
|
|
811
|
+
if (!isCurrent()) {
|
|
587
812
|
return false;
|
|
588
813
|
}
|
|
589
814
|
if (window.events.length === 0) {
|
|
815
|
+
setNewerError(null);
|
|
590
816
|
hasNewerRef.current = false;
|
|
591
817
|
setHasNewer(false);
|
|
592
818
|
// Caught up with durable history — resume the live tip stream.
|
|
@@ -598,23 +824,20 @@ export function useSessionEvents(
|
|
|
598
824
|
}
|
|
599
825
|
const current = eventWindowRef.current;
|
|
600
826
|
assertAppendOrder(current.events, window.events);
|
|
601
|
-
observeSessionStatus(window.events, sessionStatusRef
|
|
827
|
+
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
602
828
|
const previousOldest = current.events[0]?.sequence ?? null;
|
|
603
829
|
// Moving forward through history: keep the newest suffix of the merge.
|
|
604
|
-
const next =
|
|
605
|
-
direction: "newest",
|
|
606
|
-
});
|
|
830
|
+
const next = appendBrowserSessionEventWindow(current, window.events);
|
|
607
831
|
const retained = {
|
|
608
832
|
...next,
|
|
609
833
|
truncated: current.truncated || next.truncated || window.hasNewer,
|
|
610
834
|
};
|
|
611
835
|
const retainedOldest = retained.events[0]?.sequence ?? null;
|
|
612
|
-
const retainedNewest = retained.events
|
|
836
|
+
const retainedNewest = maxResumeSequenceOrNull(retained.events);
|
|
613
837
|
if (retainedNewest === null || retainedNewest <= afterSequence) {
|
|
614
838
|
throw new Error("@opengeni/react: loadNewer made no durable sequence progress");
|
|
615
839
|
}
|
|
616
840
|
eventWindowRef.current = retained;
|
|
617
|
-
setEventWindow(retained);
|
|
618
841
|
oldestSequenceRef.current = retainedOldest;
|
|
619
842
|
newestSequenceRef.current = retainedNewest;
|
|
620
843
|
// Evicting the start while paging forward re-arms hasOlder.
|
|
@@ -627,69 +850,261 @@ export function useSessionEvents(
|
|
|
627
850
|
retainedOldest > 1))
|
|
628
851
|
) {
|
|
629
852
|
hasOlderRef.current = true;
|
|
630
|
-
setHasOlder(true);
|
|
631
853
|
}
|
|
854
|
+
const rearmedOlder = hasOlderRef.current;
|
|
632
855
|
const highWater = Math.max(lastSequenceRef.current, retainedNewest);
|
|
633
856
|
lastSequenceRef.current = highWater;
|
|
634
857
|
const newer = window.hasNewer || retainedNewest < highWater;
|
|
635
858
|
hasNewerRef.current = newer;
|
|
636
|
-
|
|
859
|
+
let resumeLive = false;
|
|
637
860
|
if (!newer) {
|
|
638
861
|
streamResumeSequenceRef.current = retainedNewest;
|
|
639
862
|
viewModeRef.current = "live";
|
|
640
|
-
|
|
641
|
-
setStreamEpoch((epoch) => epoch + 1);
|
|
863
|
+
resumeLive = true;
|
|
642
864
|
}
|
|
865
|
+
loadingNewerRef.current = false;
|
|
866
|
+
setNewerError(null);
|
|
867
|
+
startTransition(() => {
|
|
868
|
+
if (status !== undefined) {
|
|
869
|
+
setSessionStatusProjection(status);
|
|
870
|
+
}
|
|
871
|
+
setEventWindow(retained);
|
|
872
|
+
setHasOlder(rearmedOlder);
|
|
873
|
+
setHasNewer(newer);
|
|
874
|
+
if (resumeLive) {
|
|
875
|
+
setViewMode("live");
|
|
876
|
+
setStreamEpoch((epoch) => epoch + 1);
|
|
877
|
+
}
|
|
878
|
+
setLoadingNewer(false);
|
|
879
|
+
});
|
|
880
|
+
published = true;
|
|
643
881
|
return newer;
|
|
882
|
+
} catch (reason) {
|
|
883
|
+
// A settled request from an old navigation lifetime is not a failure of
|
|
884
|
+
// the current session. Preserve current loading/error state as well.
|
|
885
|
+
if (!isCurrent()) {
|
|
886
|
+
return false;
|
|
887
|
+
}
|
|
888
|
+
setNewerError(normalizeError(reason));
|
|
889
|
+
// Keep authorization and integrity failures actionable for callers;
|
|
890
|
+
// timeline-owned invocations attach their own explicit recovery UI.
|
|
891
|
+
throw reason;
|
|
644
892
|
} finally {
|
|
645
|
-
|
|
646
|
-
|
|
893
|
+
if (!published && isCurrent()) {
|
|
894
|
+
loadingNewerRef.current = false;
|
|
895
|
+
setLoadingNewer(false);
|
|
896
|
+
}
|
|
647
897
|
}
|
|
648
|
-
}, [client, workspaceId, sessionId]);
|
|
898
|
+
}, [client, workspaceId, sessionId, ownsNavigation]);
|
|
899
|
+
|
|
900
|
+
const jumpToSequence = useCallback(
|
|
901
|
+
async (sequence: number, navigationOptions?: { signal?: AbortSignal }): Promise<boolean> => {
|
|
902
|
+
const signal = navigationOptions?.signal;
|
|
903
|
+
if (!ownsNavigation() || !sessionId || !Number.isSafeInteger(sequence) || sequence < 1)
|
|
904
|
+
return false;
|
|
905
|
+
// A navigation that was already cancelled (for example Find closed before
|
|
906
|
+
// the caller could react) must not disturb any current view state.
|
|
907
|
+
if (signal?.aborted) return false;
|
|
908
|
+
// Explicit targets supersede both other targets and adjacent-page requests.
|
|
909
|
+
const generation = ++navigationGenerationRef.current;
|
|
910
|
+
setQuestionEvidence(null);
|
|
911
|
+
const current = () => generation === navigationGenerationRef.current && ownsNavigation();
|
|
912
|
+
const previousMode = viewModeRef.current;
|
|
913
|
+
streamAbortRef.current?.abort();
|
|
914
|
+
generationRef.current += 1;
|
|
915
|
+
viewModeRef.current = "history";
|
|
916
|
+
setViewMode("history");
|
|
917
|
+
loadingOlderRef.current = loadingNewerRef.current = loadingOldestRef.current = false;
|
|
918
|
+
setLoadingOlder(false);
|
|
919
|
+
setLoadingNewer(false);
|
|
920
|
+
setLoadingOldest(false);
|
|
921
|
+
loadingTargetRef.current = true;
|
|
922
|
+
setLoadingTarget(true);
|
|
923
|
+
let published = false;
|
|
924
|
+
try {
|
|
925
|
+
const [before, following] = await Promise.all([
|
|
926
|
+
loadPreviousPage(client, workspaceId, sessionId, sequence + 1, { pageSize: 128 }),
|
|
927
|
+
loadNextPage(client, workspaceId, sessionId, sequence, { pageSize: 128 }),
|
|
928
|
+
]);
|
|
929
|
+
// Close-during-fetch fence: a pending jump must not replace the window
|
|
930
|
+
// after its owner (for example ConversationFind) has gone away. The
|
|
931
|
+
// finally block still settles loadingTarget for this generation.
|
|
932
|
+
if (signal?.aborted) return false;
|
|
933
|
+
if (!current()) return false;
|
|
934
|
+
if (!before.some((event) => event.sequence === sequence)) return false;
|
|
935
|
+
// Bound each side, keeping the target even if its message alone exceeds
|
|
936
|
+
// the ordinary byte budget. Never evict the very event being navigated to.
|
|
937
|
+
const prefix = boundBrowserSessionEventWindow(before, {
|
|
938
|
+
direction: "newest",
|
|
939
|
+
maxBytes: SESSION_EVENT_BROWSER_MAX_BYTES / 2,
|
|
940
|
+
});
|
|
941
|
+
const retained = boundBrowserSessionEventWindow([...prefix.events, ...following], {
|
|
942
|
+
direction: "oldest",
|
|
943
|
+
});
|
|
944
|
+
if (!retained.events.some((event) => event.sequence === sequence)) return false;
|
|
945
|
+
streamAbortRef.current?.abort();
|
|
946
|
+
generationRef.current += 1;
|
|
947
|
+
const status = observeSessionStatus(retained.events, sessionStatusRef);
|
|
948
|
+
eventWindowRef.current = retained;
|
|
949
|
+
oldestSequenceRef.current = retained.events[0]!.sequence;
|
|
950
|
+
newestSequenceRef.current = maxResumeSequenceOrNull(retained.events);
|
|
951
|
+
lastSequenceRef.current = Math.max(lastSequenceRef.current, newestSequenceRef.current ?? 0);
|
|
952
|
+
hasOlderRef.current = !isLogStart(retained.events[0]!);
|
|
953
|
+
// Compact/byte-bounded pages may be short despite more durable history.
|
|
954
|
+
// Only an empty forward page proves the end (same rule as loadNewer).
|
|
955
|
+
hasNewerRef.current =
|
|
956
|
+
following.length > 0 ||
|
|
957
|
+
retained.truncated ||
|
|
958
|
+
(newestSequenceRef.current ?? 0) < lastSequenceRef.current;
|
|
959
|
+
initialWindowLoadedRef.current = true;
|
|
960
|
+
setInitialHistoryReady(true);
|
|
961
|
+
setError(null);
|
|
962
|
+
viewModeRef.current = "history";
|
|
963
|
+
setEventWindow(retained);
|
|
964
|
+
setHasOlder(hasOlderRef.current);
|
|
965
|
+
setHasNewer(hasNewerRef.current);
|
|
966
|
+
setInitialLoading(false);
|
|
967
|
+
setViewMode("history");
|
|
968
|
+
setNewerError(null);
|
|
969
|
+
if (status !== undefined) setSessionStatusProjection(status);
|
|
970
|
+
published = true;
|
|
971
|
+
return true;
|
|
972
|
+
} catch (reason) {
|
|
973
|
+
if (!current() || signal?.aborted) return false;
|
|
974
|
+
throw reason;
|
|
975
|
+
} finally {
|
|
976
|
+
if (current()) {
|
|
977
|
+
loadingTargetRef.current = false;
|
|
978
|
+
setLoadingTarget(false);
|
|
979
|
+
if (!published && previousMode === "live") {
|
|
980
|
+
viewModeRef.current = "live";
|
|
981
|
+
setViewMode("live");
|
|
982
|
+
setStreamEpoch((epoch) => epoch + 1);
|
|
983
|
+
}
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
},
|
|
987
|
+
[client, workspaceId, sessionId, ownsNavigation],
|
|
988
|
+
);
|
|
989
|
+
|
|
990
|
+
const jumpToLatestQuestion = useCallback(
|
|
991
|
+
async (questionOptions?: LatestQuestionOptions): Promise<number | null> => {
|
|
992
|
+
if (!sessionId || !ownsNavigation()) return null;
|
|
993
|
+
let generation = navigationGenerationRef.current;
|
|
994
|
+
const current = () => ownsNavigation() && generation === navigationGenerationRef.current;
|
|
995
|
+
try {
|
|
996
|
+
// Optional action code stays off the session-open path. Capture identity
|
|
997
|
+
// before the import and fence its completion just like network reads.
|
|
998
|
+
const { resolveLatestQuestion } = await import("./latest-question");
|
|
999
|
+
if (!current()) return null;
|
|
1000
|
+
const destination = await resolveLatestQuestion({
|
|
1001
|
+
client,
|
|
1002
|
+
workspaceId,
|
|
1003
|
+
sessionId,
|
|
1004
|
+
isCurrent: current,
|
|
1005
|
+
resumeSequence: maxResumeSequence,
|
|
1006
|
+
options: questionOptions,
|
|
1007
|
+
});
|
|
1008
|
+
if (!destination || !current()) return null;
|
|
1009
|
+
if (!eventWindowRef.current.events.some((event) => event.sequence === destination.anchor)) {
|
|
1010
|
+
const navigation = jumpToSequence(destination.anchor);
|
|
1011
|
+
generation = navigationGenerationRef.current;
|
|
1012
|
+
if (!(await navigation) || !current()) return null;
|
|
1013
|
+
}
|
|
1014
|
+
setQuestionEvidence({
|
|
1015
|
+
client,
|
|
1016
|
+
streamKey,
|
|
1017
|
+
anchor: destination.anchor,
|
|
1018
|
+
events: destination.events,
|
|
1019
|
+
});
|
|
1020
|
+
return destination.questionSequence;
|
|
1021
|
+
} catch (reason) {
|
|
1022
|
+
if (!current()) return null;
|
|
1023
|
+
throw reason;
|
|
1024
|
+
}
|
|
1025
|
+
},
|
|
1026
|
+
[client, workspaceId, sessionId, streamKey, ownsNavigation, jumpToSequence],
|
|
1027
|
+
);
|
|
649
1028
|
|
|
650
1029
|
const jumpToLatest = useCallback(async (): Promise<void> => {
|
|
1030
|
+
// An old host retry closure must not clear a replacement session's error
|
|
1031
|
+
// or abort its live feed before the passive effect cleanup has run.
|
|
1032
|
+
if (!ownsNavigation()) return;
|
|
1033
|
+
if (loadingTargetRef.current) {
|
|
1034
|
+
navigationGenerationRef.current += 1;
|
|
1035
|
+
loadingTargetRef.current = false;
|
|
1036
|
+
setLoadingTarget(false);
|
|
1037
|
+
}
|
|
651
1038
|
if (!sessionId || navigationBusy()) {
|
|
652
1039
|
return;
|
|
653
1040
|
}
|
|
1041
|
+
navigationGenerationRef.current += 1;
|
|
1042
|
+
setQuestionEvidence(null);
|
|
654
1043
|
loadingLatestRef.current = true;
|
|
655
1044
|
setLoadingLatest(true);
|
|
1045
|
+
setNewerError(null);
|
|
1046
|
+
setError(null);
|
|
1047
|
+
let published = false;
|
|
656
1048
|
try {
|
|
657
1049
|
streamAbortRef.current?.abort();
|
|
658
1050
|
hasNewerRef.current = false;
|
|
659
|
-
setHasNewer(false);
|
|
660
1051
|
hasOlderRef.current = false;
|
|
661
|
-
setHasOlder(false);
|
|
662
1052
|
newestSequenceRef.current = null;
|
|
663
1053
|
oldestSequenceRef.current = null;
|
|
664
1054
|
eventWindowRef.current = EMPTY_EVENT_WINDOW;
|
|
665
|
-
setEventWindow(EMPTY_EVENT_WINDOW);
|
|
666
1055
|
initialWindowLoadedRef.current = false;
|
|
667
1056
|
streamResumeSequenceRef.current = after;
|
|
668
|
-
setInitialLoading(true);
|
|
669
1057
|
viewModeRef.current = "live";
|
|
670
|
-
|
|
671
|
-
|
|
1058
|
+
loadingLatestRef.current = false;
|
|
1059
|
+
startTransition(() => {
|
|
1060
|
+
setHasNewer(false);
|
|
1061
|
+
setHasOlder(false);
|
|
1062
|
+
setEventWindow(EMPTY_EVENT_WINDOW);
|
|
1063
|
+
setInitialLoading(true);
|
|
1064
|
+
setViewMode("live");
|
|
1065
|
+
setStreamEpoch((epoch) => epoch + 1);
|
|
1066
|
+
setLoadingLatest(false);
|
|
1067
|
+
});
|
|
1068
|
+
published = true;
|
|
672
1069
|
// Effect reloads the tip. The tip fetch itself is owned by the effect.
|
|
673
1070
|
await Promise.resolve();
|
|
674
1071
|
} finally {
|
|
675
|
-
|
|
676
|
-
|
|
1072
|
+
if (!published) {
|
|
1073
|
+
loadingLatestRef.current = false;
|
|
1074
|
+
setLoadingLatest(false);
|
|
1075
|
+
}
|
|
677
1076
|
}
|
|
678
|
-
}, [after, sessionId]);
|
|
1077
|
+
}, [after, sessionId, ownsNavigation]);
|
|
679
1078
|
|
|
680
1079
|
const identityMatches = stateStreamKey === streamKey;
|
|
681
1080
|
const visibleEvents = identityMatches ? eventWindow.events : EMPTY_EVENTS;
|
|
682
|
-
const timeline = useMemo(() =>
|
|
1081
|
+
const timeline = useMemo(() => {
|
|
1082
|
+
const witness =
|
|
1083
|
+
questionEvidence?.client === client &&
|
|
1084
|
+
questionEvidence.streamKey === streamKey &&
|
|
1085
|
+
visibleEvents.some((event) => event.sequence === questionEvidence.anchor)
|
|
1086
|
+
? questionEvidence.events
|
|
1087
|
+
: EMPTY_EVENTS;
|
|
1088
|
+
const timelineOptions = { partialStart: hasOlder || eventWindow.truncated || after > 0 };
|
|
1089
|
+
if (witness.length === 0) return buildTimeline(visibleEvents, timelineOptions);
|
|
1090
|
+
const ids = new Set(visibleEvents.map((event) => event.id));
|
|
1091
|
+
return buildTimeline(
|
|
1092
|
+
[...visibleEvents, ...witness.filter((event) => !ids.has(event.id))],
|
|
1093
|
+
timelineOptions,
|
|
1094
|
+
);
|
|
1095
|
+
}, [visibleEvents, hasOlder, eventWindow.truncated, after, questionEvidence, client, streamKey]);
|
|
683
1096
|
|
|
684
1097
|
return {
|
|
685
1098
|
events: visibleEvents,
|
|
686
1099
|
timeline,
|
|
687
1100
|
sessionStatus: identityMatches ? sessionStatusProjection : null,
|
|
1101
|
+
sessionStatusSequence: identityMatches ? sessionStatusRef.current.sequence : 0,
|
|
688
1102
|
connectionState: identityMatches ? connectionState : "idle",
|
|
689
1103
|
lastSequence: identityMatches ? lastSequenceRef.current : after,
|
|
690
1104
|
windowBytes: identityMatches ? eventWindow.bytes : 2,
|
|
691
1105
|
windowTruncated: identityMatches ? eventWindow.truncated : false,
|
|
692
1106
|
initialLoading: fullReplay ? false : identityMatches ? initialLoading : true,
|
|
1107
|
+
initialHistoryReady: identityMatches && initialHistoryReady,
|
|
693
1108
|
hasOlder: !identityMatches ? false : hasOlder,
|
|
694
1109
|
loadingOlder: !identityMatches ? false : loadingOlder,
|
|
695
1110
|
loadOlder,
|
|
@@ -700,7 +1115,10 @@ export function useSessionEvents(
|
|
|
700
1115
|
loadOldest,
|
|
701
1116
|
loadingLatest: !identityMatches ? false : loadingLatest,
|
|
702
1117
|
jumpToLatest,
|
|
703
|
-
|
|
1118
|
+
jumpToLatestQuestion,
|
|
1119
|
+
jumpToSequence,
|
|
1120
|
+
loadingTarget: identityMatches && loadingTarget,
|
|
1121
|
+
error: identityMatches ? (newerError ?? error) : null,
|
|
704
1122
|
};
|
|
705
1123
|
}
|
|
706
1124
|
|
|
@@ -709,9 +1127,10 @@ export function useSessionEvents(
|
|
|
709
1127
|
* accumulation retains the newest suffix; backward paging retains the oldest
|
|
710
1128
|
* prefix so newly fetched history cannot be immediately evicted. This is
|
|
711
1129
|
* deliberately separate from durable history and transport paging: when a
|
|
712
|
-
* backward page evicts the live tail, the hook
|
|
713
|
-
*
|
|
714
|
-
*
|
|
1130
|
+
* backward page evicts the live tail, the hook enters bounded history mode and
|
|
1131
|
+
* preserves the highest-ever-observed sequence separately for forward paging.
|
|
1132
|
+
* The source event remains durable in PostgreSQL throughout. An event larger
|
|
1133
|
+
* than the byte target is retained alone, never replaced by a lossy preview.
|
|
715
1134
|
*/
|
|
716
1135
|
export function boundBrowserSessionEventWindow(
|
|
717
1136
|
events: readonly SessionEvent[],
|
|
@@ -723,7 +1142,7 @@ export function boundBrowserSessionEventWindow(
|
|
|
723
1142
|
): BrowserSessionEventWindow {
|
|
724
1143
|
const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
|
|
725
1144
|
const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
|
|
726
|
-
const safe = events
|
|
1145
|
+
const safe = events;
|
|
727
1146
|
const selected: SessionEvent[] = [];
|
|
728
1147
|
let bytes = 2; // []
|
|
729
1148
|
const direction = options.direction ?? "newest";
|
|
@@ -734,7 +1153,7 @@ export function boundBrowserSessionEventWindow(
|
|
|
734
1153
|
const event = safe[index]!;
|
|
735
1154
|
const eventBytes = browserJsonBytes(event);
|
|
736
1155
|
const separator = selected.length === 0 ? 0 : 1;
|
|
737
|
-
if (bytes + separator + eventBytes > maxBytes) break;
|
|
1156
|
+
if (selected.length > 0 && bytes + separator + eventBytes > maxBytes) break;
|
|
738
1157
|
selected.push(event);
|
|
739
1158
|
bytes += separator + eventBytes;
|
|
740
1159
|
}
|
|
@@ -746,255 +1165,41 @@ export function boundBrowserSessionEventWindow(
|
|
|
746
1165
|
};
|
|
747
1166
|
}
|
|
748
1167
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
const
|
|
756
|
-
const
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
!event.type.includes("\n") &&
|
|
762
|
-
!event.type.includes("\r");
|
|
763
|
-
const clientEventId = boundBrowserOptionalText(
|
|
764
|
-
event.clientEventId,
|
|
765
|
-
BROWSER_EVENT_CLIENT_ID_MAX_BYTES,
|
|
766
|
-
);
|
|
767
|
-
const duplicateReason = boundBrowserOptionalText(
|
|
768
|
-
event.duplicateReason,
|
|
769
|
-
BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES,
|
|
770
|
-
);
|
|
771
|
-
|
|
772
|
-
if (
|
|
773
|
-
serialized.serializable &&
|
|
774
|
-
originalBytes !== null &&
|
|
775
|
-
originalBytes <= SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES &&
|
|
776
|
-
typeIsSafe &&
|
|
777
|
-
clientEventId === event.clientEventId &&
|
|
778
|
-
duplicateReason === event.duplicateReason
|
|
779
|
-
) {
|
|
780
|
-
return event;
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
const envelopeProjection = [
|
|
784
|
-
!typeIsSafe
|
|
785
|
-
? browserEnvelopeFieldProjection("type", event.type, "session.event.envelope_omitted")
|
|
786
|
-
: null,
|
|
787
|
-
clientEventId !== event.clientEventId
|
|
788
|
-
? browserEnvelopeFieldProjection("clientEventId", event.clientEventId, clientEventId)
|
|
789
|
-
: null,
|
|
790
|
-
duplicateReason !== event.duplicateReason
|
|
791
|
-
? browserEnvelopeFieldProjection("duplicateReason", event.duplicateReason, duplicateReason)
|
|
792
|
-
: null,
|
|
793
|
-
].filter((field) => field !== null);
|
|
794
|
-
const payloadSerialization = browserSerialize(event.payload);
|
|
795
|
-
const payloadBytes = payloadSerialization.serializable
|
|
796
|
-
? encoder.encode(payloadSerialization.value).byteLength
|
|
797
|
-
: null;
|
|
798
|
-
const preview = truncateBrowserUtf8Middle(
|
|
799
|
-
payloadSerialization.value,
|
|
800
|
-
BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES,
|
|
801
|
-
);
|
|
802
|
-
const truncation = {
|
|
803
|
-
truncated: true as const,
|
|
804
|
-
surface: "browser_legacy_guard" as const,
|
|
805
|
-
reason: serialized.serializable ? "event_envelope_bytes_exceeded" : "event_not_serializable",
|
|
806
|
-
originalBytes,
|
|
807
|
-
deliveredBytes: 0,
|
|
808
|
-
omittedBytes: originalBytes,
|
|
809
|
-
estimatedOriginalTokens: originalBytes === null ? null : Math.ceil(originalBytes / 4),
|
|
810
|
-
estimatedDeliveredTokens: 0,
|
|
811
|
-
fullEvidence: { available: false as const, reason: "not_retained" as const },
|
|
812
|
-
details: [
|
|
813
|
-
{
|
|
814
|
-
path: "$.payload",
|
|
815
|
-
kind: "payload_preview",
|
|
816
|
-
originalBytes: payloadBytes,
|
|
817
|
-
deliveredBytes: browserUtf8Bytes(preview),
|
|
818
|
-
},
|
|
819
|
-
],
|
|
820
|
-
};
|
|
821
|
-
const payload: Record<string, unknown> = {
|
|
822
|
-
...browserPayloadIdentity(event.payload),
|
|
823
|
-
preview,
|
|
824
|
-
...(envelopeProjection.length > 0
|
|
825
|
-
? {
|
|
826
|
-
originalType: boundBrowserText(event.type, BROWSER_EVENT_TYPE_MAX_BYTES),
|
|
827
|
-
envelopeProjection: {
|
|
828
|
-
truncated: true,
|
|
829
|
-
surface: "browser_legacy_guard",
|
|
830
|
-
fields: envelopeProjection,
|
|
831
|
-
},
|
|
832
|
-
}
|
|
833
|
-
: {}),
|
|
834
|
-
truncation,
|
|
835
|
-
};
|
|
836
|
-
const bounded: SessionEvent = {
|
|
837
|
-
id: boundBrowserText(event.id, BROWSER_EVENT_ID_MAX_BYTES),
|
|
838
|
-
workspaceId: boundBrowserText(event.workspaceId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
839
|
-
sessionId: boundBrowserText(event.sessionId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
840
|
-
sequence: event.sequence,
|
|
841
|
-
type: typeIsSafe ? event.type : "session.event.envelope_omitted",
|
|
842
|
-
payload,
|
|
843
|
-
occurredAt: boundBrowserText(event.occurredAt, BROWSER_EVENT_ID_MAX_BYTES),
|
|
844
|
-
clientEventId,
|
|
845
|
-
turnId: boundBrowserOptionalText(event.turnId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
846
|
-
turnGeneration: event.turnGeneration,
|
|
847
|
-
turnAttemptId: boundBrowserOptionalText(event.turnAttemptId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
848
|
-
turnAssociation: event.turnAssociation,
|
|
849
|
-
duplicateOfEventId: boundBrowserOptionalText(
|
|
850
|
-
event.duplicateOfEventId,
|
|
851
|
-
BROWSER_EVENT_ID_MAX_BYTES,
|
|
852
|
-
),
|
|
853
|
-
duplicateReason,
|
|
854
|
-
};
|
|
855
|
-
|
|
856
|
-
settleBrowserEventTruncation(bounded, truncation);
|
|
857
|
-
if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
|
|
858
|
-
payload.preview = truncateBrowserUtf8Middle(String(payload.preview), 4 * 1024);
|
|
859
|
-
truncation.details = truncation.details.slice(0, 1);
|
|
860
|
-
settleBrowserEventTruncation(bounded, truncation);
|
|
861
|
-
}
|
|
862
|
-
if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
|
|
863
|
-
bounded.clientEventId = null;
|
|
864
|
-
bounded.duplicateReason = null;
|
|
865
|
-
payload.preview = "[legacy event omitted at the browser byte boundary]";
|
|
866
|
-
settleBrowserEventTruncation(bounded, truncation);
|
|
867
|
-
}
|
|
868
|
-
return bounded;
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
function browserJsonBytes(value: unknown): number {
|
|
872
|
-
return encoder.encode(browserSerialize(value).value).byteLength;
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
function browserSerialize(value: unknown): { value: string; serializable: boolean } {
|
|
876
|
-
try {
|
|
877
|
-
const serialized = JSON.stringify(value);
|
|
878
|
-
return { value: serialized === undefined ? "null" : serialized, serializable: true };
|
|
879
|
-
} catch {
|
|
880
|
-
return {
|
|
881
|
-
value: '"[unserializable event payload omitted at browser boundary]"',
|
|
882
|
-
serializable: false,
|
|
883
|
-
};
|
|
1168
|
+
/** @internal Append immutable events without serializing the retained history again. */
|
|
1169
|
+
export function appendBrowserSessionEventWindow(
|
|
1170
|
+
current: BrowserSessionEventWindow,
|
|
1171
|
+
batch: readonly SessionEvent[],
|
|
1172
|
+
options: { maxBytes?: number; maxCount?: number } = {},
|
|
1173
|
+
): BrowserSessionEventWindow {
|
|
1174
|
+
const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
|
|
1175
|
+
const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
|
|
1176
|
+
const events = [...current.events, ...batch];
|
|
1177
|
+
let bytes = current.bytes;
|
|
1178
|
+
for (let index = 0; index < batch.length; index += 1) {
|
|
1179
|
+
bytes += browserJsonBytes(batch[index]) + (current.events.length + index > 0 ? 1 : 0);
|
|
884
1180
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
for (const field of BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS) {
|
|
892
|
-
const value = record[field];
|
|
893
|
-
if (typeof value === "string") {
|
|
894
|
-
identity[field] = boundBrowserText(value, BROWSER_EVENT_ID_MAX_BYTES);
|
|
895
|
-
} else if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
896
|
-
identity[field] = value;
|
|
897
|
-
}
|
|
1181
|
+
let start = 0;
|
|
1182
|
+
// Preserve the newest event even when it alone exceeds the byte target,
|
|
1183
|
+
// matching the full-window reducer and retaining exact cursor progress.
|
|
1184
|
+
while (events.length - start > 1 && (bytes > maxBytes || events.length - start > maxCount)) {
|
|
1185
|
+
bytes -= browserJsonBytes(events[start]) + 1;
|
|
1186
|
+
start += 1;
|
|
898
1187
|
}
|
|
899
|
-
return identity;
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
function browserEnvelopeFieldProjection(
|
|
903
|
-
field: string,
|
|
904
|
-
original: string | null | undefined,
|
|
905
|
-
delivered: string | null | undefined,
|
|
906
|
-
): { field: string; originalBytes: number; deliveredBytes: number } {
|
|
907
1188
|
return {
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
1189
|
+
events: start === 0 ? events : events.slice(start),
|
|
1190
|
+
bytes,
|
|
1191
|
+
truncated: current.truncated || start > 0,
|
|
911
1192
|
};
|
|
912
1193
|
}
|
|
913
1194
|
|
|
914
|
-
function
|
|
915
|
-
value
|
|
916
|
-
maxBytes: number,
|
|
917
|
-
): T {
|
|
918
|
-
return (typeof value === "string" ? boundBrowserText(value, maxBytes) : value) as T;
|
|
919
|
-
}
|
|
920
|
-
|
|
921
|
-
function boundBrowserText(value: string, maxBytes: number): string {
|
|
922
|
-
const bytes = encoder.encode(value);
|
|
923
|
-
if (bytes.byteLength <= maxBytes) return value;
|
|
924
|
-
const marker = "…[truncated]";
|
|
925
|
-
const prefixBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
|
|
926
|
-
let prefixEnd = Math.min(prefixBudget, bytes.byteLength);
|
|
927
|
-
while (
|
|
928
|
-
prefixEnd > 0 &&
|
|
929
|
-
prefixEnd < bytes.byteLength &&
|
|
930
|
-
isBrowserUtf8Continuation(bytes[prefixEnd]!)
|
|
931
|
-
) {
|
|
932
|
-
prefixEnd -= 1;
|
|
933
|
-
}
|
|
934
|
-
return `${decoder.decode(bytes.subarray(0, prefixEnd))}${marker}`;
|
|
935
|
-
}
|
|
936
|
-
|
|
937
|
-
function truncateBrowserUtf8Middle(value: string, maxBytes: number): string {
|
|
938
|
-
const bytes = encoder.encode(value);
|
|
939
|
-
if (bytes.byteLength <= maxBytes) return value;
|
|
940
|
-
const marker = `…[${bytes.byteLength - maxBytes} bytes omitted]…`;
|
|
941
|
-
const contentBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
|
|
942
|
-
const leftBudget = Math.floor(contentBudget / 2);
|
|
943
|
-
const rightBudget = contentBudget - leftBudget;
|
|
944
|
-
let leftEnd = Math.min(leftBudget, bytes.byteLength);
|
|
945
|
-
while (leftEnd > 0 && leftEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[leftEnd]!)) {
|
|
946
|
-
leftEnd -= 1;
|
|
947
|
-
}
|
|
948
|
-
let rightStart = Math.max(0, bytes.byteLength - rightBudget);
|
|
949
|
-
while (rightStart < bytes.byteLength && isBrowserUtf8Continuation(bytes[rightStart]!)) {
|
|
950
|
-
rightStart += 1;
|
|
951
|
-
}
|
|
952
|
-
return `${decoder.decode(bytes.subarray(0, leftEnd))}${marker}${decoder.decode(bytes.subarray(rightStart))}`;
|
|
953
|
-
}
|
|
954
|
-
|
|
955
|
-
function settleBrowserEventTruncation(
|
|
956
|
-
event: SessionEvent,
|
|
957
|
-
truncation: {
|
|
958
|
-
originalBytes: number | null;
|
|
959
|
-
deliveredBytes: number;
|
|
960
|
-
omittedBytes: number | null;
|
|
961
|
-
estimatedDeliveredTokens: number;
|
|
962
|
-
},
|
|
963
|
-
): void {
|
|
964
|
-
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
965
|
-
const deliveredBytes = browserJsonBytes(event);
|
|
966
|
-
const omittedBytes =
|
|
967
|
-
truncation.originalBytes === null
|
|
968
|
-
? null
|
|
969
|
-
: Math.max(0, truncation.originalBytes - deliveredBytes);
|
|
970
|
-
const estimatedDeliveredTokens = Math.ceil(deliveredBytes / 4);
|
|
971
|
-
if (
|
|
972
|
-
truncation.deliveredBytes === deliveredBytes &&
|
|
973
|
-
truncation.omittedBytes === omittedBytes &&
|
|
974
|
-
truncation.estimatedDeliveredTokens === estimatedDeliveredTokens
|
|
975
|
-
) {
|
|
976
|
-
return;
|
|
977
|
-
}
|
|
978
|
-
truncation.deliveredBytes = deliveredBytes;
|
|
979
|
-
truncation.omittedBytes = omittedBytes;
|
|
980
|
-
truncation.estimatedDeliveredTokens = estimatedDeliveredTokens;
|
|
981
|
-
}
|
|
982
|
-
throw new RangeError("Browser event byte accounting did not converge");
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
function browserUtf8Bytes(value: string): number {
|
|
986
|
-
return encoder.encode(value).byteLength;
|
|
987
|
-
}
|
|
988
|
-
|
|
989
|
-
function isBrowserUtf8Continuation(value: number): boolean {
|
|
990
|
-
return (value & 0xc0) === 0x80;
|
|
1195
|
+
function browserJsonBytes(value: unknown): number {
|
|
1196
|
+
return encoder.encode(JSON.stringify(value)).byteLength;
|
|
991
1197
|
}
|
|
992
1198
|
|
|
993
1199
|
function observeSessionStatus(
|
|
994
1200
|
events: readonly SessionEvent[],
|
|
995
1201
|
ref: { current: { sequence: number; status: SessionStatus | null } },
|
|
996
|
-
|
|
997
|
-
): void {
|
|
1202
|
+
): SessionStatus | undefined {
|
|
998
1203
|
let latest: { sequence: number; status: SessionStatus } | null = null;
|
|
999
1204
|
for (const event of events) {
|
|
1000
1205
|
const status = sessionStatusFromEvents([event]);
|
|
@@ -1002,9 +1207,71 @@ function observeSessionStatus(
|
|
|
1002
1207
|
latest = { sequence: event.sequence, status };
|
|
1003
1208
|
}
|
|
1004
1209
|
}
|
|
1005
|
-
if (!latest || latest.sequence < ref.current.sequence) return;
|
|
1210
|
+
if (!latest || latest.sequence < ref.current.sequence) return undefined;
|
|
1006
1211
|
ref.current = latest;
|
|
1007
|
-
|
|
1212
|
+
return latest.status;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
type ForegroundCatchupPlan =
|
|
1216
|
+
| { kind: "resume" }
|
|
1217
|
+
| { kind: "append"; events: SessionEvent[]; resumeSequence: number }
|
|
1218
|
+
| { kind: "reload" };
|
|
1219
|
+
|
|
1220
|
+
/**
|
|
1221
|
+
* Decide how a sustained hidden-tab suspension rejoins the durable event log.
|
|
1222
|
+
*
|
|
1223
|
+
* `lastSequence - cursor` is the exact raw durable work missed, but it is not
|
|
1224
|
+
* the number of visible messages: one streaming answer can own thousands of
|
|
1225
|
+
* adjacent delta rows. Small raw gaps therefore resume directly; medium gaps
|
|
1226
|
+
* get one forward compact page and are judged by the browser-visible result;
|
|
1227
|
+
* a page that cannot prove complete bounded coverage, or that would add too
|
|
1228
|
+
* many rows/groups/bytes, reloads the latest tail instead.
|
|
1229
|
+
*/
|
|
1230
|
+
async function planForegroundCatchup(
|
|
1231
|
+
client: EmbeddedSessionClientLike,
|
|
1232
|
+
workspaceId: string,
|
|
1233
|
+
sessionId: string,
|
|
1234
|
+
cursor: number,
|
|
1235
|
+
options: { signal?: AbortSignal } = {},
|
|
1236
|
+
): Promise<ForegroundCatchupPlan> {
|
|
1237
|
+
if (options.signal?.aborted) throw abortError();
|
|
1238
|
+
const session = await client.getSession(workspaceId, sessionId, {
|
|
1239
|
+
fresh: true,
|
|
1240
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
1241
|
+
});
|
|
1242
|
+
if (options.signal?.aborted) throw abortError();
|
|
1243
|
+
const durableHead = Math.max(cursor, session.lastSequence);
|
|
1244
|
+
const rawGap = durableHead - cursor;
|
|
1245
|
+
if (rawGap <= FOREGROUND_DIRECT_REPLAY_MAX_SEQUENCES) {
|
|
1246
|
+
return { kind: "resume" };
|
|
1247
|
+
}
|
|
1248
|
+
if (rawGap > FOREGROUND_COMPACT_PROBE_MAX_SEQUENCES) {
|
|
1249
|
+
return { kind: "reload" };
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
const page = await loadNextPage(client, workspaceId, sessionId, cursor, {
|
|
1253
|
+
pageSize: rawGap,
|
|
1254
|
+
...(options.signal ? { signal: options.signal } : {}),
|
|
1255
|
+
});
|
|
1256
|
+
const events = page.filter((event) => eventResumeSequence(event) > cursor);
|
|
1257
|
+
if (events.length === 0) {
|
|
1258
|
+
return { kind: "reload" };
|
|
1259
|
+
}
|
|
1260
|
+
assertAscending(events);
|
|
1261
|
+
const resumeSequence = maxResumeSequence(events);
|
|
1262
|
+
if (resumeSequence < durableHead) {
|
|
1263
|
+
// Byte/count truncation or an incomplete projection means this page cannot
|
|
1264
|
+
// prove that one append reaches the head observed above.
|
|
1265
|
+
return { kind: "reload" };
|
|
1266
|
+
}
|
|
1267
|
+
if (
|
|
1268
|
+
events.length > FOREGROUND_COMPACT_CATCHUP_MAX_EVENTS ||
|
|
1269
|
+
groupCount(events) > FOREGROUND_COMPACT_CATCHUP_MAX_GROUPS ||
|
|
1270
|
+
browserJsonBytes(events) > FOREGROUND_COMPACT_CATCHUP_MAX_BYTES
|
|
1271
|
+
) {
|
|
1272
|
+
return { kind: "reload" };
|
|
1273
|
+
}
|
|
1274
|
+
return { kind: "append", events, resumeSequence };
|
|
1008
1275
|
}
|
|
1009
1276
|
|
|
1010
1277
|
type LoadedEventWindow = {
|
|
@@ -1030,7 +1297,9 @@ async function loadEventWindow(
|
|
|
1030
1297
|
pageSize: number;
|
|
1031
1298
|
targetGroups: number;
|
|
1032
1299
|
maxFetches: number;
|
|
1300
|
+
boundaryPageCap?: number;
|
|
1033
1301
|
signal?: AbortSignal;
|
|
1302
|
+
isCurrent?: () => boolean;
|
|
1034
1303
|
},
|
|
1035
1304
|
): Promise<LoadedEventWindow> {
|
|
1036
1305
|
let cursor = options.before;
|
|
@@ -1046,6 +1315,7 @@ async function loadEventWindow(
|
|
|
1046
1315
|
pageSize: options.pageSize,
|
|
1047
1316
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1048
1317
|
});
|
|
1318
|
+
if (options.isCurrent?.() === false) throw abortError();
|
|
1049
1319
|
fetches += 1;
|
|
1050
1320
|
if (page.length === 0) {
|
|
1051
1321
|
reachedStart = true;
|
|
@@ -1064,19 +1334,19 @@ async function loadEventWindow(
|
|
|
1064
1334
|
// turn boundary already in the buffer — the dropped fragment is refetched by
|
|
1065
1335
|
// the next loadOlder (everything below the new oldest sequence), whose own
|
|
1066
1336
|
// window snaps the same way, so every seam lands on a turn start. Extra
|
|
1067
|
-
//
|
|
1068
|
-
//
|
|
1337
|
+
// page is fetched only when the buffer holds no boundary at all (one dense
|
|
1338
|
+
// turn); past the cap the existing truncation/hasOlder signal remains true.
|
|
1069
1339
|
let snapPages = 0;
|
|
1070
1340
|
while (
|
|
1071
1341
|
!reachedStart &&
|
|
1072
1342
|
findBoundaryIndex(buffer) === -1 &&
|
|
1073
|
-
snapPages <
|
|
1074
|
-
fetches < options.maxFetches
|
|
1343
|
+
snapPages < (options.boundaryPageCap ?? 0)
|
|
1075
1344
|
) {
|
|
1076
1345
|
const page = await loadPreviousPage(client, workspaceId, sessionId, cursor, {
|
|
1077
1346
|
pageSize: options.pageSize,
|
|
1078
1347
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1079
1348
|
});
|
|
1349
|
+
if (options.isCurrent?.() === false) throw abortError();
|
|
1080
1350
|
fetches += 1;
|
|
1081
1351
|
snapPages += 1;
|
|
1082
1352
|
if (page.length === 0) {
|
|
@@ -1097,6 +1367,8 @@ async function loadEventWindow(
|
|
|
1097
1367
|
buffer = buffer.slice(boundary);
|
|
1098
1368
|
}
|
|
1099
1369
|
}
|
|
1370
|
+
const densityWindow = trimBackwardToGroupTarget(buffer, options.targetGroups);
|
|
1371
|
+
buffer = densityWindow.events;
|
|
1100
1372
|
|
|
1101
1373
|
const oldest = buffer[0] ?? null;
|
|
1102
1374
|
const newest = buffer[buffer.length - 1] ?? null;
|
|
@@ -1104,21 +1376,26 @@ async function loadEventWindow(
|
|
|
1104
1376
|
events: buffer,
|
|
1105
1377
|
oldestSequence: oldest?.sequence ?? null,
|
|
1106
1378
|
newestSequence: newest ? maxResumeSequence(buffer) : 0,
|
|
1107
|
-
hasOlder:
|
|
1379
|
+
hasOlder:
|
|
1380
|
+
buffer.length > 0 &&
|
|
1381
|
+
(densityWindow.trimmed || (!reachedStart && oldest?.type !== "session.created")),
|
|
1108
1382
|
};
|
|
1109
1383
|
}
|
|
1110
1384
|
|
|
1111
1385
|
/** Index of the oldest clean turn start in the buffer, or -1. */
|
|
1112
1386
|
function findBoundaryIndex(events: SessionEvent[]): number {
|
|
1113
1387
|
for (let index = 0; index < events.length; index += 1) {
|
|
1114
|
-
|
|
1115
|
-
if (type === "session.created" || type === "user.message") {
|
|
1388
|
+
if (isTurnBoundary(events[index]!)) {
|
|
1116
1389
|
return index;
|
|
1117
1390
|
}
|
|
1118
1391
|
}
|
|
1119
1392
|
return -1;
|
|
1120
1393
|
}
|
|
1121
1394
|
|
|
1395
|
+
function isTurnBoundary(event: SessionEvent): boolean {
|
|
1396
|
+
return event.type === "session.created" || event.type === "user.message";
|
|
1397
|
+
}
|
|
1398
|
+
|
|
1122
1399
|
/**
|
|
1123
1400
|
* Forward (oldest→newer) density-bounded window. Used for jump-to-start and
|
|
1124
1401
|
* loadNewer so the browser never walks the whole middle gap.
|
|
@@ -1133,6 +1410,7 @@ async function loadForwardEventWindow(
|
|
|
1133
1410
|
targetGroups: number;
|
|
1134
1411
|
maxFetches: number;
|
|
1135
1412
|
signal?: AbortSignal;
|
|
1413
|
+
isCurrent?: () => boolean;
|
|
1136
1414
|
},
|
|
1137
1415
|
): Promise<LoadedForwardEventWindow> {
|
|
1138
1416
|
let cursor = options.after;
|
|
@@ -1148,6 +1426,7 @@ async function loadForwardEventWindow(
|
|
|
1148
1426
|
pageSize: options.pageSize,
|
|
1149
1427
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1150
1428
|
});
|
|
1429
|
+
if (options.isCurrent?.() === false) throw abortError();
|
|
1151
1430
|
fetches += 1;
|
|
1152
1431
|
if (page.length === 0) {
|
|
1153
1432
|
reachedEnd = true;
|
|
@@ -1155,12 +1434,15 @@ async function loadForwardEventWindow(
|
|
|
1155
1434
|
}
|
|
1156
1435
|
assertAscending(page);
|
|
1157
1436
|
buffer = [...buffer, ...page];
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1437
|
+
// Compact events retain their first raw sequence in `sequence` and expose
|
|
1438
|
+
// the covered high-water mark through `coalescedUntil`. Advancing by the
|
|
1439
|
+
// first sequence duplicates compacted text on the next page. A compact
|
|
1440
|
+
// response length is not an end-of-history signal either: thousands of raw
|
|
1441
|
+
// events may legitimately collapse into one returned event.
|
|
1442
|
+
cursor = maxResumeSequence(page);
|
|
1163
1443
|
}
|
|
1444
|
+
const densityWindow = trimForwardToGroupTarget(buffer, options.targetGroups);
|
|
1445
|
+
buffer = densityWindow.events;
|
|
1164
1446
|
|
|
1165
1447
|
const oldest = buffer[0] ?? null;
|
|
1166
1448
|
const newest = buffer[buffer.length - 1] ?? null;
|
|
@@ -1168,10 +1450,45 @@ async function loadForwardEventWindow(
|
|
|
1168
1450
|
events: buffer,
|
|
1169
1451
|
oldestSequence: oldest?.sequence ?? null,
|
|
1170
1452
|
newestSequence: newest ? maxResumeSequence(buffer) : 0,
|
|
1171
|
-
hasNewer: buffer.length > 0 && !reachedEnd,
|
|
1453
|
+
hasNewer: buffer.length > 0 && (densityWindow.trimmed || !reachedEnd),
|
|
1172
1454
|
};
|
|
1173
1455
|
}
|
|
1174
1456
|
|
|
1457
|
+
type DensityWindow = {
|
|
1458
|
+
events: SessionEvent[];
|
|
1459
|
+
trimmed: boolean;
|
|
1460
|
+
};
|
|
1461
|
+
|
|
1462
|
+
/** Keep the newest complete turn groups near an older-history seam. */
|
|
1463
|
+
function trimBackwardToGroupTarget(events: SessionEvent[], targetGroups: number): DensityWindow {
|
|
1464
|
+
if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
|
|
1465
|
+
return { events, trimmed: false };
|
|
1466
|
+
}
|
|
1467
|
+
for (let index = events.length - 1; index > 0; index -= 1) {
|
|
1468
|
+
if (!isTurnBoundary(events[index]!)) continue;
|
|
1469
|
+
const candidate = events.slice(index);
|
|
1470
|
+
if (groupCount(candidate) >= targetGroups) {
|
|
1471
|
+
return { events: candidate, trimmed: true };
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return { events, trimmed: false };
|
|
1475
|
+
}
|
|
1476
|
+
|
|
1477
|
+
/** Keep the oldest complete turn groups near a forward-history seam. */
|
|
1478
|
+
function trimForwardToGroupTarget(events: SessionEvent[], targetGroups: number): DensityWindow {
|
|
1479
|
+
if (!Number.isFinite(targetGroups) || targetGroups <= 0 || events.length === 0) {
|
|
1480
|
+
return { events, trimmed: false };
|
|
1481
|
+
}
|
|
1482
|
+
for (let index = 1; index < events.length; index += 1) {
|
|
1483
|
+
if (!isTurnBoundary(events[index]!)) continue;
|
|
1484
|
+
const candidate = events.slice(0, index);
|
|
1485
|
+
if (groupCount(candidate) >= targetGroups) {
|
|
1486
|
+
return { events: candidate, trimmed: true };
|
|
1487
|
+
}
|
|
1488
|
+
}
|
|
1489
|
+
return { events, trimmed: false };
|
|
1490
|
+
}
|
|
1491
|
+
|
|
1175
1492
|
type PreviousPage = SessionEvent[] & { requested: number };
|
|
1176
1493
|
|
|
1177
1494
|
async function loadPreviousPage(
|
|
@@ -1248,18 +1565,22 @@ function isLogStart(event: SessionEvent): boolean {
|
|
|
1248
1565
|
return event.type === "session.created" || event.sequence <= 1;
|
|
1249
1566
|
}
|
|
1250
1567
|
|
|
1251
|
-
function maxResumeSequence(events: SessionEvent[]): number {
|
|
1568
|
+
function maxResumeSequence(events: readonly SessionEvent[]): number {
|
|
1252
1569
|
return events.reduce((max, event) => Math.max(max, eventResumeSequence(event)), 0);
|
|
1253
1570
|
}
|
|
1254
1571
|
|
|
1255
|
-
function
|
|
1256
|
-
|
|
1257
|
-
const coalescedUntil = Number(payload.coalescedUntil);
|
|
1258
|
-
return Math.max(event.sequence, Number.isFinite(coalescedUntil) ? Math.floor(coalescedUntil) : 0);
|
|
1572
|
+
function maxResumeSequenceOrNull(events: readonly SessionEvent[]): number | null {
|
|
1573
|
+
return events.length > 0 ? maxResumeSequence(events) : null;
|
|
1259
1574
|
}
|
|
1260
1575
|
|
|
1261
|
-
function
|
|
1262
|
-
|
|
1576
|
+
function eventResumeSequence(event: SessionEvent): number {
|
|
1577
|
+
const streamedCoverage = sessionEventStreamCoveredThrough(event);
|
|
1578
|
+
if (streamedCoverage !== null) return streamedCoverage;
|
|
1579
|
+
return typeof event.coveredThrough === "number" &&
|
|
1580
|
+
Number.isSafeInteger(event.coveredThrough) &&
|
|
1581
|
+
event.coveredThrough >= event.sequence
|
|
1582
|
+
? event.coveredThrough
|
|
1583
|
+
: event.sequence;
|
|
1263
1584
|
}
|
|
1264
1585
|
|
|
1265
1586
|
function assertAscending(events: SessionEvent[]): void {
|