@opengeni/react 3.3.2 → 3.4.1-canary.38960406141001
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 +2 -0
- 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-4EUKJXUO.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-XYRDDSXF.js → chunk-2I6R3GCZ.js} +4285 -3980
- package/dist/chunk-2I6R3GCZ.js.map +1 -0
- package/dist/{chunk-JAQ5K7W6.js → chunk-2SFFYIH3.js} +1178 -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-3RJKUVCA.js +14068 -0
- package/dist/chunk-3RJKUVCA.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-ZPBTI3TE.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-YVCHAQZY.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-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-IOLVN74D.js → chunk-V5ZUJT2T.js} +175 -42
- package/dist/chunk-V5ZUJT2T.js.map +1 -0
- package/dist/chunk-WBJY57A3.js +402 -0
- package/dist/chunk-WBJY57A3.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-3AGBGTRT.js → chunk-X5UVUIMF.js} +1712 -1174
- 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/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 +35 -28
- 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/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 +26 -5
- 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 +24 -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 +2340 -6603
- 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 -1333
- 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/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 +106 -7
- 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 +193 -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 +5 -3
- 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 +2370 -667
- 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 +252 -429
- 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/tool-action-review.tsx +650 -0
- package/src/components/tool-review-history.tsx +88 -0
- 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 +2 -2
- 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 +227 -44
- 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 +79 -9
- package/src/hooks/use-last-started-turn-policy.ts +37 -11
- 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 +531 -335
- 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 +30 -2
- 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 +74 -7
- 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 +205 -10
- 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 -550
- 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/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 +1742 -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-MKKFRRRV.js +0 -14
- package/dist/chunk-2DZVYHVO.js +0 -470
- package/dist/chunk-2DZVYHVO.js.map +0 -1
- package/dist/chunk-3AGBGTRT.js.map +0 -1
- package/dist/chunk-4MDLEHXL.js.map +0 -1
- package/dist/chunk-5YK2ZDIG.js.map +0 -1
- package/dist/chunk-7EAFGICR.js +0 -302
- package/dist/chunk-7EAFGICR.js.map +0 -1
- package/dist/chunk-ELIYWBZR.js.map +0 -1
- package/dist/chunk-H5YGYNXS.js.map +0 -1
- package/dist/chunk-HUIKIDM5.js.map +0 -1
- package/dist/chunk-ICA6R6TW.js.map +0 -1
- package/dist/chunk-IOLVN74D.js.map +0 -1
- package/dist/chunk-JAQ5K7W6.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-NEXLLTP4.js.map +0 -1
- package/dist/chunk-PEI6RANI.js.map +0 -1
- package/dist/chunk-VTOREEXF.js +0 -142
- package/dist/chunk-VTOREEXF.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-XYRDDSXF.js.map +0 -1
- package/dist/chunk-YG4QEQF5.js +0 -9557
- package/dist/chunk-YG4QEQF5.js.map +0 -1
- package/dist/chunk-YVCHAQZY.js.map +0 -1
- package/dist/chunk-ZPBTI3TE.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-MKKFRRRV.js.map → browser-viewer-ZWR6MWJ7.js.map} +0 -0
- /package/dist/{computer-viewer-FWEZISW4.js.map → chat-interactive-block-4EUKJXUO.js.map} +0 -0
|
@@ -1,11 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
import {
|
|
2
|
+
sessionEventStreamCoveredThrough,
|
|
3
|
+
type SessionEvent,
|
|
4
|
+
type SessionStatus,
|
|
5
|
+
type StreamConnectionState,
|
|
6
|
+
} from "@opengeni/sdk";
|
|
2
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
|
|
@@ -288,7 +367,7 @@ export function useSessionEvents(
|
|
|
288
367
|
setSessionStatusProjection(status);
|
|
289
368
|
}
|
|
290
369
|
const current = eventWindowRef.current;
|
|
291
|
-
const next =
|
|
370
|
+
const next = appendBrowserSessionEventWindow(current, batch);
|
|
292
371
|
const retained = {
|
|
293
372
|
...next,
|
|
294
373
|
truncated: current.truncated || next.truncated,
|
|
@@ -309,6 +388,56 @@ export function useSessionEvents(
|
|
|
309
388
|
|
|
310
389
|
void (async () => {
|
|
311
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
|
+
}
|
|
312
441
|
if (!fullReplay && !initialWindowLoadedRef.current) {
|
|
313
442
|
setConnectionState("connecting");
|
|
314
443
|
// First paint is ONE compact fetch — the newest window, revealed at
|
|
@@ -316,9 +445,10 @@ export function useSessionEvents(
|
|
|
316
445
|
// reader actually scrolls up (the sentinel drives loadOlder).
|
|
317
446
|
const window = await loadEventWindow(client, workspaceId, sessionId, {
|
|
318
447
|
before: Number.MAX_SAFE_INTEGER,
|
|
319
|
-
pageSize:
|
|
448
|
+
pageSize: SESSION_HISTORY_PAGE_SIZE,
|
|
320
449
|
targetGroups: Number.POSITIVE_INFINITY,
|
|
321
450
|
maxFetches: INITIAL_FETCH_CAP,
|
|
451
|
+
boundaryPageCap: BOUNDARY_PAGE_CAP,
|
|
322
452
|
signal: controller.signal,
|
|
323
453
|
});
|
|
324
454
|
if (!isCurrent()) {
|
|
@@ -326,6 +456,16 @@ export function useSessionEvents(
|
|
|
326
456
|
}
|
|
327
457
|
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
328
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);
|
|
329
469
|
eventWindowRef.current = retained;
|
|
330
470
|
oldestSequenceRef.current = retained.events[0]?.sequence ?? window.oldestSequence;
|
|
331
471
|
newestSequenceRef.current =
|
|
@@ -335,6 +475,8 @@ export function useSessionEvents(
|
|
|
335
475
|
lastSequenceRef.current = window.newestSequence;
|
|
336
476
|
streamResumeSequenceRef.current = window.newestSequence;
|
|
337
477
|
initialWindowLoadedRef.current = true;
|
|
478
|
+
setInitialHistoryReady(true);
|
|
479
|
+
setError(null);
|
|
338
480
|
if (status !== undefined) {
|
|
339
481
|
setSessionStatusProjection(status);
|
|
340
482
|
}
|
|
@@ -357,7 +499,7 @@ export function useSessionEvents(
|
|
|
357
499
|
.then(() => reconcileSession(sessionId))
|
|
358
500
|
.catch((cause) => {
|
|
359
501
|
if (isCurrent()) {
|
|
360
|
-
setError(
|
|
502
|
+
setError(normalizeError(cause));
|
|
361
503
|
}
|
|
362
504
|
});
|
|
363
505
|
},
|
|
@@ -369,7 +511,7 @@ export function useSessionEvents(
|
|
|
369
511
|
});
|
|
370
512
|
for await (const event of stream) {
|
|
371
513
|
if (!isCurrent()) break;
|
|
372
|
-
const boundedEvent =
|
|
514
|
+
const boundedEvent = event;
|
|
373
515
|
const boundedEventBytes = browserJsonBytes(boundedEvent);
|
|
374
516
|
const separatorBytes = pending.length === 0 ? 0 : 1;
|
|
375
517
|
if (
|
|
@@ -398,7 +540,7 @@ export function useSessionEvents(
|
|
|
398
540
|
} catch (cause) {
|
|
399
541
|
if (isCurrent()) {
|
|
400
542
|
flush();
|
|
401
|
-
setError(
|
|
543
|
+
setError(normalizeError(cause));
|
|
402
544
|
setConnectionState("error");
|
|
403
545
|
// A failed first compact-tail request has no later success path in
|
|
404
546
|
// this effect instance. End the loading gate so hosts can render the
|
|
@@ -425,6 +567,8 @@ export function useSessionEvents(
|
|
|
425
567
|
workspaceId,
|
|
426
568
|
sessionId,
|
|
427
569
|
after,
|
|
570
|
+
enabled,
|
|
571
|
+
pageLive,
|
|
428
572
|
streamEnabled,
|
|
429
573
|
fullReplay,
|
|
430
574
|
streamKey,
|
|
@@ -434,6 +578,7 @@ export function useSessionEvents(
|
|
|
434
578
|
]);
|
|
435
579
|
|
|
436
580
|
const navigationBusy = (): boolean =>
|
|
581
|
+
loadingTargetRef.current ||
|
|
437
582
|
loadingOlderRef.current ||
|
|
438
583
|
loadingNewerRef.current ||
|
|
439
584
|
loadingOldestRef.current ||
|
|
@@ -441,8 +586,8 @@ export function useSessionEvents(
|
|
|
441
586
|
|
|
442
587
|
const loadOlder = useCallback(
|
|
443
588
|
(): OlderHistoryLoadReceipt =>
|
|
444
|
-
createOlderHistoryLoadReceipt(async (markCommitted) => {
|
|
445
|
-
if (!sessionId || navigationBusy() || !hasOlderRef.current) {
|
|
589
|
+
createOlderHistoryLoadReceipt(async (markCommitted, preserveTail, markTailPreserved) => {
|
|
590
|
+
if (!ownsNavigation() || !sessionId || navigationBusy() || !hasOlderRef.current) {
|
|
446
591
|
return false;
|
|
447
592
|
}
|
|
448
593
|
const before = oldestSequenceRef.current;
|
|
@@ -452,18 +597,20 @@ export function useSessionEvents(
|
|
|
452
597
|
setHasOlder(false);
|
|
453
598
|
return false;
|
|
454
599
|
}
|
|
455
|
-
const generation =
|
|
600
|
+
const generation = navigationGenerationRef.current;
|
|
601
|
+
const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
|
|
456
602
|
loadingOlderRef.current = true;
|
|
457
603
|
setLoadingOlder(true);
|
|
458
604
|
let published = false;
|
|
459
605
|
try {
|
|
460
606
|
const window = await loadEventWindow(client, workspaceId, sessionId, {
|
|
461
607
|
before,
|
|
462
|
-
pageSize:
|
|
608
|
+
pageSize: SESSION_HISTORY_PAGE_SIZE,
|
|
463
609
|
targetGroups: OLDER_GROUP_TARGET,
|
|
464
610
|
maxFetches: OLDER_FETCH_CAP,
|
|
611
|
+
isCurrent,
|
|
465
612
|
});
|
|
466
|
-
if (
|
|
613
|
+
if (!isCurrent()) {
|
|
467
614
|
return false;
|
|
468
615
|
}
|
|
469
616
|
if (window.events.length === 0) {
|
|
@@ -474,14 +621,16 @@ export function useSessionEvents(
|
|
|
474
621
|
}
|
|
475
622
|
const current = eventWindowRef.current;
|
|
476
623
|
assertPrependOrder(current.events, window.events);
|
|
477
|
-
// Freeze the live iterator before replacing its in-memory window. Rows
|
|
478
|
-
// pending in the aborted iterator were never cursor-committed and will
|
|
479
|
-
// be replayed from the retained high-water mark below.
|
|
480
|
-
streamAbortRef.current?.abort();
|
|
481
|
-
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
482
624
|
const next = boundBrowserSessionEventWindow([...window.events, ...current.events], {
|
|
483
625
|
direction: "oldest",
|
|
484
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);
|
|
485
634
|
const retained = {
|
|
486
635
|
...next,
|
|
487
636
|
truncated: current.truncated || next.truncated,
|
|
@@ -492,21 +641,28 @@ export function useSessionEvents(
|
|
|
492
641
|
}
|
|
493
642
|
const retainedNewest = maxResumeSequenceOrNull(retained.events);
|
|
494
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
|
+
}
|
|
495
652
|
eventWindowRef.current = retained;
|
|
496
653
|
markCommitted();
|
|
497
654
|
oldestSequenceRef.current = retainedOldest;
|
|
498
655
|
newestSequenceRef.current = retainedNewest;
|
|
499
|
-
|
|
656
|
+
if (evictedLiveTail) {
|
|
657
|
+
streamResumeSequenceRef.current = maxResumeSequence(retained.events);
|
|
658
|
+
}
|
|
500
659
|
// Oldest-directed eviction can discard newer in-memory rows. That fact
|
|
501
660
|
// keeps windowTruncated true, but it does not imply older durable rows
|
|
502
661
|
// exist; only the backward DB page can answer hasOlder truthfully.
|
|
503
662
|
const olderStillAvailable = window.hasOlder;
|
|
504
663
|
hasOlderRef.current = olderStillAvailable;
|
|
505
|
-
const evictedLiveTail =
|
|
506
|
-
previousNewest !== null && retainedNewest !== null && retainedNewest < previousNewest;
|
|
507
664
|
let newer: boolean | null = null;
|
|
508
665
|
let enterHistory = false;
|
|
509
|
-
let reconnectLive = false;
|
|
510
666
|
if (viewModeRef.current === "history" || evictedLiveTail) {
|
|
511
667
|
const highWater = lastSequenceRef.current;
|
|
512
668
|
newer = retainedNewest !== null && (retainedNewest < highWater || retained.truncated);
|
|
@@ -519,11 +675,13 @@ export function useSessionEvents(
|
|
|
519
675
|
viewModeRef.current = "history";
|
|
520
676
|
enterHistory = true;
|
|
521
677
|
}
|
|
522
|
-
} else {
|
|
523
|
-
// The retained merge is still one contiguous live suffix. Restart
|
|
524
|
-
// SSE from its cursor so events queued during the fetch are replayed.
|
|
525
|
-
reconnectLive = true;
|
|
526
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.
|
|
527
685
|
loadingOlderRef.current = false;
|
|
528
686
|
startTransition(() => {
|
|
529
687
|
if (status !== undefined) {
|
|
@@ -536,39 +694,44 @@ export function useSessionEvents(
|
|
|
536
694
|
}
|
|
537
695
|
if (enterHistory) {
|
|
538
696
|
setViewMode("history");
|
|
539
|
-
} else if (reconnectLive) {
|
|
540
|
-
setStreamEpoch((epoch) => epoch + 1);
|
|
541
697
|
}
|
|
542
698
|
setLoadingOlder(false);
|
|
543
699
|
});
|
|
544
700
|
published = true;
|
|
545
701
|
return olderStillAvailable;
|
|
702
|
+
} catch (reason) {
|
|
703
|
+
if (!isCurrent()) return false;
|
|
704
|
+
throw reason;
|
|
546
705
|
} finally {
|
|
547
|
-
if (!published) {
|
|
706
|
+
if (!published && isCurrent()) {
|
|
548
707
|
loadingOlderRef.current = false;
|
|
549
708
|
setLoadingOlder(false);
|
|
550
709
|
}
|
|
551
710
|
}
|
|
552
711
|
}),
|
|
553
|
-
[client, workspaceId, sessionId],
|
|
712
|
+
[client, workspaceId, sessionId, ownsNavigation],
|
|
554
713
|
);
|
|
555
714
|
|
|
556
715
|
const loadOldest = useCallback(async (): Promise<boolean> => {
|
|
557
|
-
if (!sessionId || navigationBusy() || !hasOlderRef.current) {
|
|
716
|
+
if (!ownsNavigation() || !sessionId || navigationBusy() || !hasOlderRef.current) {
|
|
558
717
|
return false;
|
|
559
718
|
}
|
|
560
|
-
|
|
719
|
+
// Explicit window replacement supersedes unresolved question destinations.
|
|
720
|
+
const generation = ++navigationGenerationRef.current;
|
|
721
|
+
const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
|
|
722
|
+
setQuestionEvidence(null);
|
|
561
723
|
loadingOldestRef.current = true;
|
|
562
724
|
setLoadingOldest(true);
|
|
563
725
|
let published = false;
|
|
564
726
|
try {
|
|
565
727
|
const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
|
|
566
728
|
after: 0,
|
|
567
|
-
pageSize:
|
|
729
|
+
pageSize: SESSION_HISTORY_PAGE_SIZE,
|
|
568
730
|
targetGroups: OLDEST_GROUP_TARGET,
|
|
569
731
|
maxFetches: OLDEST_FETCH_CAP,
|
|
732
|
+
isCurrent,
|
|
570
733
|
});
|
|
571
|
-
if (
|
|
734
|
+
if (!isCurrent()) {
|
|
572
735
|
return false;
|
|
573
736
|
}
|
|
574
737
|
if (window.events.length === 0) {
|
|
@@ -594,8 +757,11 @@ export function useSessionEvents(
|
|
|
594
757
|
(retainedNewest !== null && retainedNewest < highWater);
|
|
595
758
|
hasNewerRef.current = newer;
|
|
596
759
|
initialWindowLoadedRef.current = true;
|
|
760
|
+
setInitialHistoryReady(true);
|
|
761
|
+
setError(null);
|
|
597
762
|
viewModeRef.current = "history";
|
|
598
763
|
loadingOldestRef.current = false;
|
|
764
|
+
setNewerError(null);
|
|
599
765
|
startTransition(() => {
|
|
600
766
|
if (status !== undefined) {
|
|
601
767
|
setSessionStatusProjection(status);
|
|
@@ -608,16 +774,19 @@ export function useSessionEvents(
|
|
|
608
774
|
});
|
|
609
775
|
published = true;
|
|
610
776
|
return newer;
|
|
777
|
+
} catch (reason) {
|
|
778
|
+
if (!isCurrent()) return false;
|
|
779
|
+
throw reason;
|
|
611
780
|
} finally {
|
|
612
|
-
if (!published) {
|
|
781
|
+
if (!published && isCurrent()) {
|
|
613
782
|
loadingOldestRef.current = false;
|
|
614
783
|
setLoadingOldest(false);
|
|
615
784
|
}
|
|
616
785
|
}
|
|
617
|
-
}, [client, workspaceId, sessionId]);
|
|
786
|
+
}, [client, workspaceId, sessionId, ownsNavigation]);
|
|
618
787
|
|
|
619
788
|
const loadNewer = useCallback(async (): Promise<boolean> => {
|
|
620
|
-
if (!sessionId || navigationBusy() || !hasNewerRef.current) {
|
|
789
|
+
if (!ownsNavigation() || !sessionId || navigationBusy() || !hasNewerRef.current) {
|
|
621
790
|
return false;
|
|
622
791
|
}
|
|
623
792
|
const afterSequence = newestSequenceRef.current;
|
|
@@ -626,21 +795,24 @@ export function useSessionEvents(
|
|
|
626
795
|
setHasNewer(false);
|
|
627
796
|
return false;
|
|
628
797
|
}
|
|
629
|
-
const generation =
|
|
798
|
+
const generation = navigationGenerationRef.current;
|
|
799
|
+
const isCurrent = () => ownsNavigation() && navigationGenerationRef.current === generation;
|
|
630
800
|
loadingNewerRef.current = true;
|
|
631
801
|
setLoadingNewer(true);
|
|
632
802
|
let published = false;
|
|
633
803
|
try {
|
|
634
804
|
const window = await loadForwardEventWindow(client, workspaceId, sessionId, {
|
|
635
805
|
after: afterSequence,
|
|
636
|
-
pageSize:
|
|
806
|
+
pageSize: SESSION_HISTORY_PAGE_SIZE,
|
|
637
807
|
targetGroups: NEWER_GROUP_TARGET,
|
|
638
808
|
maxFetches: NEWER_FETCH_CAP,
|
|
809
|
+
isCurrent,
|
|
639
810
|
});
|
|
640
|
-
if (
|
|
811
|
+
if (!isCurrent()) {
|
|
641
812
|
return false;
|
|
642
813
|
}
|
|
643
814
|
if (window.events.length === 0) {
|
|
815
|
+
setNewerError(null);
|
|
644
816
|
hasNewerRef.current = false;
|
|
645
817
|
setHasNewer(false);
|
|
646
818
|
// Caught up with durable history — resume the live tip stream.
|
|
@@ -655,9 +827,7 @@ export function useSessionEvents(
|
|
|
655
827
|
const status = observeSessionStatus(window.events, sessionStatusRef);
|
|
656
828
|
const previousOldest = current.events[0]?.sequence ?? null;
|
|
657
829
|
// Moving forward through history: keep the newest suffix of the merge.
|
|
658
|
-
const next =
|
|
659
|
-
direction: "newest",
|
|
660
|
-
});
|
|
830
|
+
const next = appendBrowserSessionEventWindow(current, window.events);
|
|
661
831
|
const retained = {
|
|
662
832
|
...next,
|
|
663
833
|
truncated: current.truncated || next.truncated || window.hasNewer,
|
|
@@ -693,6 +863,7 @@ export function useSessionEvents(
|
|
|
693
863
|
resumeLive = true;
|
|
694
864
|
}
|
|
695
865
|
loadingNewerRef.current = false;
|
|
866
|
+
setNewerError(null);
|
|
696
867
|
startTransition(() => {
|
|
697
868
|
if (status !== undefined) {
|
|
698
869
|
setSessionStatusProjection(status);
|
|
@@ -708,20 +879,171 @@ export function useSessionEvents(
|
|
|
708
879
|
});
|
|
709
880
|
published = true;
|
|
710
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;
|
|
711
892
|
} finally {
|
|
712
|
-
if (!published) {
|
|
893
|
+
if (!published && isCurrent()) {
|
|
713
894
|
loadingNewerRef.current = false;
|
|
714
895
|
setLoadingNewer(false);
|
|
715
896
|
}
|
|
716
897
|
}
|
|
717
|
-
}, [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
|
+
);
|
|
718
1028
|
|
|
719
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
|
+
}
|
|
720
1038
|
if (!sessionId || navigationBusy()) {
|
|
721
1039
|
return;
|
|
722
1040
|
}
|
|
1041
|
+
navigationGenerationRef.current += 1;
|
|
1042
|
+
setQuestionEvidence(null);
|
|
723
1043
|
loadingLatestRef.current = true;
|
|
724
1044
|
setLoadingLatest(true);
|
|
1045
|
+
setNewerError(null);
|
|
1046
|
+
setError(null);
|
|
725
1047
|
let published = false;
|
|
726
1048
|
try {
|
|
727
1049
|
streamAbortRef.current?.abort();
|
|
@@ -752,21 +1074,37 @@ export function useSessionEvents(
|
|
|
752
1074
|
setLoadingLatest(false);
|
|
753
1075
|
}
|
|
754
1076
|
}
|
|
755
|
-
}, [after, sessionId]);
|
|
1077
|
+
}, [after, sessionId, ownsNavigation]);
|
|
756
1078
|
|
|
757
1079
|
const identityMatches = stateStreamKey === streamKey;
|
|
758
1080
|
const visibleEvents = identityMatches ? eventWindow.events : EMPTY_EVENTS;
|
|
759
|
-
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]);
|
|
760
1096
|
|
|
761
1097
|
return {
|
|
762
1098
|
events: visibleEvents,
|
|
763
1099
|
timeline,
|
|
764
1100
|
sessionStatus: identityMatches ? sessionStatusProjection : null,
|
|
1101
|
+
sessionStatusSequence: identityMatches ? sessionStatusRef.current.sequence : 0,
|
|
765
1102
|
connectionState: identityMatches ? connectionState : "idle",
|
|
766
1103
|
lastSequence: identityMatches ? lastSequenceRef.current : after,
|
|
767
1104
|
windowBytes: identityMatches ? eventWindow.bytes : 2,
|
|
768
1105
|
windowTruncated: identityMatches ? eventWindow.truncated : false,
|
|
769
1106
|
initialLoading: fullReplay ? false : identityMatches ? initialLoading : true,
|
|
1107
|
+
initialHistoryReady: identityMatches && initialHistoryReady,
|
|
770
1108
|
hasOlder: !identityMatches ? false : hasOlder,
|
|
771
1109
|
loadingOlder: !identityMatches ? false : loadingOlder,
|
|
772
1110
|
loadOlder,
|
|
@@ -777,7 +1115,10 @@ export function useSessionEvents(
|
|
|
777
1115
|
loadOldest,
|
|
778
1116
|
loadingLatest: !identityMatches ? false : loadingLatest,
|
|
779
1117
|
jumpToLatest,
|
|
780
|
-
|
|
1118
|
+
jumpToLatestQuestion,
|
|
1119
|
+
jumpToSequence,
|
|
1120
|
+
loadingTarget: identityMatches && loadingTarget,
|
|
1121
|
+
error: identityMatches ? (newerError ?? error) : null,
|
|
781
1122
|
};
|
|
782
1123
|
}
|
|
783
1124
|
|
|
@@ -788,7 +1129,8 @@ export function useSessionEvents(
|
|
|
788
1129
|
* deliberately separate from durable history and transport paging: when a
|
|
789
1130
|
* backward page evicts the live tail, the hook enters bounded history mode and
|
|
790
1131
|
* preserves the highest-ever-observed sequence separately for forward paging.
|
|
791
|
-
* The source event remains durable in PostgreSQL throughout.
|
|
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.
|
|
792
1134
|
*/
|
|
793
1135
|
export function boundBrowserSessionEventWindow(
|
|
794
1136
|
events: readonly SessionEvent[],
|
|
@@ -800,7 +1142,7 @@ export function boundBrowserSessionEventWindow(
|
|
|
800
1142
|
): BrowserSessionEventWindow {
|
|
801
1143
|
const maxBytes = Math.max(1024, options.maxBytes ?? SESSION_EVENT_BROWSER_MAX_BYTES);
|
|
802
1144
|
const maxCount = Math.max(1, Math.floor(options.maxCount ?? SESSION_EVENT_BROWSER_MAX_COUNT));
|
|
803
|
-
const safe = events
|
|
1145
|
+
const safe = events;
|
|
804
1146
|
const selected: SessionEvent[] = [];
|
|
805
1147
|
let bytes = 2; // []
|
|
806
1148
|
const direction = options.direction ?? "newest";
|
|
@@ -811,7 +1153,7 @@ export function boundBrowserSessionEventWindow(
|
|
|
811
1153
|
const event = safe[index]!;
|
|
812
1154
|
const eventBytes = browserJsonBytes(event);
|
|
813
1155
|
const separator = selected.length === 0 ? 0 : 1;
|
|
814
|
-
if (bytes + separator + eventBytes > maxBytes) break;
|
|
1156
|
+
if (selected.length > 0 && bytes + separator + eventBytes > maxBytes) break;
|
|
815
1157
|
selected.push(event);
|
|
816
1158
|
bytes += separator + eventBytes;
|
|
817
1159
|
}
|
|
@@ -823,248 +1165,35 @@ export function boundBrowserSessionEventWindow(
|
|
|
823
1165
|
};
|
|
824
1166
|
}
|
|
825
1167
|
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
const
|
|
833
|
-
const
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
!event.type.includes("\n") &&
|
|
839
|
-
!event.type.includes("\r");
|
|
840
|
-
const clientEventId = boundBrowserOptionalText(
|
|
841
|
-
event.clientEventId,
|
|
842
|
-
BROWSER_EVENT_CLIENT_ID_MAX_BYTES,
|
|
843
|
-
);
|
|
844
|
-
const duplicateReason = boundBrowserOptionalText(
|
|
845
|
-
event.duplicateReason,
|
|
846
|
-
BROWSER_EVENT_DUPLICATE_REASON_MAX_BYTES,
|
|
847
|
-
);
|
|
848
|
-
|
|
849
|
-
if (
|
|
850
|
-
serialized.serializable &&
|
|
851
|
-
originalBytes !== null &&
|
|
852
|
-
originalBytes <= SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES &&
|
|
853
|
-
typeIsSafe &&
|
|
854
|
-
clientEventId === event.clientEventId &&
|
|
855
|
-
duplicateReason === event.duplicateReason
|
|
856
|
-
) {
|
|
857
|
-
return event;
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
const envelopeProjection = [
|
|
861
|
-
!typeIsSafe
|
|
862
|
-
? browserEnvelopeFieldProjection("type", event.type, "session.event.envelope_omitted")
|
|
863
|
-
: null,
|
|
864
|
-
clientEventId !== event.clientEventId
|
|
865
|
-
? browserEnvelopeFieldProjection("clientEventId", event.clientEventId, clientEventId)
|
|
866
|
-
: null,
|
|
867
|
-
duplicateReason !== event.duplicateReason
|
|
868
|
-
? browserEnvelopeFieldProjection("duplicateReason", event.duplicateReason, duplicateReason)
|
|
869
|
-
: null,
|
|
870
|
-
].filter((field) => field !== null);
|
|
871
|
-
const payloadSerialization = browserSerialize(event.payload);
|
|
872
|
-
const payloadBytes = payloadSerialization.serializable
|
|
873
|
-
? encoder.encode(payloadSerialization.value).byteLength
|
|
874
|
-
: null;
|
|
875
|
-
const preview = truncateBrowserUtf8Middle(
|
|
876
|
-
payloadSerialization.value,
|
|
877
|
-
BROWSER_EVENT_PAYLOAD_PREVIEW_MAX_BYTES,
|
|
878
|
-
);
|
|
879
|
-
const truncation = {
|
|
880
|
-
truncated: true as const,
|
|
881
|
-
surface: "browser_legacy_guard" as const,
|
|
882
|
-
reason: serialized.serializable ? "event_envelope_bytes_exceeded" : "event_not_serializable",
|
|
883
|
-
originalBytes,
|
|
884
|
-
deliveredBytes: 0,
|
|
885
|
-
omittedBytes: originalBytes,
|
|
886
|
-
estimatedOriginalTokens: originalBytes === null ? null : Math.ceil(originalBytes / 4),
|
|
887
|
-
estimatedDeliveredTokens: 0,
|
|
888
|
-
fullEvidence: { available: false as const, reason: "not_retained" as const },
|
|
889
|
-
details: [
|
|
890
|
-
{
|
|
891
|
-
path: "$.payload",
|
|
892
|
-
kind: "payload_preview",
|
|
893
|
-
originalBytes: payloadBytes,
|
|
894
|
-
deliveredBytes: browserUtf8Bytes(preview),
|
|
895
|
-
},
|
|
896
|
-
],
|
|
897
|
-
};
|
|
898
|
-
const payload: Record<string, unknown> = {
|
|
899
|
-
...browserPayloadIdentity(event.payload),
|
|
900
|
-
preview,
|
|
901
|
-
...(envelopeProjection.length > 0
|
|
902
|
-
? {
|
|
903
|
-
originalType: boundBrowserText(event.type, BROWSER_EVENT_TYPE_MAX_BYTES),
|
|
904
|
-
envelopeProjection: {
|
|
905
|
-
truncated: true,
|
|
906
|
-
surface: "browser_legacy_guard",
|
|
907
|
-
fields: envelopeProjection,
|
|
908
|
-
},
|
|
909
|
-
}
|
|
910
|
-
: {}),
|
|
911
|
-
truncation,
|
|
912
|
-
};
|
|
913
|
-
const bounded: SessionEvent = {
|
|
914
|
-
id: boundBrowserText(event.id, BROWSER_EVENT_ID_MAX_BYTES),
|
|
915
|
-
workspaceId: boundBrowserText(event.workspaceId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
916
|
-
sessionId: boundBrowserText(event.sessionId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
917
|
-
sequence: event.sequence,
|
|
918
|
-
type: typeIsSafe ? event.type : "session.event.envelope_omitted",
|
|
919
|
-
payload,
|
|
920
|
-
occurredAt: boundBrowserText(event.occurredAt, BROWSER_EVENT_ID_MAX_BYTES),
|
|
921
|
-
clientEventId,
|
|
922
|
-
turnId: boundBrowserOptionalText(event.turnId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
923
|
-
turnGeneration: event.turnGeneration,
|
|
924
|
-
turnAttemptId: boundBrowserOptionalText(event.turnAttemptId, BROWSER_EVENT_ID_MAX_BYTES),
|
|
925
|
-
turnAssociation: event.turnAssociation,
|
|
926
|
-
duplicateOfEventId: boundBrowserOptionalText(
|
|
927
|
-
event.duplicateOfEventId,
|
|
928
|
-
BROWSER_EVENT_ID_MAX_BYTES,
|
|
929
|
-
),
|
|
930
|
-
duplicateReason,
|
|
931
|
-
};
|
|
932
|
-
|
|
933
|
-
settleBrowserEventTruncation(bounded, truncation);
|
|
934
|
-
if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
|
|
935
|
-
payload.preview = truncateBrowserUtf8Middle(String(payload.preview), 4 * 1024);
|
|
936
|
-
truncation.details = truncation.details.slice(0, 1);
|
|
937
|
-
settleBrowserEventTruncation(bounded, truncation);
|
|
938
|
-
}
|
|
939
|
-
if (browserJsonBytes(bounded) > SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES) {
|
|
940
|
-
bounded.clientEventId = null;
|
|
941
|
-
bounded.duplicateReason = null;
|
|
942
|
-
payload.preview = "[legacy event omitted at the browser byte boundary]";
|
|
943
|
-
settleBrowserEventTruncation(bounded, truncation);
|
|
944
|
-
}
|
|
945
|
-
return bounded;
|
|
946
|
-
}
|
|
947
|
-
|
|
948
|
-
function browserJsonBytes(value: unknown): number {
|
|
949
|
-
return encoder.encode(browserSerialize(value).value).byteLength;
|
|
950
|
-
}
|
|
951
|
-
|
|
952
|
-
function browserSerialize(value: unknown): { value: string; serializable: boolean } {
|
|
953
|
-
try {
|
|
954
|
-
const serialized = JSON.stringify(value);
|
|
955
|
-
return { value: serialized === undefined ? "null" : serialized, serializable: true };
|
|
956
|
-
} catch {
|
|
957
|
-
return {
|
|
958
|
-
value: '"[unserializable event payload omitted at browser boundary]"',
|
|
959
|
-
serializable: false,
|
|
960
|
-
};
|
|
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);
|
|
961
1180
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
for (const field of BROWSER_EVENT_PAYLOAD_IDENTITY_FIELDS) {
|
|
969
|
-
const value = record[field];
|
|
970
|
-
if (typeof value === "string") {
|
|
971
|
-
identity[field] = boundBrowserText(value, BROWSER_EVENT_ID_MAX_BYTES);
|
|
972
|
-
} else if (typeof value === "number" || typeof value === "boolean" || value === null) {
|
|
973
|
-
identity[field] = value;
|
|
974
|
-
}
|
|
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;
|
|
975
1187
|
}
|
|
976
|
-
return identity;
|
|
977
|
-
}
|
|
978
|
-
|
|
979
|
-
function browserEnvelopeFieldProjection(
|
|
980
|
-
field: string,
|
|
981
|
-
original: string | null | undefined,
|
|
982
|
-
delivered: string | null | undefined,
|
|
983
|
-
): { field: string; originalBytes: number; deliveredBytes: number } {
|
|
984
1188
|
return {
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
1189
|
+
events: start === 0 ? events : events.slice(start),
|
|
1190
|
+
bytes,
|
|
1191
|
+
truncated: current.truncated || start > 0,
|
|
988
1192
|
};
|
|
989
1193
|
}
|
|
990
1194
|
|
|
991
|
-
function
|
|
992
|
-
value
|
|
993
|
-
maxBytes: number,
|
|
994
|
-
): T {
|
|
995
|
-
return (typeof value === "string" ? boundBrowserText(value, maxBytes) : value) as T;
|
|
996
|
-
}
|
|
997
|
-
|
|
998
|
-
function boundBrowserText(value: string, maxBytes: number): string {
|
|
999
|
-
const bytes = encoder.encode(value);
|
|
1000
|
-
if (bytes.byteLength <= maxBytes) return value;
|
|
1001
|
-
const marker = "…[truncated]";
|
|
1002
|
-
const prefixBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
|
|
1003
|
-
let prefixEnd = Math.min(prefixBudget, bytes.byteLength);
|
|
1004
|
-
while (
|
|
1005
|
-
prefixEnd > 0 &&
|
|
1006
|
-
prefixEnd < bytes.byteLength &&
|
|
1007
|
-
isBrowserUtf8Continuation(bytes[prefixEnd]!)
|
|
1008
|
-
) {
|
|
1009
|
-
prefixEnd -= 1;
|
|
1010
|
-
}
|
|
1011
|
-
return `${decoder.decode(bytes.subarray(0, prefixEnd))}${marker}`;
|
|
1012
|
-
}
|
|
1013
|
-
|
|
1014
|
-
function truncateBrowserUtf8Middle(value: string, maxBytes: number): string {
|
|
1015
|
-
const bytes = encoder.encode(value);
|
|
1016
|
-
if (bytes.byteLength <= maxBytes) return value;
|
|
1017
|
-
const marker = `…[${bytes.byteLength - maxBytes} bytes omitted]…`;
|
|
1018
|
-
const contentBudget = Math.max(0, maxBytes - browserUtf8Bytes(marker));
|
|
1019
|
-
const leftBudget = Math.floor(contentBudget / 2);
|
|
1020
|
-
const rightBudget = contentBudget - leftBudget;
|
|
1021
|
-
let leftEnd = Math.min(leftBudget, bytes.byteLength);
|
|
1022
|
-
while (leftEnd > 0 && leftEnd < bytes.byteLength && isBrowserUtf8Continuation(bytes[leftEnd]!)) {
|
|
1023
|
-
leftEnd -= 1;
|
|
1024
|
-
}
|
|
1025
|
-
let rightStart = Math.max(0, bytes.byteLength - rightBudget);
|
|
1026
|
-
while (rightStart < bytes.byteLength && isBrowserUtf8Continuation(bytes[rightStart]!)) {
|
|
1027
|
-
rightStart += 1;
|
|
1028
|
-
}
|
|
1029
|
-
return `${decoder.decode(bytes.subarray(0, leftEnd))}${marker}${decoder.decode(bytes.subarray(rightStart))}`;
|
|
1030
|
-
}
|
|
1031
|
-
|
|
1032
|
-
function settleBrowserEventTruncation(
|
|
1033
|
-
event: SessionEvent,
|
|
1034
|
-
truncation: {
|
|
1035
|
-
originalBytes: number | null;
|
|
1036
|
-
deliveredBytes: number;
|
|
1037
|
-
omittedBytes: number | null;
|
|
1038
|
-
estimatedDeliveredTokens: number;
|
|
1039
|
-
},
|
|
1040
|
-
): void {
|
|
1041
|
-
for (let attempt = 0; attempt < 16; attempt += 1) {
|
|
1042
|
-
const deliveredBytes = browserJsonBytes(event);
|
|
1043
|
-
const omittedBytes =
|
|
1044
|
-
truncation.originalBytes === null
|
|
1045
|
-
? null
|
|
1046
|
-
: Math.max(0, truncation.originalBytes - deliveredBytes);
|
|
1047
|
-
const estimatedDeliveredTokens = Math.ceil(deliveredBytes / 4);
|
|
1048
|
-
if (
|
|
1049
|
-
truncation.deliveredBytes === deliveredBytes &&
|
|
1050
|
-
truncation.omittedBytes === omittedBytes &&
|
|
1051
|
-
truncation.estimatedDeliveredTokens === estimatedDeliveredTokens
|
|
1052
|
-
) {
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
truncation.deliveredBytes = deliveredBytes;
|
|
1056
|
-
truncation.omittedBytes = omittedBytes;
|
|
1057
|
-
truncation.estimatedDeliveredTokens = estimatedDeliveredTokens;
|
|
1058
|
-
}
|
|
1059
|
-
throw new RangeError("Browser event byte accounting did not converge");
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
|
-
function browserUtf8Bytes(value: string): number {
|
|
1063
|
-
return encoder.encode(value).byteLength;
|
|
1064
|
-
}
|
|
1065
|
-
|
|
1066
|
-
function isBrowserUtf8Continuation(value: number): boolean {
|
|
1067
|
-
return (value & 0xc0) === 0x80;
|
|
1195
|
+
function browserJsonBytes(value: unknown): number {
|
|
1196
|
+
return encoder.encode(JSON.stringify(value)).byteLength;
|
|
1068
1197
|
}
|
|
1069
1198
|
|
|
1070
1199
|
function observeSessionStatus(
|
|
@@ -1083,6 +1212,68 @@ function observeSessionStatus(
|
|
|
1083
1212
|
return latest.status;
|
|
1084
1213
|
}
|
|
1085
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 };
|
|
1275
|
+
}
|
|
1276
|
+
|
|
1086
1277
|
type LoadedEventWindow = {
|
|
1087
1278
|
events: SessionEvent[];
|
|
1088
1279
|
oldestSequence: number | null;
|
|
@@ -1106,7 +1297,9 @@ async function loadEventWindow(
|
|
|
1106
1297
|
pageSize: number;
|
|
1107
1298
|
targetGroups: number;
|
|
1108
1299
|
maxFetches: number;
|
|
1300
|
+
boundaryPageCap?: number;
|
|
1109
1301
|
signal?: AbortSignal;
|
|
1302
|
+
isCurrent?: () => boolean;
|
|
1110
1303
|
},
|
|
1111
1304
|
): Promise<LoadedEventWindow> {
|
|
1112
1305
|
let cursor = options.before;
|
|
@@ -1122,6 +1315,7 @@ async function loadEventWindow(
|
|
|
1122
1315
|
pageSize: options.pageSize,
|
|
1123
1316
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1124
1317
|
});
|
|
1318
|
+
if (options.isCurrent?.() === false) throw abortError();
|
|
1125
1319
|
fetches += 1;
|
|
1126
1320
|
if (page.length === 0) {
|
|
1127
1321
|
reachedStart = true;
|
|
@@ -1140,19 +1334,19 @@ async function loadEventWindow(
|
|
|
1140
1334
|
// turn boundary already in the buffer — the dropped fragment is refetched by
|
|
1141
1335
|
// the next loadOlder (everything below the new oldest sequence), whose own
|
|
1142
1336
|
// window snaps the same way, so every seam lands on a turn start. Extra
|
|
1143
|
-
//
|
|
1144
|
-
//
|
|
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.
|
|
1145
1339
|
let snapPages = 0;
|
|
1146
1340
|
while (
|
|
1147
1341
|
!reachedStart &&
|
|
1148
1342
|
findBoundaryIndex(buffer) === -1 &&
|
|
1149
|
-
snapPages <
|
|
1150
|
-
fetches < options.maxFetches
|
|
1343
|
+
snapPages < (options.boundaryPageCap ?? 0)
|
|
1151
1344
|
) {
|
|
1152
1345
|
const page = await loadPreviousPage(client, workspaceId, sessionId, cursor, {
|
|
1153
1346
|
pageSize: options.pageSize,
|
|
1154
1347
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1155
1348
|
});
|
|
1349
|
+
if (options.isCurrent?.() === false) throw abortError();
|
|
1156
1350
|
fetches += 1;
|
|
1157
1351
|
snapPages += 1;
|
|
1158
1352
|
if (page.length === 0) {
|
|
@@ -1216,6 +1410,7 @@ async function loadForwardEventWindow(
|
|
|
1216
1410
|
targetGroups: number;
|
|
1217
1411
|
maxFetches: number;
|
|
1218
1412
|
signal?: AbortSignal;
|
|
1413
|
+
isCurrent?: () => boolean;
|
|
1219
1414
|
},
|
|
1220
1415
|
): Promise<LoadedForwardEventWindow> {
|
|
1221
1416
|
let cursor = options.after;
|
|
@@ -1231,6 +1426,7 @@ async function loadForwardEventWindow(
|
|
|
1231
1426
|
pageSize: options.pageSize,
|
|
1232
1427
|
...(options.signal ? { signal: options.signal } : {}),
|
|
1233
1428
|
});
|
|
1429
|
+
if (options.isCurrent?.() === false) throw abortError();
|
|
1234
1430
|
fetches += 1;
|
|
1235
1431
|
if (page.length === 0) {
|
|
1236
1432
|
reachedEnd = true;
|
|
@@ -1378,13 +1574,13 @@ function maxResumeSequenceOrNull(events: readonly SessionEvent[]): number | null
|
|
|
1378
1574
|
}
|
|
1379
1575
|
|
|
1380
1576
|
function eventResumeSequence(event: SessionEvent): number {
|
|
1381
|
-
const
|
|
1382
|
-
|
|
1383
|
-
return
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
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;
|
|
1388
1584
|
}
|
|
1389
1585
|
|
|
1390
1586
|
function assertAscending(events: SessionEvent[]): void {
|