@opengeni/react 3.1.0 → 3.1.3-canary.38890816741001
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +769 -22
- package/dist/accounts.js +89 -11
- package/dist/accounts.js.map +1 -1
- package/dist/approvals.d.ts +2 -1
- package/dist/artifacts-document.js +5 -3
- package/dist/artifacts-presentation.js +5 -3
- package/dist/artifacts-spreadsheet.d.ts +1 -1
- package/dist/artifacts-spreadsheet.js +5 -3
- package/dist/artifacts.d.ts +5 -1
- package/dist/artifacts.js +536 -42
- package/dist/artifacts.js.map +1 -1
- package/dist/browser-viewer-ZWR6MWJ7.js +19 -0
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/capability-logo-fallback.d.ts +3 -0
- package/dist/chat-interactive-block-BO2IWXVK.js +14 -0
- package/dist/chunk-2DDJ6YH6.js +41 -0
- package/dist/chunk-2DDJ6YH6.js.map +1 -0
- package/dist/chunk-2HVLQ26H.js +183 -0
- package/dist/chunk-2HVLQ26H.js.map +1 -0
- package/dist/{chunk-RGALCTWO.js → chunk-2I6R3GCZ.js} +4285 -3980
- package/dist/chunk-2I6R3GCZ.js.map +1 -0
- package/dist/{chunk-D3UIAJKY.js → chunk-2SFFYIH3.js} +1179 -166
- package/dist/chunk-2SFFYIH3.js.map +1 -0
- package/dist/chunk-3PVDYTTB.js +24 -0
- package/dist/chunk-3PVDYTTB.js.map +1 -0
- package/dist/chunk-3UZCWK46.js +298 -0
- package/dist/chunk-3UZCWK46.js.map +1 -0
- package/dist/chunk-47CKUXTW.js +293 -0
- package/dist/chunk-47CKUXTW.js.map +1 -0
- package/dist/chunk-4QYP6QUI.js +109 -0
- package/dist/chunk-4QYP6QUI.js.map +1 -0
- package/dist/chunk-5EY26VZ7.js +1053 -0
- package/dist/chunk-5EY26VZ7.js.map +1 -0
- package/dist/chunk-5I4VRWBB.js +56 -0
- package/dist/chunk-5I4VRWBB.js.map +1 -0
- package/dist/chunk-5WOR7UCL.js +191 -0
- package/dist/chunk-5WOR7UCL.js.map +1 -0
- package/dist/chunk-5YJLGOM5.js +51 -0
- package/dist/chunk-5YJLGOM5.js.map +1 -0
- package/dist/chunk-6JFN3OWG.js +386 -0
- package/dist/chunk-6JFN3OWG.js.map +1 -0
- package/dist/chunk-7QJT5O4S.js +35 -0
- package/dist/chunk-7QJT5O4S.js.map +1 -0
- package/dist/chunk-A55KV72M.js +402 -0
- package/dist/chunk-A55KV72M.js.map +1 -0
- package/dist/chunk-ADWCLI4G.js +2028 -0
- package/dist/chunk-ADWCLI4G.js.map +1 -0
- package/dist/chunk-B27E3V37.js +29 -0
- package/dist/chunk-B27E3V37.js.map +1 -0
- package/dist/chunk-B7TC2A33.js +83 -0
- package/dist/chunk-B7TC2A33.js.map +1 -0
- package/dist/chunk-BLL56F7R.js +385 -0
- package/dist/chunk-BLL56F7R.js.map +1 -0
- package/dist/chunk-BOFEQC6K.js +162 -0
- package/dist/chunk-BOFEQC6K.js.map +1 -0
- package/dist/chunk-CM7LVUKC.js +35 -0
- package/dist/chunk-CM7LVUKC.js.map +1 -0
- package/dist/chunk-CORUP6JB.js +122 -0
- package/dist/chunk-CORUP6JB.js.map +1 -0
- package/dist/chunk-CSX5FFKZ.js +35 -0
- package/dist/chunk-CSX5FFKZ.js.map +1 -0
- package/dist/chunk-EGVHJKGJ.js +3125 -0
- package/dist/chunk-EGVHJKGJ.js.map +1 -0
- package/dist/{chunk-NEXLLTP4.js → chunk-G76LSA76.js} +6 -5
- package/dist/chunk-G76LSA76.js.map +1 -0
- package/dist/{chunk-PEI6RANI.js → chunk-GQMRL52F.js} +311 -75
- package/dist/chunk-GQMRL52F.js.map +1 -0
- package/dist/{chunk-ELIYWBZR.js → chunk-GUUXOISA.js} +1113 -457
- package/dist/chunk-GUUXOISA.js.map +1 -0
- package/dist/{chunk-HUIKIDM5.js → chunk-I7X2P4FU.js} +225 -116
- package/dist/chunk-I7X2P4FU.js.map +1 -0
- package/dist/chunk-IGFZFLI2.js +1390 -0
- package/dist/chunk-IGFZFLI2.js.map +1 -0
- package/dist/{chunk-4MDLEHXL.js → chunk-IMSUVYQB.js} +26 -13
- package/dist/chunk-IMSUVYQB.js.map +1 -0
- package/dist/chunk-IOYHDEP3.js +38 -0
- package/dist/chunk-IOYHDEP3.js.map +1 -0
- package/dist/chunk-IZV5FTJC.js +55 -0
- package/dist/chunk-IZV5FTJC.js.map +1 -0
- package/dist/chunk-KF33GTWN.js +343 -0
- package/dist/chunk-KF33GTWN.js.map +1 -0
- package/dist/chunk-KSGQ5ODA.js +457 -0
- package/dist/chunk-KSGQ5ODA.js.map +1 -0
- package/dist/{chunk-OAM4WAAM.js → chunk-KZC6QRNR.js} +1513 -697
- package/dist/chunk-KZC6QRNR.js.map +1 -0
- package/dist/chunk-LJUMYUBO.js +218 -0
- package/dist/chunk-LJUMYUBO.js.map +1 -0
- package/dist/chunk-LSW2OJVV.js +380 -0
- package/dist/chunk-LSW2OJVV.js.map +1 -0
- package/dist/chunk-M357MHH3.js +2 -0
- package/dist/chunk-M357MHH3.js.map +1 -0
- package/dist/chunk-MK2TQO4Y.js +24 -0
- package/dist/chunk-MK2TQO4Y.js.map +1 -0
- package/dist/chunk-N7S72EXK.js +25 -0
- package/dist/chunk-N7S72EXK.js.map +1 -0
- package/dist/{chunk-XXWH2JLC.js → chunk-NKMYZFHA.js} +616 -621
- package/dist/chunk-NKMYZFHA.js.map +1 -0
- package/dist/chunk-NX4ADH73.js +14 -0
- package/dist/chunk-NX4ADH73.js.map +1 -0
- package/dist/chunk-O7F6UJO4.js +71 -0
- package/dist/chunk-O7F6UJO4.js.map +1 -0
- package/dist/chunk-OVB5WIG5.js +14064 -0
- package/dist/chunk-OVB5WIG5.js.map +1 -0
- package/dist/chunk-OYQQJL3J.js +16 -0
- package/dist/chunk-OYQQJL3J.js.map +1 -0
- package/dist/{chunk-H5YGYNXS.js → chunk-P4BNUO57.js} +60 -17
- package/dist/chunk-P4BNUO57.js.map +1 -0
- package/dist/{chunk-JVYQRRC4.js → chunk-PEHTX3V7.js} +21 -1
- package/dist/chunk-PEHTX3V7.js.map +1 -0
- package/dist/chunk-PMV53IMM.js +26 -0
- package/dist/chunk-PMV53IMM.js.map +1 -0
- package/dist/{chunk-KCGXPP5V.js → chunk-PYEOPA4M.js} +13 -7
- package/dist/chunk-PYEOPA4M.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-QHUE7E4N.js} +8 -3
- package/dist/chunk-QHUE7E4N.js.map +1 -0
- package/dist/chunk-QV3DJVRZ.js +11 -0
- package/dist/chunk-QV3DJVRZ.js.map +1 -0
- package/dist/chunk-R5SBNG7S.js +810 -0
- package/dist/chunk-R5SBNG7S.js.map +1 -0
- package/dist/chunk-RI42H5IU.js +326 -0
- package/dist/chunk-RI42H5IU.js.map +1 -0
- package/dist/chunk-S2QH6EZ5.js +211 -0
- package/dist/chunk-S2QH6EZ5.js.map +1 -0
- package/dist/chunk-SHLZMAJN.js +304 -0
- package/dist/chunk-SHLZMAJN.js.map +1 -0
- package/dist/chunk-TGXK4BAZ.js +293 -0
- package/dist/chunk-TGXK4BAZ.js.map +1 -0
- package/dist/{chunk-5YK2ZDIG.js → chunk-TNEZNMSN.js} +4 -10
- package/dist/chunk-TNEZNMSN.js.map +1 -0
- package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
- package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
- package/dist/chunk-UNJPZGNK.js +63 -0
- package/dist/chunk-UNJPZGNK.js.map +1 -0
- package/dist/{chunk-MZ2G2XBA.js → chunk-V5ZUJT2T.js} +205 -45
- package/dist/chunk-V5ZUJT2T.js.map +1 -0
- package/dist/chunk-VIUEOHXY.js +394 -0
- package/dist/chunk-VIUEOHXY.js.map +1 -0
- package/dist/chunk-WIUKB3LD.js +1911 -0
- package/dist/chunk-WIUKB3LD.js.map +1 -0
- package/dist/{chunk-ICA6R6TW.js → chunk-WNN7NO2N.js} +1451 -355
- package/dist/chunk-WNN7NO2N.js.map +1 -0
- package/dist/{chunk-YJFGAFBD.js → chunk-X5UVUIMF.js} +1892 -1223
- package/dist/chunk-X5UVUIMF.js.map +1 -0
- package/dist/chunk-XDA5QJE7.js +402 -0
- package/dist/chunk-XDA5QJE7.js.map +1 -0
- package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
- package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
- package/dist/chunk-XHF7OYAN.js +1150 -0
- package/dist/chunk-XHF7OYAN.js.map +1 -0
- package/dist/chunk-YSRBFJMQ.js +43 -0
- package/dist/chunk-YSRBFJMQ.js.map +1 -0
- package/dist/chunk-ZOZAPAUO.js +174 -0
- package/dist/chunk-ZOZAPAUO.js.map +1 -0
- package/dist/chunk-ZQV5W7YC.js +809 -0
- package/dist/chunk-ZQV5W7YC.js.map +1 -0
- package/dist/chunk-ZUWF4NF7.js +13 -0
- package/dist/chunk-ZUWF4NF7.js.map +1 -0
- package/dist/client.d.ts +16 -7
- package/dist/clipboard.js +3 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/anthropic-mark.d.ts +3 -0
- package/dist/components/approval-surface.d.ts +19 -6
- package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
- package/dist/components/artifacts/artifact-sandbox.d.ts +30 -0
- package/dist/components/artifacts/artifact-surface.d.ts +4 -2
- package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
- package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
- package/dist/components/artifacts/document-editor.d.ts +4 -2
- package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
- package/dist/components/artifacts/editable-artifact-view.d.ts +48 -0
- package/dist/components/artifacts/editable-document.d.ts +1 -1
- package/dist/components/artifacts/editable-presentation.d.ts +2 -2
- package/dist/components/artifacts/editable-spreadsheet.d.ts +8 -3
- package/dist/components/artifacts/index.d.ts +9 -2
- package/dist/components/artifacts/inline-html-document.d.ts +2 -0
- package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
- package/dist/components/artifacts/presentation-editor.d.ts +6 -2
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +23 -3
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +26 -0
- package/dist/components/artifacts/spreadsheet-command-queue.d.ts +20 -0
- package/dist/components/artifacts/spreadsheet-download.d.ts +7 -0
- package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
- package/dist/components/artifacts/spreadsheet-grid.d.ts +10 -3
- package/dist/components/artifacts/spreadsheet-resize.d.ts +50 -0
- package/dist/components/artifacts/spreadsheet-submissions.d.ts +9 -0
- package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
- package/dist/components/browser-select-control.d.ts +16 -0
- package/dist/components/browser-viewer.d.ts +11 -2
- package/dist/components/chat-composer.d.ts +21 -3
- package/dist/components/child-session-link.d.ts +9 -0
- package/dist/components/claude-mark.d.ts +3 -0
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/composer-transcription-control.d.ts +7 -0
- package/dist/components/composer.d.ts +34 -67
- package/dist/components/computer-viewer.d.ts +1 -1
- package/dist/components/copy-button.d.ts +3 -1
- package/dist/components/desktop-viewer.d.ts +1 -1
- package/dist/components/fleet-tile.d.ts +2 -2
- package/dist/components/grok-mark.d.ts +26 -0
- package/dist/components/human-input-form.d.ts +28 -32
- package/dist/components/human-input-surface.d.ts +7 -2
- package/dist/components/linkified-text.d.ts +18 -0
- package/dist/components/machine-input-display.d.ts +22 -1
- package/dist/components/machines/agent-status-icon.d.ts +6 -0
- package/dist/components/machines/machine-detail.d.ts +7 -1
- package/dist/components/machines/machine-status.d.ts +48 -0
- package/dist/components/markdown-table-layout.d.ts +18 -0
- package/dist/components/markdown.d.ts +37 -4
- package/dist/components/message-timeline.d.ts +86 -7
- package/dist/components/model-mark.d.ts +29 -0
- package/dist/components/model-policy-picker-menu.d.ts +9 -0
- package/dist/components/model-policy-picker.d.ts +41 -25
- package/dist/components/new-conversation.d.ts +32 -0
- package/dist/components/open-geni-chat.d.ts +73 -0
- package/dist/components/open-geni-links.d.ts +68 -0
- package/dist/components/openrouter-mark.d.ts +6 -0
- package/dist/components/opper-mark.d.ts +7 -0
- package/dist/components/preview-loading.d.ts +2 -0
- package/dist/components/provider-recovery-notice.d.ts +16 -0
- package/dist/components/queue-item-content.d.ts +9 -0
- package/dist/components/rehype-secret-refs.d.ts +23 -0
- package/dist/components/remark-soft-line-breaks.d.ts +38 -0
- package/dist/components/sandbox-files.d.ts +3 -1
- package/dist/components/sandbox-workspace.d.ts +18 -0
- package/dist/components/secret-question-field.d.ts +31 -0
- package/dist/components/secret-ref-chip.d.ts +35 -0
- package/dist/components/session-capability-frame.d.ts +37 -0
- package/dist/components/session-chrome.d.ts +57 -8
- package/dist/components/session-commands-panel.d.ts +6 -0
- package/dist/components/session-commands.d.ts +6 -0
- package/dist/components/session-connection-request.d.ts +8 -0
- package/dist/components/session-conversation.d.ts +157 -0
- package/dist/components/session-list.d.ts +39 -0
- package/dist/components/session-mcp-capability-card.d.ts +41 -0
- package/dist/components/session-proxy-scope.d.ts +35 -0
- package/dist/components/session-status.d.ts +4 -1
- package/dist/components/session-usage-limit-notice.d.ts +39 -0
- package/dist/components/timeline-anchor.d.ts +24 -0
- package/dist/components/timeline-annotation-chrome.d.ts +25 -0
- package/dist/components/timeline-annotation-layout.d.ts +42 -0
- package/dist/components/timeline-annotation-markers.d.ts +8 -0
- package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
- package/dist/components/timeline-annotation-shared.d.ts +31 -0
- package/dist/components/timeline-annotations-dialog.d.ts +3 -2
- package/dist/components/timeline-annotations.d.ts +13 -3
- package/dist/components/timeline-reading-progress.d.ts +11 -0
- package/dist/components/timeline-search.d.ts +34 -0
- package/dist/components/timeline-settlement.d.ts +16 -0
- package/dist/components/tip-follow.d.ts +16 -0
- package/dist/components/tool-action-review.d.ts +47 -0
- package/dist/components/tool-review-history.d.ts +25 -0
- package/dist/components/usage-limit-notice.d.ts +49 -0
- package/dist/components/usage-member-list.d.ts +91 -0
- package/dist/components/usage-meter.d.ts +73 -0
- package/dist/components/use-viewer-menu-dismiss.d.ts +2 -0
- package/dist/components/user-message-body.d.ts +10 -1
- package/dist/composer-messages.d.ts +74 -0
- package/dist/composer-messages.js +9 -0
- package/dist/composer-messages.js.map +1 -0
- package/dist/composer-voice-events.d.ts +8 -0
- package/dist/composer.d.ts +5 -4
- package/dist/composer.js +26 -13
- package/dist/computer-viewer-GMLDXPON.js +20 -0
- package/dist/computer-viewer-GMLDXPON.js.map +1 -0
- package/dist/connect-account-identity.d.ts +8 -0
- package/dist/connect-accounts.d.ts +15 -0
- package/dist/connect-chooser.d.ts +18 -0
- package/dist/connect-panel.d.ts +18 -0
- package/dist/connect-setup.d.ts +18 -0
- package/dist/connect.d.ts +21 -0
- package/dist/connect.js +2149 -0
- package/dist/connect.js.map +1 -0
- package/dist/connection-catalog.d.ts +38 -0
- package/dist/connection-discovery.d.ts +12 -0
- package/dist/connection-installed.d.ts +17 -0
- package/dist/connection-logo.d.ts +9 -0
- package/dist/connection-service-logo.d.ts +8 -0
- package/dist/connection-service-presentation.d.ts +13 -0
- package/dist/connection-skeleton.d.ts +5 -0
- package/dist/connection-type-picker.d.ts +6 -0
- package/dist/conversation-timeline.d.ts +5 -0
- package/dist/desktop.d.ts +7 -0
- package/dist/desktop.js +27 -0
- package/dist/desktop.js.map +1 -0
- package/dist/device-authorization.d.ts +23 -0
- package/dist/diffs.d.ts +8 -0
- package/dist/diffs.js +15 -0
- package/dist/diffs.js.map +1 -0
- package/dist/discovery-cache.d.ts +5 -0
- package/dist/editor.d.ts +7 -0
- package/dist/editor.js +22 -0
- package/dist/editor.js.map +1 -0
- package/dist/embedded-new-voice-OCRNT6U5.js +18 -0
- package/dist/embedded-new-voice-OCRNT6U5.js.map +1 -0
- package/dist/embedded-voice-RPHPYVBT.js +18 -0
- package/dist/embedded-voice-RPHPYVBT.js.map +1 -0
- package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-SI63O5V4.js} +4 -1
- package/dist/fleet-decision-row-SI63O5V4.js.map +1 -0
- package/dist/hooks/latest-question.d.ts +25 -0
- package/dist/hooks/shared-machines-list.d.ts +4 -0
- package/dist/hooks/shared-query.d.ts +113 -0
- package/dist/hooks/use-attached-browsers.d.ts +1 -1
- package/dist/hooks/use-auth-runs.d.ts +1 -4
- package/dist/hooks/use-available-models.d.ts +1 -1
- package/dist/hooks/use-browser-frame-stream.d.ts +1 -1
- package/dist/hooks/use-browser-session.d.ts +12 -1
- package/dist/hooks/use-client-config-flags.d.ts +49 -0
- package/dist/hooks/use-codex-accounts.d.ts +59 -10
- package/dist/hooks/use-composer.d.ts +24 -2
- package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
- package/dist/hooks/use-computer-input-posture.d.ts +13 -0
- package/dist/hooks/use-computer-session.d.ts +4 -0
- package/dist/hooks/use-connect.d.ts +23 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +12 -0
- package/dist/hooks/use-goal.d.ts +2 -1
- package/dist/hooks/use-last-started-turn-policy.d.ts +5 -1
- package/dist/hooks/use-new-conversation.d.ts +56 -0
- package/dist/hooks/use-new-session-draft.d.ts +74 -0
- package/dist/hooks/use-realtime-voice-models.d.ts +7 -0
- package/dist/hooks/use-sandbox-files.d.ts +4 -1
- package/dist/hooks/use-session-conversation.d.ts +49 -0
- package/dist/hooks/use-session-events.d.ts +28 -7
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session-model-account.d.ts +48 -0
- package/dist/hooks/use-supergrok-accounts.d.ts +7 -1
- package/dist/hooks/use-terminal-stream.d.ts +11 -5
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/hooks/use-vaults.d.ts +53 -0
- package/dist/hooks/use-voice-input.d.ts +8 -1
- package/dist/hooks/use-workspace-edit.d.ts +4 -0
- package/dist/hooks/use-workspace-sessions.d.ts +22 -5
- package/dist/human-input-model.d.ts +122 -0
- package/dist/identity-link-accounts.d.ts +11 -0
- package/dist/identity-link-consent.d.ts +18 -0
- package/dist/index.d.ts +82 -20
- package/dist/index.js +2350 -6605
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +14 -24
- package/dist/interaction.js.map +1 -1
- package/dist/latest-question-R77KWSUP.js +118 -0
- package/dist/latest-question-R77KWSUP.js.map +1 -0
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +58 -4
- package/dist/lib/host-theme.d.ts +77 -0
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/lib/menu-styles.d.ts +22 -0
- package/dist/lib/notify-observer.d.ts +2 -0
- package/dist/lib/pierre-diffs-loader.d.ts +20 -0
- package/dist/lib/provider-recovery.d.ts +55 -0
- package/dist/lib/scrollbars.d.ts +11 -0
- package/dist/lib/terminal-input-readiness.d.ts +11 -0
- package/dist/lib/transient-retry.d.ts +25 -0
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/lib/xterm-theme.d.ts +7 -5
- package/dist/machines.js +9 -5
- package/dist/markdown-table-layout-3PV2UAAH.js +113 -0
- package/dist/markdown-table-layout-3PV2UAAH.js.map +1 -0
- package/dist/mcp-connection-status.d.ts +14 -0
- package/dist/model-mark-paths.d.ts +34 -0
- package/dist/model-mark-paths.js +16 -0
- package/dist/model-mark-paths.js.map +1 -0
- package/dist/model-picker-model.d.ts +111 -0
- package/dist/model-picker-model.js +25 -0
- package/dist/model-picker-model.js.map +1 -0
- package/dist/model-policy-picker-menu-7GJ5URPR.js +17 -0
- package/dist/model-policy-picker-menu-7GJ5URPR.js.map +1 -0
- package/dist/model-policy.d.ts +30 -3
- package/dist/model-policy.js +14 -1
- package/dist/native-previews.d.ts +28 -0
- package/dist/native-previews.js +82 -0
- package/dist/native-previews.js.map +1 -0
- package/dist/new-session-draft.d.ts +7 -0
- package/dist/new-session-draft.js +639 -0
- package/dist/new-session-draft.js.map +1 -0
- package/dist/notification-text.d.ts +27 -0
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
- package/dist/older-history.d.ts +3 -2
- package/dist/organization-model.d.ts +32 -0
- package/dist/organization-model.js +65 -0
- package/dist/organization-model.js.map +1 -0
- package/dist/{platform-activity-row-BJHUHXAT.js → platform-activity-row-5OCKOQWZ.js} +16 -78
- package/dist/platform-activity-row-5OCKOQWZ.js.map +1 -0
- package/dist/plugin-details.d.ts +14 -0
- package/dist/plugin-discovery.d.ts +25 -0
- package/dist/preview-loading-paint.d.ts +20 -0
- package/dist/provider.d.ts +25 -2
- package/dist/queue-presentation-model.d.ts +72 -0
- package/dist/queue-presentation-model.js +38 -0
- package/dist/queue-presentation-model.js.map +1 -0
- package/dist/{queue-surface-implementation-BSKUWHRI.js → queue-surface-implementation-GJBH7NKO.js} +65 -265
- package/dist/queue-surface-implementation-GJBH7NKO.js.map +1 -0
- package/dist/realtime/embedded-new-voice.d.ts +1 -0
- package/dist/realtime/embedded-voice.d.ts +1 -0
- package/dist/realtime/realtime-control.d.ts +4 -65
- package/dist/realtime/session-realtime.d.ts +78 -0
- package/dist/realtime.js +25 -1294
- package/dist/realtime.js.map +1 -1
- package/dist/sandbox-files-O27IPOZW.js +21 -0
- package/dist/sandbox-files-O27IPOZW.js.map +1 -0
- package/dist/sandbox-label-model.d.ts +30 -0
- package/dist/sandbox-label-model.js +20 -0
- package/dist/sandbox-label-model.js.map +1 -0
- package/dist/scrollbars.d.ts +1 -0
- package/dist/scrollbars.js +10 -0
- package/dist/scrollbars.js.map +1 -0
- package/dist/secret-reference-label.d.ts +14 -0
- package/dist/secrets-ui.d.ts +57 -0
- package/dist/session-agents-model.d.ts +10 -0
- package/dist/session-agents-model.js +25 -0
- package/dist/session-agents-model.js.map +1 -0
- package/dist/session-attention-model.d.ts +8 -0
- package/dist/session-attention-model.js +47 -0
- package/dist/session-attention-model.js.map +1 -0
- package/dist/session-auth-recommendation.d.ts +5 -0
- package/dist/session-capability-policy.d.ts +31 -0
- package/dist/session-feedback-model.d.ts +20 -0
- package/dist/session-feedback-model.js +56 -0
- package/dist/session-feedback-model.js.map +1 -0
- package/dist/session-list-model.d.ts +138 -0
- package/dist/session-list-model.js +274 -0
- package/dist/session-list-model.js.map +1 -0
- package/dist/session-realtime-7DJOIHQW.js +21 -0
- package/dist/session-realtime-7DJOIHQW.js.map +1 -0
- package/dist/session-realtime.d.ts +7 -0
- package/dist/session-realtime.js +19 -0
- package/dist/session-realtime.js.map +1 -0
- package/dist/session-status-model.d.ts +36 -0
- package/dist/session-ui.d.ts +39 -1
- package/dist/session-ui.js +126 -12
- package/dist/session-ui.js.map +1 -1
- package/dist/session.d.ts +8 -2
- package/dist/session.js +35 -20
- package/dist/site-snapshot.d.ts +14 -0
- package/dist/sites-ui.d.ts +24 -0
- package/dist/sites.d.ts +3 -0
- package/dist/sites.js +14 -0
- package/dist/sites.js.map +1 -0
- package/dist/skill-discovery.d.ts +47 -0
- package/dist/terminal.d.ts +8 -0
- package/dist/terminal.js +32 -0
- package/dist/terminal.js.map +1 -0
- package/dist/testing/session-scenarios.d.ts +10 -0
- package/dist/testing/session-scenarios.js +367 -0
- package/dist/testing/session-scenarios.js.map +1 -0
- package/dist/timeline/activity-rail.d.ts +11 -2
- package/dist/timeline/agent-identity.d.ts +30 -0
- package/dist/timeline/agent-row.d.ts +31 -0
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/compaction-copy.d.ts +46 -0
- package/dist/timeline/entrance.d.ts +13 -1
- package/dist/timeline/focus-sequence.d.ts +9 -0
- package/dist/timeline/genie-copy.d.ts +3 -0
- package/dist/timeline/genie-loading.d.ts +47 -0
- package/dist/timeline/index.d.ts +10 -5
- package/dist/timeline/knowledge-receipt.d.ts +17 -0
- package/dist/timeline/message-sender.d.ts +19 -0
- package/dist/timeline/notice-presentation.d.ts +13 -0
- package/dist/timeline/parsers.d.ts +11 -0
- package/dist/timeline/patch-apply-command.d.ts +18 -0
- package/dist/timeline/platform-activity-presentation.d.ts +21 -0
- package/dist/timeline/platform-activity-row.d.ts +1 -2
- package/dist/timeline/presented-image.d.ts +11 -0
- package/dist/timeline/projection.d.ts +31 -2
- package/dist/timeline/provider-logo.d.ts +15 -0
- package/dist/timeline/question-nav-model.d.ts +23 -0
- package/dist/timeline/registry.d.ts +12 -1
- package/dist/timeline/retained-image.d.ts +18 -0
- package/dist/timeline/retained-loaders.d.ts +27 -0
- package/dist/timeline/rolling-activity.d.ts +11 -0
- package/dist/timeline/screenshot-lightbox.d.ts +23 -1
- package/dist/timeline/session-usage-limit-row.d.ts +11 -0
- package/dist/timeline/shared.d.ts +4 -1
- package/dist/timeline/startup-dispatch-details.d.ts +5 -0
- package/dist/timeline/startup-preference.d.ts +3 -0
- package/dist/timeline/startup-timings.d.ts +4 -0
- package/dist/timeline/tool-display-name.d.ts +5 -2
- package/dist/timeline/tool-presentation.d.ts +172 -0
- package/dist/timeline/turn-summary-model.d.ts +95 -0
- package/dist/timeline/turn-summary.d.ts +20 -54
- package/dist/timeline/types.d.ts +179 -7
- package/dist/timeline/work-presentation.d.ts +60 -0
- package/dist/timeline-annotation-markers-U3G66KUD.js +166 -0
- package/dist/timeline-annotation-markers-U3G66KUD.js.map +1 -0
- package/dist/timeline-annotation-selection-BI7QTPTZ.js +242 -0
- package/dist/timeline-annotation-selection-BI7QTPTZ.js.map +1 -0
- package/dist/timeline-annotations-dialog-FOMHSDTE.js +274 -0
- package/dist/timeline-annotations-dialog-FOMHSDTE.js.map +1 -0
- package/dist/timeline-model.d.ts +29 -0
- package/dist/timeline-model.js +254 -0
- package/dist/timeline-model.js.map +1 -0
- package/dist/usage/allowance-copy.d.ts +25 -0
- package/dist/usage/session-usage-limit.d.ts +88 -0
- package/dist/usage/summary.d.ts +35 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.js +1187 -0
- package/dist/usage.js.map +1 -0
- package/dist/voice-recording-owner.d.ts +7 -0
- package/package.json +107 -7
- package/src/accounts.tsx +116 -14
- package/src/approvals.ts +8 -1
- package/src/artifacts-spreadsheet.ts +1 -0
- package/src/artifacts.ts +44 -0
- package/src/assets/README.md +8 -0
- package/src/assets/gmail.ico +0 -0
- package/src/assets/outlook.ico +0 -0
- package/src/capability-catalog-row.tsx +94 -0
- package/src/capability-logo-fallback.ts +22 -0
- package/src/client.ts +20 -31
- package/src/commands/registry.ts +18 -15
- package/src/commands/types.ts +2 -0
- package/src/components/anthropic-mark.tsx +10 -0
- package/src/components/approval-surface.tsx +401 -138
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +189 -0
- package/src/components/artifacts/artifact-surface.tsx +40 -20
- package/src/components/artifacts/chat-interactive-block.tsx +260 -0
- package/src/components/artifacts/deferred-chat-media.tsx +67 -0
- package/src/components/artifacts/document-editor.tsx +141 -16
- package/src/components/artifacts/editable-artifact-ui.tsx +4 -2
- package/src/components/artifacts/editable-artifact-view.tsx +197 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
- package/src/components/artifacts/editable-document.tsx +37 -36
- package/src/components/artifacts/editable-presentation.tsx +36 -18
- package/src/components/artifacts/editable-spreadsheet.tsx +543 -103
- package/src/components/artifacts/index.ts +45 -0
- package/src/components/artifacts/inline-html-document.ts +24 -0
- package/src/components/artifacts/latest-task-runner.ts +49 -0
- package/src/components/artifacts/presentation-editor.tsx +323 -67
- package/src/components/artifacts/published-html-artifact-frame.tsx +122 -5
- package/src/components/artifacts/session-artifact-viewer.tsx +378 -0
- package/src/components/artifacts/site-view.tsx +121 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
- package/src/components/artifacts/spreadsheet-command-queue.ts +184 -0
- package/src/components/artifacts/spreadsheet-download.tsx +109 -0
- package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +344 -144
- package/src/components/artifacts/spreadsheet-resize.tsx +421 -0
- package/src/components/artifacts/spreadsheet-submissions.tsx +46 -0
- package/src/components/artifacts/visualization-assets/base.css +1015 -0
- package/src/components/artifacts/visualization-assets/helpers.html +349 -0
- package/src/components/artifacts/visualization-assets.gen.ts +3 -0
- package/src/components/browser-select-control.tsx +312 -0
- package/src/components/browser-viewer.tsx +778 -318
- package/src/components/chat-composer.tsx +47 -5
- package/src/components/child-session-link.tsx +36 -0
- package/src/components/claude-mark.tsx +11 -0
- package/src/components/code-editor.tsx +13 -16
- package/src/components/composer-transcription-control.tsx +128 -30
- package/src/components/composer.tsx +404 -195
- package/src/components/computer-viewer.tsx +880 -253
- package/src/components/copy-button.tsx +13 -5
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/enrollment-consent.tsx +1 -1
- package/src/components/enrollment-device-flow.tsx +1 -1
- package/src/components/file-browser.tsx +10 -14
- package/src/components/fleet-tile.tsx +9 -18
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/grok-mark.tsx +34 -0
- package/src/components/human-input-form.tsx +590 -283
- package/src/components/human-input-surface.tsx +11 -0
- package/src/components/interaction-intervention-banner.tsx +2 -2
- package/src/components/linkified-text.tsx +79 -0
- package/src/components/machine-card.tsx +35 -78
- package/src/components/machine-dock-bar.tsx +1 -1
- package/src/components/machine-input-display.ts +197 -3
- package/src/components/machines/agent-status-icon.tsx +31 -0
- package/src/components/machines/machine-detail.tsx +200 -31
- package/src/components/machines/machine-operation-policy.tsx +1 -1
- package/src/components/machines/machine-status.ts +241 -0
- package/src/components/machines-dashboard.tsx +2 -17
- package/src/components/markdown-table-layout.ts +143 -0
- package/src/components/markdown.tsx +466 -129
- package/src/components/message-timeline.tsx +2567 -728
- package/src/components/model-mark.tsx +102 -0
- package/src/components/model-picker.tsx +15 -4
- package/src/components/model-policy-picker-menu.tsx +371 -0
- package/src/components/model-policy-picker.tsx +256 -412
- package/src/components/new-conversation.tsx +221 -0
- package/src/components/open-geni-chat.tsx +309 -0
- package/src/components/open-geni-links.tsx +179 -0
- package/src/components/openrouter-mark.tsx +13 -0
- package/src/components/opper-mark.tsx +18 -0
- package/src/components/pierre-diff.tsx +41 -16
- package/src/components/pierre-file.tsx +35 -10
- package/src/components/preview-loading.tsx +109 -0
- package/src/components/provider-recovery-notice.tsx +49 -0
- package/src/components/queue-item-content.ts +14 -0
- package/src/components/queue-surface-implementation.tsx +51 -356
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/rehype-secret-refs.ts +98 -0
- package/src/components/remark-soft-line-breaks.ts +166 -0
- package/src/components/sandbox-files.tsx +91 -15
- package/src/components/sandbox-terminal.tsx +84 -13
- package/src/components/sandbox-workspace.tsx +147 -51
- package/src/components/secret-question-field.tsx +324 -0
- package/src/components/secret-ref-chip.tsx +185 -0
- package/src/components/session-capability-frame.tsx +197 -0
- package/src/components/session-chrome.tsx +842 -251
- package/src/components/session-commands-panel.tsx +126 -0
- package/src/components/session-commands.tsx +18 -0
- package/src/components/session-connection-request.tsx +97 -0
- package/src/components/session-conversation.tsx +876 -0
- package/src/components/session-list.tsx +257 -0
- package/src/components/session-mcp-capability-card.tsx +680 -0
- package/src/components/session-proxy-scope.tsx +153 -0
- package/src/components/session-status.tsx +26 -17
- package/src/components/session-usage-limit-notice.tsx +174 -0
- package/src/components/timeline-anchor.tsx +119 -0
- package/src/components/timeline-annotation-chrome.tsx +196 -0
- package/src/components/timeline-annotation-layout.ts +268 -0
- package/src/components/timeline-annotation-markers.tsx +185 -0
- package/src/components/timeline-annotation-reveal-context.ts +9 -0
- package/src/components/timeline-annotation-selection.tsx +106 -54
- package/src/components/timeline-annotation-shared.ts +245 -0
- package/src/components/timeline-annotations-dialog.tsx +185 -128
- package/src/components/timeline-annotations.tsx +189 -45
- package/src/components/timeline-reading-progress.ts +80 -0
- package/src/components/timeline-search.tsx +168 -0
- package/src/components/timeline-settlement.ts +60 -0
- package/src/components/tip-follow.ts +52 -0
- package/src/components/tool-action-review.tsx +650 -0
- package/src/components/tool-review-history.tsx +88 -0
- package/src/components/tooltip.tsx +38 -5
- package/src/components/usage-limit-notice.tsx +179 -0
- package/src/components/usage-member-list.tsx +792 -0
- package/src/components/usage-meter.tsx +382 -0
- package/src/components/use-viewer-menu-dismiss.ts +33 -0
- package/src/components/user-message-body.tsx +27 -2
- package/src/components/workspace-dock.tsx +18 -1
- package/src/composer-messages.ts +150 -0
- package/src/composer-voice-events.ts +12 -0
- package/src/composer.ts +6 -1
- package/src/connect-account-identity.tsx +34 -0
- package/src/connect-accounts.tsx +233 -0
- package/src/connect-chooser.tsx +336 -0
- package/src/connect-panel.tsx +99 -0
- package/src/connect-setup.tsx +355 -0
- package/src/connect.ts +60 -0
- package/src/connection-assets.d.ts +4 -0
- package/src/connection-catalog.tsx +162 -0
- package/src/connection-discovery.tsx +238 -0
- package/src/connection-installed.tsx +51 -0
- package/src/connection-logo.tsx +37 -0
- package/src/connection-service-logo.tsx +55 -0
- package/src/connection-service-presentation.ts +46 -0
- package/src/connection-skeleton.tsx +16 -0
- package/src/connection-type-picker.tsx +36 -0
- package/src/conversation-timeline.ts +92 -0
- package/src/desktop.ts +24 -0
- package/src/device-authorization.tsx +98 -0
- package/src/diffs.ts +16 -0
- package/src/discovery-cache.ts +42 -0
- package/src/editor.ts +13 -0
- package/src/hooks/internal.ts +3 -2
- package/src/hooks/latest-question.ts +164 -0
- package/src/hooks/shared-machines-list.ts +34 -1
- package/src/hooks/shared-query.ts +721 -0
- package/src/hooks/use-attached-browsers.ts +1 -1
- package/src/hooks/use-auth-runs.ts +0 -27
- package/src/hooks/use-available-models.ts +21 -15
- package/src/hooks/use-browser-frame-stream.ts +19 -2
- package/src/hooks/use-browser-session.ts +680 -205
- package/src/hooks/use-browser-sessions.ts +32 -1
- package/src/hooks/use-client-config-flags.ts +118 -0
- package/src/hooks/use-codex-accounts.ts +247 -47
- package/src/hooks/use-composer.ts +276 -47
- package/src/hooks/use-computer-frame-stream.ts +25 -7
- package/src/hooks/use-computer-input-posture.ts +94 -0
- package/src/hooks/use-computer-session.ts +409 -95
- package/src/hooks/use-connect.ts +23 -0
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-file-attachments.ts +80 -4
- package/src/hooks/use-goal.ts +107 -13
- package/src/hooks/use-last-started-turn-policy.ts +46 -14
- package/src/hooks/use-machines.ts +4 -0
- package/src/hooks/use-new-conversation.ts +374 -0
- package/src/hooks/use-new-session-draft.ts +813 -0
- package/src/hooks/use-realtime-voice-models.ts +46 -0
- package/src/hooks/use-sandbox-files.ts +68 -20
- package/src/hooks/use-session-background-commands.ts +22 -11
- package/src/hooks/use-session-capabilities.ts +3 -2
- package/src/hooks/use-session-conversation.ts +150 -0
- package/src/hooks/use-session-events.ts +703 -382
- package/src/hooks/use-session-lineage.ts +20 -16
- package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
- package/src/hooks/use-session-model-account.ts +174 -0
- package/src/hooks/use-session.ts +53 -21
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-supergrok-accounts.ts +22 -8
- package/src/hooks/use-terminal-stream.ts +153 -27
- package/src/hooks/use-transcription.ts +6 -0
- package/src/hooks/use-turn-queue.ts +2 -1
- package/src/hooks/use-usage.ts +100 -0
- package/src/hooks/use-vaults.ts +153 -0
- package/src/hooks/use-video-artifact-playback.ts +1 -1
- package/src/hooks/use-voice-input.ts +127 -19
- package/src/hooks/use-workspace-capture.ts +3 -2
- package/src/hooks/use-workspace-edit.ts +23 -5
- package/src/hooks/use-workspace-sessions.ts +97 -32
- package/src/human-input-model.ts +359 -0
- package/src/identity-link-accounts.tsx +148 -0
- package/src/identity-link-consent.tsx +192 -0
- package/src/index.ts +262 -16
- package/src/interaction.ts +1 -0
- package/src/lib/clipboard.ts +29 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +213 -16
- package/src/lib/host-theme.ts +529 -0
- package/src/lib/interaction-errors.ts +15 -0
- package/src/lib/menu-styles.ts +44 -0
- package/src/lib/notify-observer.ts +15 -0
- package/src/lib/pierre-diffs-loader.ts +53 -0
- package/src/lib/provider-recovery.ts +243 -0
- package/src/lib/scrollbars.ts +30 -0
- package/src/lib/terminal-input-readiness.ts +51 -0
- package/src/lib/transient-retry.ts +99 -0
- package/src/lib/workbench-peers.ts +62 -0
- package/src/lib/xterm-theme.ts +23 -21
- package/src/mcp-connection-status.ts +35 -0
- package/src/model-mark-paths.ts +53 -0
- package/src/model-picker-model.ts +330 -0
- package/src/model-policy.ts +299 -74
- package/src/native-previews.ts +100 -0
- package/src/new-session-draft.ts +22 -0
- package/src/notification-text.ts +81 -0
- package/src/older-history.ts +22 -4
- package/src/organization-model.ts +118 -0
- package/src/plugin-details.tsx +185 -0
- package/src/plugin-discovery.tsx +196 -0
- package/src/preview-loading-paint.ts +68 -0
- package/src/provider.tsx +74 -15
- package/src/queue-presentation-model.ts +464 -0
- package/src/realtime/dropdown-menu.tsx +19 -12
- package/src/realtime/embedded-new-voice.tsx +1 -0
- package/src/realtime/embedded-voice.tsx +3 -0
- package/src/realtime/realtime-control.tsx +138 -501
- package/src/realtime/session-realtime.ts +566 -0
- package/src/sandbox-label-model.ts +53 -0
- package/src/scrollbars.ts +3 -0
- package/src/secret-reference-label.ts +43 -0
- package/src/secrets-ui.tsx +59 -0
- package/src/session-agents-model.ts +33 -0
- package/src/session-attention-model.ts +62 -0
- package/src/session-auth-recommendation.ts +48 -0
- package/src/session-capability-policy.ts +196 -0
- package/src/session-context.ts +3 -4
- package/src/session-feedback-model.ts +95 -0
- package/src/session-list-model.ts +434 -0
- package/src/session-realtime.ts +18 -0
- package/src/session-status-model.ts +99 -0
- package/src/session-ui.ts +98 -1
- package/src/session.ts +17 -1
- package/src/site-snapshot.ts +74 -0
- package/src/sites-ui.tsx +348 -0
- package/src/sites.ts +3 -0
- package/src/skill-discovery.tsx +208 -0
- package/src/terminal.ts +26 -0
- package/src/testing/session-scenarios.ts +404 -0
- package/src/timeline/activity-rail.tsx +242 -101
- package/src/timeline/agent-identity.tsx +84 -0
- package/src/timeline/agent-row.tsx +231 -0
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/compaction-copy.ts +115 -0
- package/src/timeline/entrance.tsx +28 -3
- package/src/timeline/fleet-decision-projection.ts +2 -1
- package/src/timeline/fleet-decision-row.tsx +1 -0
- package/src/timeline/focus-sequence.ts +44 -0
- package/src/timeline/genie-copy.ts +19 -0
- package/src/timeline/genie-loading.tsx +143 -0
- package/src/timeline/index.ts +30 -2
- package/src/timeline/knowledge-receipt.tsx +100 -0
- package/src/timeline/message-sender.tsx +71 -0
- package/src/timeline/notice-presentation.ts +48 -0
- package/src/timeline/parsers.ts +42 -2
- package/src/timeline/patch-apply-command.tsx +135 -0
- package/src/timeline/platform-activity-presentation.ts +166 -0
- package/src/timeline/platform-activity-row.tsx +15 -90
- package/src/timeline/presented-image.ts +63 -0
- package/src/timeline/projection.ts +1746 -76
- package/src/timeline/provider-logo.tsx +53 -0
- package/src/timeline/question-nav-model.ts +28 -0
- package/src/timeline/registry.ts +26 -4
- package/src/timeline/retained-image.ts +97 -0
- package/src/timeline/retained-loaders.ts +91 -0
- package/src/timeline/rolling-activity.tsx +80 -0
- package/src/timeline/screenshot-lightbox.tsx +228 -5
- package/src/timeline/session-usage-limit-row.tsx +42 -0
- package/src/timeline/shared.tsx +42 -15
- package/src/timeline/startup-dispatch-details.tsx +36 -0
- package/src/timeline/startup-preference.ts +33 -0
- package/src/timeline/startup-timings.tsx +145 -0
- package/src/timeline/tool-display-name.ts +19 -7
- package/src/timeline/tool-presentation.ts +1316 -0
- package/src/timeline/tool-renderers.tsx +835 -1138
- package/src/timeline/turn-summary-model.ts +343 -0
- package/src/timeline/turn-summary.tsx +272 -365
- package/src/timeline/types.ts +189 -6
- package/src/timeline/work-presentation.ts +189 -0
- package/src/timeline-model.ts +143 -0
- package/src/usage/allowance-copy.ts +64 -0
- package/src/usage/session-usage-limit.ts +266 -0
- package/src/usage/summary.ts +93 -0
- package/src/usage.ts +70 -0
- package/src/voice-recording-owner.ts +22 -0
- package/styles/compiled.css +2319 -748
- package/styles/connect.css +290 -0
- package/styles/connect.d.ts +1 -0
- package/styles/effective-tokens.css +1 -0
- package/styles/index.css +309 -9
- package/styles/responsive.css +99 -7
- package/styles/session-capability.css +44 -0
- package/styles/tokens.css +134 -66
- package/dist/browser-viewer-AEFHUYZK.js +0 -14
- package/dist/chunk-4MDLEHXL.js.map +0 -1
- package/dist/chunk-5YK2ZDIG.js.map +0 -1
- package/dist/chunk-D3UIAJKY.js.map +0 -1
- package/dist/chunk-ELIYWBZR.js.map +0 -1
- package/dist/chunk-H5YGYNXS.js.map +0 -1
- package/dist/chunk-HQVCKCLG.js +0 -9458
- package/dist/chunk-HQVCKCLG.js.map +0 -1
- package/dist/chunk-HUIKIDM5.js.map +0 -1
- package/dist/chunk-ICA6R6TW.js.map +0 -1
- package/dist/chunk-JVYQRRC4.js.map +0 -1
- package/dist/chunk-KCGXPP5V.js.map +0 -1
- package/dist/chunk-KZ73RL76.js.map +0 -1
- package/dist/chunk-LAKLF4PA.js +0 -141
- package/dist/chunk-LAKLF4PA.js.map +0 -1
- package/dist/chunk-MZ2G2XBA.js.map +0 -1
- package/dist/chunk-NEXLLTP4.js.map +0 -1
- package/dist/chunk-OAM4WAAM.js.map +0 -1
- package/dist/chunk-PEI6RANI.js.map +0 -1
- package/dist/chunk-RGALCTWO.js.map +0 -1
- package/dist/chunk-W6NFJXSA.js +0 -454
- package/dist/chunk-W6NFJXSA.js.map +0 -1
- package/dist/chunk-WSK7G5LE.js +0 -170
- package/dist/chunk-WSK7G5LE.js.map +0 -1
- package/dist/chunk-X4A66EWZ.js +0 -223
- package/dist/chunk-X4A66EWZ.js.map +0 -1
- package/dist/chunk-XXWH2JLC.js.map +0 -1
- package/dist/chunk-YFKJ7VZH.js +0 -268
- package/dist/chunk-YFKJ7VZH.js.map +0 -1
- package/dist/chunk-YJFGAFBD.js.map +0 -1
- package/dist/computer-viewer-FWEZISW4.js +0 -16
- package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
- package/dist/hooks/use-environments.d.ts +0 -41
- package/dist/hooks/use-packs.d.ts +0 -32
- package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
- package/dist/queue-surface-implementation-BSKUWHRI.js.map +0 -1
- package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
- package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
- package/src/hooks/use-environments.ts +0 -182
- package/src/hooks/use-packs.ts +0 -157
- /package/dist/{browser-viewer-AEFHUYZK.js.map → browser-viewer-ZWR6MWJ7.js.map} +0 -0
- /package/dist/{computer-viewer-FWEZISW4.js.map → chat-interactive-block-BO2IWXVK.js.map} +0 -0
|
@@ -1,20 +1,36 @@
|
|
|
1
1
|
import {
|
|
2
|
+
parseCustomMcpSetupRequest,
|
|
2
3
|
parseMediaGenerationResult,
|
|
4
|
+
parseToolDisplayMetadata,
|
|
5
|
+
EMPTY_FINAL_REPLY_NOTICE,
|
|
6
|
+
turnCompletedWithEmptyFinalReply,
|
|
3
7
|
type HumanInputAnswer,
|
|
4
8
|
type HumanInputQuestion,
|
|
5
9
|
type HumanInputResponse,
|
|
10
|
+
type HumanInputSecretAnswerResult,
|
|
11
|
+
type HumanInputSecretSpec,
|
|
6
12
|
type SessionEvent,
|
|
7
13
|
type SessionStatus,
|
|
8
14
|
type TimelineAnnotation,
|
|
9
15
|
} from "@opengeni/sdk";
|
|
10
16
|
import fleetDecisionItem from "./fleet-decision-projection";
|
|
17
|
+
import {
|
|
18
|
+
allowanceExhaustedMessage,
|
|
19
|
+
parseAllowanceExhaustedRefusal,
|
|
20
|
+
} from "@opengeni/sdk/allowance-refusal";
|
|
21
|
+
import {
|
|
22
|
+
parseSessionUsageLimitReachedRefusal,
|
|
23
|
+
SESSION_USAGE_LIMIT_SEGMENT,
|
|
24
|
+
sessionUsageLimitReachedMessage,
|
|
25
|
+
} from "@opengeni/sdk/session-usage-limits";
|
|
11
26
|
import {
|
|
12
27
|
CREDIT_EXHAUSTION_MESSAGE,
|
|
13
|
-
|
|
28
|
+
presentFailure,
|
|
14
29
|
isCreditExhaustion,
|
|
15
30
|
tryParseJson,
|
|
16
31
|
} from "../lib/format";
|
|
17
32
|
import { mcpToolLeaf, toolMatchesLeaf } from "./tool-display-name";
|
|
33
|
+
import { presentTurnFailureText } from "./compaction-copy";
|
|
18
34
|
import type {
|
|
19
35
|
AgentMessageItem,
|
|
20
36
|
ActivityItem,
|
|
@@ -24,7 +40,9 @@ import type {
|
|
|
24
40
|
HumanInputAnswerSummary,
|
|
25
41
|
HumanInputItem,
|
|
26
42
|
MachineInputBatchItem,
|
|
43
|
+
MachineInputMember,
|
|
27
44
|
MemoryItem,
|
|
45
|
+
NoticeItem,
|
|
28
46
|
SandboxItem,
|
|
29
47
|
StartupPhase,
|
|
30
48
|
StartupPhaseItem,
|
|
@@ -39,6 +57,14 @@ import type {
|
|
|
39
57
|
|
|
40
58
|
export { toolDisplayName, mcpToolLeaf, toolMatchesLeaf } from "./tool-display-name";
|
|
41
59
|
|
|
60
|
+
/** Message identity only: queue placement needs the surrounding turn history. */
|
|
61
|
+
export function isTimelineUserQuestion(event: SessionEvent): boolean {
|
|
62
|
+
return (
|
|
63
|
+
event.type === "user.message" &&
|
|
64
|
+
workerCompletionPayload(asRecord(event.payload).childCompletion) === null
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
42
68
|
/* ----------------------------------------------------------------------------
|
|
43
69
|
Timeline projection
|
|
44
70
|
|
|
@@ -53,11 +79,13 @@ export { toolDisplayName, mcpToolLeaf, toolMatchesLeaf } from "./tool-display-na
|
|
|
53
79
|
memoized, unit-tested, and re-run incrementally as new events stream in.
|
|
54
80
|
-------------------------------------------------------------------------- */
|
|
55
81
|
|
|
56
|
-
/** Tool leaves on the first-party
|
|
82
|
+
/** Tool leaves on the first-party Opengeni MCP server that operate on sessions. */
|
|
57
83
|
const WORKER_SPAWN_TOOL = "session_create";
|
|
58
84
|
const WORKER_MESSAGE_TOOL = "session_send_message";
|
|
59
85
|
const WORKER_FAILURE_CODE_MAX_LENGTH = 128;
|
|
60
86
|
const WORKER_FAILURE_MESSAGE_MAX_UTF8_BYTES = 1_024;
|
|
87
|
+
type PendingWaitOutcome = { id: string; reason: string; occurredAt: string };
|
|
88
|
+
type TrackedAgentResponse = { item: AgentMessageItem; completed: boolean };
|
|
61
89
|
|
|
62
90
|
/**
|
|
63
91
|
* Tools whose durable side-effect events already own the timeline (MemoryRow).
|
|
@@ -127,16 +155,110 @@ function timelineAnnotationToolOutputText(output: unknown): string | null {
|
|
|
127
155
|
return text.length > 0 ? text : null;
|
|
128
156
|
}
|
|
129
157
|
|
|
130
|
-
export function buildTimeline(
|
|
158
|
+
export function buildTimeline(
|
|
159
|
+
events: SessionEvent[],
|
|
160
|
+
options: { partialStart?: boolean } = {},
|
|
161
|
+
): TimelineItem[] {
|
|
131
162
|
const items: TimelineItem[] = [];
|
|
163
|
+
const allowanceNotices = new Set<string>();
|
|
164
|
+
const allowanceNotice = (event: SessionEvent, turnId: string | null) => {
|
|
165
|
+
const refusal = parseAllowanceExhaustedRefusal(event.payload);
|
|
166
|
+
if (!refusal) return null;
|
|
167
|
+
const text = allowanceExhaustedMessage(refusal);
|
|
168
|
+
const key = JSON.stringify([
|
|
169
|
+
turnId,
|
|
170
|
+
event.turnAttemptId,
|
|
171
|
+
startupRecoveryRevisionByTurn.get(turnId ?? "") ?? 0,
|
|
172
|
+
refusal.scope,
|
|
173
|
+
refusal.subjectId,
|
|
174
|
+
refusal.resetsAt,
|
|
175
|
+
]);
|
|
176
|
+
if (!allowanceNotices.has(key)) {
|
|
177
|
+
allowanceNotices.add(key);
|
|
178
|
+
items.push({
|
|
179
|
+
kind: "notice",
|
|
180
|
+
id: `${event.id}-allowance`,
|
|
181
|
+
tone: "failed",
|
|
182
|
+
text,
|
|
183
|
+
// The canonical sentence replaces upstream prose, so a host renderer
|
|
184
|
+
// reading `message` never shows a wrapper's private wording.
|
|
185
|
+
allowance: { ...refusal, message: text },
|
|
186
|
+
occurredAt: event.occurredAt,
|
|
187
|
+
});
|
|
188
|
+
}
|
|
189
|
+
return text;
|
|
190
|
+
};
|
|
132
191
|
const prescan = prescanTurnAnchors(events);
|
|
133
192
|
const ordered = orderTimelineEvents(events, prescan);
|
|
193
|
+
// A bounded replay can begin halfway through a message (including inside an
|
|
194
|
+
// interactive fence). Never parse that suffix as a complete Markdown source.
|
|
195
|
+
// Keep the raw events available for pagination; a completed receipt restores
|
|
196
|
+
// the authoritative message without requiring an unbounded history fetch.
|
|
197
|
+
let completeHistoryPrefix = options.partialStart !== true;
|
|
198
|
+
const knownMessagePrefixTurns = new Set<string | null>();
|
|
199
|
+
const incompleteMessageKeys = new Set<string>();
|
|
200
|
+
const pendingWaitOutcomeByTurn = new Map<string | null, PendingWaitOutcome>();
|
|
201
|
+
const latestAgentResponseByTurn = new Map<string | null, TrackedAgentResponse>();
|
|
202
|
+
const identifiedMessages = new Map<string, AgentMessageItem>();
|
|
134
203
|
const humanInputRequests = humanInputRequestsById(events);
|
|
135
204
|
const humanInputToolCallIds = new Set(
|
|
136
205
|
[...humanInputRequests.values()]
|
|
137
206
|
.map((request) => stringValue(request.toolCallId))
|
|
138
207
|
.filter(Boolean),
|
|
139
208
|
);
|
|
209
|
+
// Delegated workers that have not reported back yet, so a recorded agent wait
|
|
210
|
+
// can say how many agents it is waiting for. Only a session's own workers
|
|
211
|
+
// report back, so a message to any other session is never awaited. Best
|
|
212
|
+
// effort: a bounded replay only knows the spawns and results inside its window.
|
|
213
|
+
const workerSessions = new Set<string>();
|
|
214
|
+
const pendingWorkerSessions = new Set<string>();
|
|
215
|
+
let openWaitNotice: NoticeItem | null = null;
|
|
216
|
+
// Tool results update the original tool row in place, and running lifecycle
|
|
217
|
+
// receipts have no row. Carry resume evidence on the attention landmarks so
|
|
218
|
+
// grouping cannot mistake historical approval for a live wait.
|
|
219
|
+
const presentationWaits = new Map<string | null, Array<NoticeItem | SessionStatusItem>>();
|
|
220
|
+
// Admission blocks hold the whole session, not one turn; any later status
|
|
221
|
+
// (a recheck, a new message, a pause) ends them.
|
|
222
|
+
let openAdmissionBlocks: SessionStatusItem[] = [];
|
|
223
|
+
const presentationFailures = new Map<string | null, SessionStatusItem[]>();
|
|
224
|
+
let presentationTurnId: string | null = null;
|
|
225
|
+
const rememberPresentationWait = (
|
|
226
|
+
item: NoticeItem | SessionStatusItem,
|
|
227
|
+
turnId: string | null,
|
|
228
|
+
) => {
|
|
229
|
+
const key = turnId ?? presentationTurnId;
|
|
230
|
+
const waits = presentationWaits.get(key) ?? [];
|
|
231
|
+
waits.push(item);
|
|
232
|
+
presentationWaits.set(key, waits);
|
|
233
|
+
items.push(item);
|
|
234
|
+
};
|
|
235
|
+
const resolvePresentationWait = (event: SessionEvent, turnId: string | null) => {
|
|
236
|
+
if (event.duplicateOfEventId || (event.turnAssociation && event.turnAssociation !== "current"))
|
|
237
|
+
return;
|
|
238
|
+
const key = turnId ?? presentationTurnId;
|
|
239
|
+
for (const item of presentationWaits.get(key) ?? []) item.resolvedAt = event.occurredAt;
|
|
240
|
+
presentationWaits.delete(key);
|
|
241
|
+
};
|
|
242
|
+
// Live model-capacity waits. Later lifecycle evidence that the turn is no
|
|
243
|
+
// longer blocked (resume, a non-waiting status, its settlement) resolves
|
|
244
|
+
// them, so a recovered wait never lingers as a warning.
|
|
245
|
+
let openCapacityWaits: NoticeItem[] = [];
|
|
246
|
+
const resolveCapacityWaits = (event: SessionEvent, payload: Record<string, unknown>) => {
|
|
247
|
+
if (openCapacityWaits.length === 0 || !endsCapacityWait(event, payload)) return;
|
|
248
|
+
const eventTurnId = event.turnId ?? null;
|
|
249
|
+
openCapacityWaits = openCapacityWaits.filter((item) => {
|
|
250
|
+
const waitTurnId = item.capacityWait?.turnId ?? null;
|
|
251
|
+
// Status changes are session-wide; a turn settlement ends only its own wait
|
|
252
|
+
// (a queued follow-up being cancelled does not unblock the waiting turn).
|
|
253
|
+
const ends =
|
|
254
|
+
event.type === "session.status.changed" ||
|
|
255
|
+
eventTurnId === null ||
|
|
256
|
+
waitTurnId === null ||
|
|
257
|
+
eventTurnId === waitTurnId;
|
|
258
|
+
if (ends) item.resolvedAt = event.occurredAt;
|
|
259
|
+
return !ends;
|
|
260
|
+
});
|
|
261
|
+
};
|
|
140
262
|
const queuedAtByTurn = new Map<string, string>();
|
|
141
263
|
const startupRecoveryRevisionByTurn = new Map<string, number>();
|
|
142
264
|
const startupPhases = new Map<string, readonly [StartupPhaseItem, string | null, number]>();
|
|
@@ -153,6 +275,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
153
275
|
mode = 0,
|
|
154
276
|
explicitStartedAt?: string,
|
|
155
277
|
explicitId?: string,
|
|
278
|
+
blockedReason?: StartupPhaseItem["blockedReason"],
|
|
156
279
|
): void => {
|
|
157
280
|
const key = `${startupTurnId}:${phase}`;
|
|
158
281
|
const priorState = startupPhases.get(key);
|
|
@@ -179,6 +302,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
179
302
|
? elapsedDurationMs(open.startedAt, startupEvent.occurredAt)
|
|
180
303
|
: Math.max(0, duration);
|
|
181
304
|
open.outcome = outcome ?? open.outcome;
|
|
305
|
+
open.blockedReason = blockedReason;
|
|
182
306
|
return;
|
|
183
307
|
}
|
|
184
308
|
const running = status === "running";
|
|
@@ -197,6 +321,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
197
321
|
completedAt: running ? null : startupEvent.occurredAt,
|
|
198
322
|
durationMs,
|
|
199
323
|
outcome,
|
|
324
|
+
blockedReason,
|
|
200
325
|
occurredAt: startedAt,
|
|
201
326
|
} satisfies Partial<StartupPhaseItem>);
|
|
202
327
|
startupPhases.set(key, [prior, startupAttemptId, startupRecoveryRevision]);
|
|
@@ -212,6 +337,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
212
337
|
completedAt: running ? null : startupEvent.occurredAt,
|
|
213
338
|
durationMs,
|
|
214
339
|
outcome,
|
|
340
|
+
blockedReason,
|
|
215
341
|
occurredAt: startedAt,
|
|
216
342
|
};
|
|
217
343
|
items.push(item);
|
|
@@ -220,6 +346,31 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
220
346
|
|
|
221
347
|
const last = (): TimelineItem | undefined => items[items.length - 1];
|
|
222
348
|
|
|
349
|
+
const rememberAgentResponse = (
|
|
350
|
+
turnId: string | null,
|
|
351
|
+
item: AgentMessageItem,
|
|
352
|
+
completed: boolean,
|
|
353
|
+
): void => {
|
|
354
|
+
latestAgentResponseByTurn.set(turnId, { item, completed });
|
|
355
|
+
};
|
|
356
|
+
|
|
357
|
+
const takePendingWaitOutcome = (turnId: string | null): PendingWaitOutcome | undefined => {
|
|
358
|
+
const outcome = pendingWaitOutcomeByTurn.get(turnId);
|
|
359
|
+
pendingWaitOutcomeByTurn.delete(turnId);
|
|
360
|
+
return outcome;
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
const takeAgentResponse = (turnId: string | null): TrackedAgentResponse | undefined => {
|
|
364
|
+
const response = latestAgentResponseByTurn.get(turnId);
|
|
365
|
+
latestAgentResponseByTurn.delete(turnId);
|
|
366
|
+
return response;
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
const clearUnscopedTerminalTracking = (): void => {
|
|
370
|
+
latestAgentResponseByTurn.delete(null);
|
|
371
|
+
pendingWaitOutcomeByTurn.delete(null);
|
|
372
|
+
};
|
|
373
|
+
|
|
223
374
|
/** A new item of a different kind ends whatever was streaming at the tail. */
|
|
224
375
|
const closeStreamingTail = (): void => {
|
|
225
376
|
const open = last();
|
|
@@ -265,6 +416,40 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
265
416
|
for (const event of ordered) {
|
|
266
417
|
const payload = asRecord(event.payload);
|
|
267
418
|
const turnId = event.turnId ?? null;
|
|
419
|
+
resolveCapacityWaits(event, payload);
|
|
420
|
+
if (
|
|
421
|
+
turnId &&
|
|
422
|
+
(event.type === "turn.started" ||
|
|
423
|
+
(presentationTurnId === null && isAgentActivityEvent(event.type)))
|
|
424
|
+
) {
|
|
425
|
+
presentationTurnId = turnId;
|
|
426
|
+
}
|
|
427
|
+
if (
|
|
428
|
+
openWaitNotice &&
|
|
429
|
+
(event.type === "user.message" ||
|
|
430
|
+
event.type === "system.update.delivered" ||
|
|
431
|
+
event.type === "turn.started" ||
|
|
432
|
+
event.type === "session.control.paused" ||
|
|
433
|
+
(event.type === "session.status.changed" &&
|
|
434
|
+
(payload.status === "cancelled" || payload.status === "failed")))
|
|
435
|
+
) {
|
|
436
|
+
// New input, a pause, or the end of the session ends a recorded wait; the
|
|
437
|
+
// notice keeps its wait span.
|
|
438
|
+
openWaitNotice.waitEndedAt = event.occurredAt;
|
|
439
|
+
openWaitNotice = null;
|
|
440
|
+
}
|
|
441
|
+
if (event.type === "session.created") {
|
|
442
|
+
completeHistoryPrefix = true;
|
|
443
|
+
} else if (
|
|
444
|
+
event.type === "turn.started" ||
|
|
445
|
+
(event.type === "user.message" &&
|
|
446
|
+
payload.delivery !== "steer" &&
|
|
447
|
+
payload.routing !== "accepted_for_steering")
|
|
448
|
+
) {
|
|
449
|
+
// Accepted Steer is visible before its replacement turn starts. It says
|
|
450
|
+
// nothing about the prefix of a still-streaming message from the old turn.
|
|
451
|
+
knownMessagePrefixTurns.add(turnId);
|
|
452
|
+
}
|
|
268
453
|
startupEvent = event;
|
|
269
454
|
startupTurnId = turnId;
|
|
270
455
|
startupAttemptId =
|
|
@@ -285,11 +470,39 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
285
470
|
: null;
|
|
286
471
|
if (setupPhase && setupStatus) {
|
|
287
472
|
closeStreamingTail();
|
|
473
|
+
let blockedReason: StartupPhaseItem["blockedReason"];
|
|
474
|
+
if (setupPhase === "model_preparation" && setupStatus === "failed") {
|
|
475
|
+
blockedReason = startupLifecycleWaitReason(payload);
|
|
476
|
+
// Older preparation events omit the typed lifecycle fields. The
|
|
477
|
+
// sandbox span can supply them only inside the same turn attempt.
|
|
478
|
+
const sandbox = startupPhases.get(`${startupTurnId}:sandbox`);
|
|
479
|
+
const sameAttempt =
|
|
480
|
+
sandbox?.[1] || startupAttemptId
|
|
481
|
+
? Boolean(startupAttemptId && sandbox?.[1] === startupAttemptId)
|
|
482
|
+
: sandbox?.[2] === startupRecoveryRevision;
|
|
483
|
+
if (
|
|
484
|
+
!blockedReason &&
|
|
485
|
+
payload.expectedTransition === undefined &&
|
|
486
|
+
payload.failureCode === undefined &&
|
|
487
|
+
payload.failureStage === undefined &&
|
|
488
|
+
payload.failureCategory === undefined &&
|
|
489
|
+
payload.error === undefined &&
|
|
490
|
+
startupTurnId &&
|
|
491
|
+
sameAttempt &&
|
|
492
|
+
sandbox?.[0].status === "cancelled"
|
|
493
|
+
) {
|
|
494
|
+
blockedReason = sandbox[0].blockedReason;
|
|
495
|
+
}
|
|
496
|
+
}
|
|
288
497
|
settleStartupPhase(
|
|
289
498
|
setupPhase,
|
|
290
|
-
setupStatus,
|
|
499
|
+
blockedReason ? "cancelled" : setupStatus,
|
|
291
500
|
numberOrNull(payload.durationMs),
|
|
292
501
|
event.type === "rig.setup.skipped" ? "skipped" : null,
|
|
502
|
+
0,
|
|
503
|
+
undefined,
|
|
504
|
+
undefined,
|
|
505
|
+
blockedReason,
|
|
293
506
|
);
|
|
294
507
|
continue;
|
|
295
508
|
}
|
|
@@ -298,9 +511,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
298
511
|
case "user.message": {
|
|
299
512
|
// A steering message must not mark in-flight tools complete; it only
|
|
300
513
|
// ends whatever text was streaming. Turn lifecycle events finalize.
|
|
514
|
+
clearUnscopedTerminalTracking();
|
|
301
515
|
closeStreamingTail();
|
|
302
516
|
const childCompletion = workerCompletionPayload(payload.childCompletion);
|
|
303
517
|
if (childCompletion) {
|
|
518
|
+
workerSessions.add(childCompletion.childSessionId);
|
|
519
|
+
pendingWorkerSessions.delete(childCompletion.childSessionId);
|
|
304
520
|
items.push({
|
|
305
521
|
kind: "worker-completion",
|
|
306
522
|
id: event.id,
|
|
@@ -317,6 +533,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
317
533
|
break;
|
|
318
534
|
}
|
|
319
535
|
const voiceMessage = realtimeVoiceMessage(payload);
|
|
536
|
+
const sender = messageSender(payload.initiator);
|
|
320
537
|
items.push({
|
|
321
538
|
kind: "user-message",
|
|
322
539
|
id: event.id,
|
|
@@ -336,6 +553,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
336
553
|
...(voiceMessage ? { presentation: voiceMessage.presentation } : {}),
|
|
337
554
|
resources: resourceRefs(payload.resources),
|
|
338
555
|
tools: toolRefs(payload.tools),
|
|
556
|
+
...(sender ? { sender } : {}),
|
|
339
557
|
occurredAt: event.occurredAt,
|
|
340
558
|
});
|
|
341
559
|
break;
|
|
@@ -353,13 +571,50 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
353
571
|
break;
|
|
354
572
|
}
|
|
355
573
|
|
|
574
|
+
case "system.update.settled": {
|
|
575
|
+
// An operator retirement is a passive historical receipt, not a new
|
|
576
|
+
// prompt or model-input delivery. Reuse the existing agent-result row
|
|
577
|
+
// while ordinary turn-input settlement metadata remains quiet.
|
|
578
|
+
if (payload.notificationOnly !== true || payload.reason !== "operator_retirement") break;
|
|
579
|
+
const members = machineInputMembers(payload.members).filter(
|
|
580
|
+
(member) =>
|
|
581
|
+
member.kind === "child_terminal_result" && member.classification === "failure",
|
|
582
|
+
);
|
|
583
|
+
if (members.length === 0) break;
|
|
584
|
+
for (const member of members) {
|
|
585
|
+
workerSessions.add(member.sourceId);
|
|
586
|
+
pendingWorkerSessions.delete(member.sourceId);
|
|
587
|
+
}
|
|
588
|
+
items.push({
|
|
589
|
+
kind: "machine-input-batch",
|
|
590
|
+
id: event.id,
|
|
591
|
+
turnId: null,
|
|
592
|
+
members,
|
|
593
|
+
occurredAt: event.occurredAt,
|
|
594
|
+
});
|
|
595
|
+
break;
|
|
596
|
+
}
|
|
597
|
+
|
|
356
598
|
case "system.update.delivered": {
|
|
357
599
|
const inputs = machineInputMembers(payload.members);
|
|
600
|
+
for (const input of inputs) {
|
|
601
|
+
if (input.kind === "child_terminal_result") {
|
|
602
|
+
workerSessions.add(input.sourceId);
|
|
603
|
+
pendingWorkerSessions.delete(input.sourceId);
|
|
604
|
+
}
|
|
605
|
+
}
|
|
358
606
|
if (inputs.length === 0) break;
|
|
359
|
-
//
|
|
360
|
-
//
|
|
361
|
-
|
|
362
|
-
|
|
607
|
+
// Background command receipts are agent-facing inputs, not chat messages.
|
|
608
|
+
// The durable event remains available to the agent and audit history.
|
|
609
|
+
const visibleInputs = inputs.filter(
|
|
610
|
+
(member) => member.kind !== "background_command_result",
|
|
611
|
+
);
|
|
612
|
+
// Goal continuations already have GoalRows. A batch with no other
|
|
613
|
+
// visible update must not interrupt an in-flight streaming message.
|
|
614
|
+
if (
|
|
615
|
+
visibleInputs.length === 0 ||
|
|
616
|
+
visibleInputs.every((member) => member.kind === "goal_continuation")
|
|
617
|
+
) {
|
|
363
618
|
break;
|
|
364
619
|
}
|
|
365
620
|
closeStreamingTail();
|
|
@@ -367,7 +622,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
367
622
|
kind: "machine-input-batch",
|
|
368
623
|
id: event.id,
|
|
369
624
|
turnId,
|
|
370
|
-
members:
|
|
625
|
+
members: visibleInputs,
|
|
371
626
|
occurredAt: event.occurredAt,
|
|
372
627
|
});
|
|
373
628
|
break;
|
|
@@ -378,31 +633,85 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
378
633
|
if (!text) {
|
|
379
634
|
break;
|
|
380
635
|
}
|
|
636
|
+
const messageId = stringValue(payload.messageId);
|
|
637
|
+
const messageKey = messageId ? JSON.stringify([turnId, messageId]) : null;
|
|
638
|
+
// Responses providers declare the phase when they announce the item, so
|
|
639
|
+
// a streaming progress note is known as commentary before it completes.
|
|
640
|
+
const phase = assistantMessagePhase(payload.phase);
|
|
641
|
+
if (
|
|
642
|
+
(!completeHistoryPrefix && !knownMessagePrefixTurns.has(turnId)) ||
|
|
643
|
+
(messageKey && incompleteMessageKeys.has(messageKey))
|
|
644
|
+
) {
|
|
645
|
+
if (messageKey) incompleteMessageKeys.add(messageKey);
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
const identified = messageKey ? identifiedMessages.get(messageKey) : undefined;
|
|
649
|
+
if (identified) {
|
|
650
|
+
if (identified.annotationSource?.eventType !== "agent.message.completed") {
|
|
651
|
+
identified.text += text;
|
|
652
|
+
if (phase && !identified.phase) identified.phase = phase;
|
|
653
|
+
rememberAgentResponse(turnId, identified, false);
|
|
654
|
+
}
|
|
655
|
+
break;
|
|
656
|
+
}
|
|
381
657
|
const open = last();
|
|
382
|
-
if (open?.kind === "
|
|
658
|
+
if (!messageKey && open?.kind === "tool-call" && open.status === "running") {
|
|
659
|
+
const previous = latestAgentResponseByTurn.get(turnId);
|
|
660
|
+
const previousIndex = previous ? items.indexOf(previous.item) : -1;
|
|
661
|
+
if (
|
|
662
|
+
previous &&
|
|
663
|
+
!previous.completed &&
|
|
664
|
+
previousIndex >= 0 &&
|
|
665
|
+
items
|
|
666
|
+
.slice(previousIndex + 1)
|
|
667
|
+
.every(
|
|
668
|
+
(item) =>
|
|
669
|
+
item.kind === "tool-call" && item.turnId === turnId && item.status === "running",
|
|
670
|
+
)
|
|
671
|
+
) {
|
|
672
|
+
// Legacy deltas have no provider message identity. Tool creation
|
|
673
|
+
// alone is not a text boundary; completed output still is.
|
|
674
|
+
previous.item.text += text;
|
|
675
|
+
break;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
if (
|
|
679
|
+
!messageKey &&
|
|
680
|
+
open?.kind === "agent-message" &&
|
|
681
|
+
open.streaming &&
|
|
682
|
+
open.turnId === turnId
|
|
683
|
+
) {
|
|
383
684
|
open.text += text;
|
|
685
|
+
rememberAgentResponse(turnId, open, false);
|
|
384
686
|
break;
|
|
385
687
|
}
|
|
386
688
|
closeStreamingTail();
|
|
387
|
-
|
|
689
|
+
const item: AgentMessageItem = {
|
|
388
690
|
kind: "agent-message",
|
|
389
691
|
id: event.id,
|
|
390
692
|
turnId,
|
|
391
693
|
text,
|
|
694
|
+
...(phase ? { phase } : {}),
|
|
392
695
|
streaming: true,
|
|
393
696
|
occurredAt: event.occurredAt,
|
|
394
|
-
}
|
|
697
|
+
};
|
|
698
|
+
items.push(item);
|
|
699
|
+
if (messageKey) identifiedMessages.set(messageKey, item);
|
|
700
|
+
rememberAgentResponse(turnId, item, false);
|
|
395
701
|
break;
|
|
396
702
|
}
|
|
397
703
|
|
|
398
704
|
case "agent.message.completed": {
|
|
705
|
+
knownMessagePrefixTurns.add(turnId);
|
|
399
706
|
const text = stringValue(payload.text);
|
|
707
|
+
const messageId = stringValue(payload.messageId);
|
|
708
|
+
const messageKey = messageId ? JSON.stringify([turnId, messageId]) : null;
|
|
400
709
|
const phase = assistantMessagePhase(payload.phase);
|
|
401
710
|
// Reconcile the most recent same-turn agent message — even when
|
|
402
711
|
// activity (tool calls, reasoning) landed after its deltas — so the
|
|
403
712
|
// completed text never duplicates the streamed one.
|
|
404
|
-
let openIndex = -1;
|
|
405
|
-
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
713
|
+
let openIndex = messageKey ? items.indexOf(identifiedMessages.get(messageKey)!) : -1;
|
|
714
|
+
for (let index = messageKey ? -1 : items.length - 1; index >= 0; index -= 1) {
|
|
406
715
|
const candidate = items[index];
|
|
407
716
|
if (candidate?.kind === "agent-message" && candidate.turnId === turnId) {
|
|
408
717
|
openIndex = index;
|
|
@@ -414,18 +723,26 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
414
723
|
candidate?.kind === "agent-message" ? candidate : undefined;
|
|
415
724
|
if (
|
|
416
725
|
open &&
|
|
417
|
-
(
|
|
726
|
+
(messageKey ||
|
|
727
|
+
open.streaming ||
|
|
728
|
+
!open.text ||
|
|
729
|
+
text === open.text ||
|
|
730
|
+
text.startsWith(open.text))
|
|
418
731
|
) {
|
|
419
|
-
//
|
|
420
|
-
|
|
732
|
+
// Identity makes the final text authoritative even if it corrects the
|
|
733
|
+
// draft; legacy receipts still require an extension match.
|
|
734
|
+
if (!open.text || (text && (messageKey || text.startsWith(open.text)))) {
|
|
421
735
|
open.text = text || open.text;
|
|
422
736
|
}
|
|
423
|
-
open.streaming = false;
|
|
424
737
|
// A phase-less settlement mirror (the worker's final-output receipt)
|
|
425
738
|
// must not erase the provider-declared phase captured by the SDK item.
|
|
426
739
|
if (phase) open.phase = phase;
|
|
427
|
-
// Completion time is what the footer shows ("finished at");
|
|
428
|
-
// first-delta stamp
|
|
740
|
+
// Completion time is what the footer shows ("finished at"); the
|
|
741
|
+
// first-delta stamp stays as the stream's start.
|
|
742
|
+
if (open.startedAt === undefined && open.occurredAt !== event.occurredAt) {
|
|
743
|
+
open.startedAt = open.occurredAt;
|
|
744
|
+
}
|
|
745
|
+
open.streaming = false;
|
|
429
746
|
open.occurredAt = event.occurredAt;
|
|
430
747
|
open.annotationSource =
|
|
431
748
|
text && open.text === text
|
|
@@ -447,10 +764,11 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
447
764
|
items.splice(openIndex, 1);
|
|
448
765
|
items.push(open);
|
|
449
766
|
}
|
|
767
|
+
rememberAgentResponse(turnId, open, true);
|
|
450
768
|
break;
|
|
451
769
|
}
|
|
452
770
|
if (text) {
|
|
453
|
-
|
|
771
|
+
const item: AgentMessageItem = {
|
|
454
772
|
kind: "agent-message",
|
|
455
773
|
id: event.id,
|
|
456
774
|
turnId,
|
|
@@ -466,7 +784,10 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
466
784
|
turnId,
|
|
467
785
|
text,
|
|
468
786
|
},
|
|
469
|
-
}
|
|
787
|
+
};
|
|
788
|
+
items.push(item);
|
|
789
|
+
if (messageKey) identifiedMessages.set(messageKey, item);
|
|
790
|
+
rememberAgentResponse(turnId, item, true);
|
|
470
791
|
}
|
|
471
792
|
break;
|
|
472
793
|
}
|
|
@@ -478,7 +799,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
478
799
|
}
|
|
479
800
|
const open = last();
|
|
480
801
|
if (open?.kind === "reasoning" && open.streaming && open.turnId === turnId) {
|
|
481
|
-
open.text += text;
|
|
802
|
+
open.text += reasoningDeltaJoiner(open.text, text) + text;
|
|
482
803
|
break;
|
|
483
804
|
}
|
|
484
805
|
closeStreamingTail();
|
|
@@ -495,6 +816,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
495
816
|
|
|
496
817
|
case "agent.toolCall.created": {
|
|
497
818
|
const name = typeof payload.name === "string" ? payload.name : "tool";
|
|
819
|
+
const display = parseToolDisplayMetadata(payload.display);
|
|
498
820
|
const callId = typeof payload.id === "string" ? payload.id : null;
|
|
499
821
|
const args = payload.arguments ?? null;
|
|
500
822
|
closeStreamingTail();
|
|
@@ -508,13 +830,16 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
508
830
|
toolMatchesLeaf(name, WORKER_SPAWN_TOOL) ||
|
|
509
831
|
toolMatchesLeaf(name, WORKER_MESSAGE_TOOL)
|
|
510
832
|
) {
|
|
833
|
+
const spawn = toolMatchesLeaf(name, WORKER_SPAWN_TOOL);
|
|
834
|
+
const title = spawn ? workerTitle(args) : null;
|
|
511
835
|
items.push({
|
|
512
836
|
kind: "worker",
|
|
513
837
|
id: event.id,
|
|
514
838
|
turnId,
|
|
515
839
|
callId,
|
|
516
|
-
action:
|
|
840
|
+
action: spawn ? "spawn" : "message",
|
|
517
841
|
prompt: workerPrompt(args),
|
|
842
|
+
...(title ? { title } : {}),
|
|
518
843
|
workerSessionId: extractSessionRef(args),
|
|
519
844
|
failure: null,
|
|
520
845
|
status: "running",
|
|
@@ -536,6 +861,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
536
861
|
(item): item is ToolCallItem => item.kind === "tool-call" && item.callId === callId,
|
|
537
862
|
);
|
|
538
863
|
if (existing) {
|
|
864
|
+
if (display) existing.display = display;
|
|
539
865
|
if (args != null) {
|
|
540
866
|
existing.arguments = args;
|
|
541
867
|
}
|
|
@@ -552,6 +878,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
552
878
|
turnId,
|
|
553
879
|
callId,
|
|
554
880
|
name,
|
|
881
|
+
...(display ? { display } : {}),
|
|
555
882
|
arguments: args,
|
|
556
883
|
output: undefined,
|
|
557
884
|
truncation: null,
|
|
@@ -570,12 +897,20 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
570
897
|
if (!target) {
|
|
571
898
|
break;
|
|
572
899
|
}
|
|
900
|
+
resolvePresentationWait(event, turnId ?? target.turnId);
|
|
573
901
|
if (target.kind === "worker") {
|
|
574
902
|
// A worker spawn/message that returns an error flag (or an MCP
|
|
575
903
|
// isError result) settles to "failed" too, so WorkerRow surfaces it.
|
|
576
904
|
target.status = isErrorOutput(payload) ? "failed" : "complete";
|
|
577
905
|
target.workerSessionId = target.workerSessionId ?? extractSessionRef(payload.output);
|
|
578
906
|
target.failure = target.status === "failed" ? workerFailure(payload.output) : null;
|
|
907
|
+
// A spawned or re-messaged worker owes this session a result.
|
|
908
|
+
if (target.status === "complete" && target.workerSessionId) {
|
|
909
|
+
if (target.action === "spawn") workerSessions.add(target.workerSessionId);
|
|
910
|
+
if (workerSessions.has(target.workerSessionId)) {
|
|
911
|
+
pendingWorkerSessions.add(target.workerSessionId);
|
|
912
|
+
}
|
|
913
|
+
}
|
|
579
914
|
break;
|
|
580
915
|
}
|
|
581
916
|
// An output carrying an explicit error flag (or an MCP isError result)
|
|
@@ -616,6 +951,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
616
951
|
}
|
|
617
952
|
|
|
618
953
|
case "turn.started": {
|
|
954
|
+
if (turnId) clearUnscopedTerminalTracking();
|
|
619
955
|
if (!turnId) break;
|
|
620
956
|
const queuedAt = queuedAtByTurn.get(turnId);
|
|
621
957
|
if (!queuedAt) break;
|
|
@@ -631,7 +967,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
631
967
|
null,
|
|
632
968
|
0,
|
|
633
969
|
queuedAt,
|
|
634
|
-
`${
|
|
970
|
+
`${turnId}-queue`,
|
|
635
971
|
);
|
|
636
972
|
break;
|
|
637
973
|
}
|
|
@@ -656,7 +992,21 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
656
992
|
const startupPhase = startupPhaseForSandboxOperation(name);
|
|
657
993
|
if (startupPhase) {
|
|
658
994
|
closeStreamingTail();
|
|
659
|
-
settleStartupPhase(
|
|
995
|
+
settleStartupPhase(
|
|
996
|
+
startupPhase,
|
|
997
|
+
status,
|
|
998
|
+
numberOrNull(payload.durationMs),
|
|
999
|
+
origin,
|
|
1000
|
+
0,
|
|
1001
|
+
undefined,
|
|
1002
|
+
undefined,
|
|
1003
|
+
status === "cancelled"
|
|
1004
|
+
? (startupLifecycleWaitReason(payload) ??
|
|
1005
|
+
(payload.failureCode === "rotation_in_progress"
|
|
1006
|
+
? "rotation_in_progress"
|
|
1007
|
+
: undefined))
|
|
1008
|
+
: undefined,
|
|
1009
|
+
);
|
|
660
1010
|
break;
|
|
661
1011
|
}
|
|
662
1012
|
// Routine per-turn platform plumbing that runs before EVERY turn to
|
|
@@ -669,8 +1019,13 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
669
1019
|
// Rendering either every turn reads as churn. Only FAILURES surface, and
|
|
670
1020
|
// they surface loudly — the failed event below creates its own item even
|
|
671
1021
|
// without a started row.
|
|
1022
|
+
// - optional-repository-access: the worker's per-turn report of an
|
|
1023
|
+
// automatically attached repository that sat the turn out, reported
|
|
1024
|
+
// like a skipped optional clone (event payload + log, no row).
|
|
672
1025
|
if (
|
|
673
|
-
(name === "repository-clone" ||
|
|
1026
|
+
(name === "repository-clone" ||
|
|
1027
|
+
name === "file-resource-download" ||
|
|
1028
|
+
name === "optional-repository-access") &&
|
|
674
1029
|
status !== "failed"
|
|
675
1030
|
) {
|
|
676
1031
|
break;
|
|
@@ -758,6 +1113,12 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
758
1113
|
if (!isSessionStatus(status)) {
|
|
759
1114
|
break;
|
|
760
1115
|
}
|
|
1116
|
+
const blocked = status === "requires_action" && payload.code === "admission_blocked";
|
|
1117
|
+
if (!blocked && openAdmissionBlocks.length > 0) {
|
|
1118
|
+
for (const open of openAdmissionBlocks) open.resolvedAt = event.occurredAt;
|
|
1119
|
+
openAdmissionBlocks = [];
|
|
1120
|
+
}
|
|
1121
|
+
if (status === "running") resolvePresentationWait(event, turnId);
|
|
761
1122
|
// Only attention-worthy statuses earn a timeline divider. queued /
|
|
762
1123
|
// running / idle are machinery telemetry: the header pill carries the
|
|
763
1124
|
// live status, the shimmer says "running", and the turn chip's duration
|
|
@@ -769,27 +1130,93 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
769
1130
|
const previous = [...items]
|
|
770
1131
|
.reverse()
|
|
771
1132
|
.find((item): item is SessionStatusItem => item.kind === "session-status");
|
|
772
|
-
if (
|
|
1133
|
+
if (
|
|
1134
|
+
previous?.status === status &&
|
|
1135
|
+
!previous.resolvedAt &&
|
|
1136
|
+
Boolean(previous.blocked) === blocked
|
|
1137
|
+
) {
|
|
773
1138
|
break;
|
|
774
1139
|
}
|
|
775
|
-
|
|
1140
|
+
const item: SessionStatusItem = {
|
|
776
1141
|
kind: "session-status",
|
|
777
1142
|
id: event.id,
|
|
778
1143
|
status,
|
|
779
1144
|
occurredAt: event.occurredAt,
|
|
780
|
-
|
|
1145
|
+
...(blocked ? { blocked: true as const } : {}),
|
|
1146
|
+
};
|
|
1147
|
+
if (blocked) {
|
|
1148
|
+
openAdmissionBlocks.push(item);
|
|
1149
|
+
items.push(item);
|
|
1150
|
+
} else if (status === "requires_action") rememberPresentationWait(item, turnId);
|
|
1151
|
+
else {
|
|
1152
|
+
items.push(item);
|
|
1153
|
+
if (status === "failed") {
|
|
1154
|
+
const key = turnId ?? presentationTurnId;
|
|
1155
|
+
const failures = presentationFailures.get(key) ?? [];
|
|
1156
|
+
failures.push(item);
|
|
1157
|
+
presentationFailures.set(key, failures);
|
|
1158
|
+
}
|
|
1159
|
+
}
|
|
781
1160
|
break;
|
|
782
1161
|
}
|
|
783
1162
|
|
|
784
|
-
case "
|
|
785
|
-
|
|
1163
|
+
case "subscription.account.switched": {
|
|
1164
|
+
// One neutral receipt, not its provider-specific compatibility alias.
|
|
1165
|
+
// Never expose connection ids, personal labels, or provider error text.
|
|
1166
|
+
const text =
|
|
1167
|
+
payload.reason === "return_to_preferred"
|
|
1168
|
+
? "Returned to your preferred model."
|
|
1169
|
+
: payload.reason === "failover_cross_provider"
|
|
1170
|
+
? "Switched to a fallback model."
|
|
1171
|
+
: "Switched to another subscription account.";
|
|
786
1172
|
items.push({
|
|
1173
|
+
kind: "notice",
|
|
1174
|
+
id: event.id,
|
|
1175
|
+
tone: "input",
|
|
1176
|
+
text,
|
|
1177
|
+
occurredAt: event.occurredAt,
|
|
1178
|
+
});
|
|
1179
|
+
break;
|
|
1180
|
+
}
|
|
1181
|
+
|
|
1182
|
+
case "codex.capacity.waiting":
|
|
1183
|
+
case "turn.capacity_waiting": {
|
|
1184
|
+
const capacityWait = capacityWaitPresentation(event.type, payload, turnId);
|
|
1185
|
+
const item: NoticeItem = {
|
|
787
1186
|
kind: "notice",
|
|
788
1187
|
id: event.id,
|
|
789
1188
|
tone: "waiting",
|
|
790
|
-
text:
|
|
1189
|
+
text: capacityWait.text,
|
|
1190
|
+
capacityWait: {
|
|
1191
|
+
turnId: capacityWait.turnId,
|
|
1192
|
+
label: capacityWait.label,
|
|
1193
|
+
detail: capacityWait.detail,
|
|
1194
|
+
},
|
|
791
1195
|
occurredAt: event.occurredAt,
|
|
1196
|
+
};
|
|
1197
|
+
// A re-armed wait continues the same blocked span; one live wait per turn.
|
|
1198
|
+
openCapacityWaits = openCapacityWaits.filter((open) => {
|
|
1199
|
+
if ((open.capacityWait?.turnId ?? null) !== turnId) return true;
|
|
1200
|
+
open.resolvedAt = event.occurredAt;
|
|
1201
|
+
return false;
|
|
792
1202
|
});
|
|
1203
|
+
items.push(item);
|
|
1204
|
+
openCapacityWaits.push(item);
|
|
1205
|
+
break;
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1208
|
+
case "session.requiresAction": {
|
|
1209
|
+
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
1210
|
+
rememberPresentationWait(
|
|
1211
|
+
{
|
|
1212
|
+
kind: "notice",
|
|
1213
|
+
id: event.id,
|
|
1214
|
+
tone: "waiting",
|
|
1215
|
+
text: "Approval needed — the turn is paused until someone decides.",
|
|
1216
|
+
occurredAt: event.occurredAt,
|
|
1217
|
+
},
|
|
1218
|
+
turnId,
|
|
1219
|
+
);
|
|
793
1220
|
break;
|
|
794
1221
|
}
|
|
795
1222
|
|
|
@@ -804,6 +1231,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
804
1231
|
estimatedTokensBefore: numberOrNull(payload.estimatedTokensBefore),
|
|
805
1232
|
estimatedTokensAfter: null,
|
|
806
1233
|
skipReason: null,
|
|
1234
|
+
providerRejection: null,
|
|
807
1235
|
implementation:
|
|
808
1236
|
typeof payload.implementation === "string" ? payload.implementation : null,
|
|
809
1237
|
occurredAt: event.occurredAt,
|
|
@@ -821,6 +1249,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
821
1249
|
estimatedTokensBefore: numberOrNull(payload.estimatedTokensBefore),
|
|
822
1250
|
estimatedTokensAfter: numberOrNull(payload.estimatedTokensAfter),
|
|
823
1251
|
skipReason: null,
|
|
1252
|
+
providerRejection: null,
|
|
824
1253
|
implementation:
|
|
825
1254
|
typeof payload.implementation === "string" ? payload.implementation : null,
|
|
826
1255
|
occurredAt: event.occurredAt,
|
|
@@ -838,6 +1267,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
838
1267
|
estimatedTokensBefore: numberOrNull(payload.estimatedTokensBefore),
|
|
839
1268
|
estimatedTokensAfter: null,
|
|
840
1269
|
skipReason: typeof payload.reason === "string" ? payload.reason : null,
|
|
1270
|
+
providerRejection: compactionProviderRejection(payload),
|
|
841
1271
|
implementation:
|
|
842
1272
|
typeof payload.implementation === "string" ? payload.implementation : null,
|
|
843
1273
|
occurredAt: event.occurredAt,
|
|
@@ -854,6 +1284,23 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
854
1284
|
turnId,
|
|
855
1285
|
(startupRecoveryRevisionByTurn.get(turnId) ?? 0) + 1,
|
|
856
1286
|
);
|
|
1287
|
+
if (
|
|
1288
|
+
!event.duplicateOfEventId &&
|
|
1289
|
+
(!event.turnAssociation || event.turnAssociation === "current")
|
|
1290
|
+
) {
|
|
1291
|
+
// Human Retry reopens the existing logical turn, unlike a new
|
|
1292
|
+
// prompt. Keep failure evidence, but do not let its old settlement
|
|
1293
|
+
// close the recovered turn's work surface.
|
|
1294
|
+
for (const item of items) {
|
|
1295
|
+
if (item.kind === "turn-end" && item.turnId === turnId && item.outcome === "failed") {
|
|
1296
|
+
item.resumedAt ??= event.occurredAt;
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
for (const item of presentationFailures.get(turnId) ?? []) {
|
|
1300
|
+
item.resolvedAt = event.occurredAt;
|
|
1301
|
+
}
|
|
1302
|
+
presentationFailures.delete(turnId);
|
|
1303
|
+
}
|
|
857
1304
|
}
|
|
858
1305
|
break;
|
|
859
1306
|
}
|
|
@@ -868,6 +1315,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
868
1315
|
case "tool.auth_needed":
|
|
869
1316
|
case "credential.auth_needed": {
|
|
870
1317
|
const capability = capabilityAuthorizationRequest(payload.capability);
|
|
1318
|
+
const setupRequest = customMcpSetupRequest(payload.setupRequest);
|
|
871
1319
|
if (
|
|
872
1320
|
event.type === "tool.auth_needed" &&
|
|
873
1321
|
!stringValue(payload.toolName) &&
|
|
@@ -890,25 +1338,44 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
890
1338
|
id: event.id,
|
|
891
1339
|
turnId,
|
|
892
1340
|
serverId: typeof payload.serverId === "string" ? payload.serverId : null,
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
1341
|
+
canonicalServerId:
|
|
1342
|
+
typeof payload.canonicalServerId === "string" ? payload.canonicalServerId : null,
|
|
1343
|
+
connectionSubjectScope:
|
|
1344
|
+
payload.connectionSubjectScope === "subject" ||
|
|
1345
|
+
payload.connectionSubjectScope === "workspace"
|
|
1346
|
+
? payload.connectionSubjectScope
|
|
1347
|
+
: null,
|
|
1348
|
+
source:
|
|
1349
|
+
capability || setupRequest
|
|
1350
|
+
? "capability"
|
|
1351
|
+
: event.type === "tool.auth_needed"
|
|
1352
|
+
? "tool"
|
|
1353
|
+
: "credential",
|
|
898
1354
|
providerDomain: stringValue(payload.providerDomain),
|
|
899
1355
|
connectionId: typeof payload.connectionId === "string" ? payload.connectionId : null,
|
|
900
|
-
|
|
1356
|
+
authoritySource: payload.authoritySource === "host" ? "host" : null,
|
|
1357
|
+
reason: authNeededReason(
|
|
1358
|
+
payload.authoritySource === "host" && typeof payload.authorizationUrl === "string"
|
|
1359
|
+
? (payload.hostReason ?? payload.reason)
|
|
1360
|
+
: payload.reason,
|
|
1361
|
+
),
|
|
901
1362
|
scopes: stringList(payload.scopes),
|
|
902
1363
|
resource: typeof payload.resource === "string" ? payload.resource : null,
|
|
903
1364
|
toolName: typeof payload.toolName === "string" ? payload.toolName : null,
|
|
904
1365
|
authorizationUrl:
|
|
905
1366
|
typeof payload.authorizationUrl === "string" ? payload.authorizationUrl : null,
|
|
906
1367
|
capability,
|
|
1368
|
+
setupRequest,
|
|
907
1369
|
occurredAt: event.occurredAt,
|
|
908
1370
|
});
|
|
909
1371
|
break;
|
|
910
1372
|
}
|
|
911
1373
|
|
|
1374
|
+
case "usage.exhausted": {
|
|
1375
|
+
allowanceNotice(event, turnId);
|
|
1376
|
+
break;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
912
1379
|
case "turn.completed": {
|
|
913
1380
|
// A standalone manual compaction uses the turn ledger for fencing and
|
|
914
1381
|
// recovery, but it is maintenance rather than a conversational turn.
|
|
@@ -917,16 +1384,40 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
917
1384
|
// extra agent response.
|
|
918
1385
|
if (payload.maintenance === "context_compaction") {
|
|
919
1386
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
1387
|
+
takeAgentResponse(turnId);
|
|
1388
|
+
takePendingWaitOutcome(turnId);
|
|
920
1389
|
break;
|
|
921
1390
|
}
|
|
922
1391
|
// Credit exhaustion arrives as a NOMINALLY completed turn (`detail:
|
|
923
|
-
// "insufficient
|
|
1392
|
+
// "insufficient Opengeni credits"`, `segmentLimit: "budget_exhausted"`)
|
|
924
1393
|
// — the engine ended the segment early, it did not finish the work.
|
|
925
1394
|
// Rendering it as a clean "complete" turn is a lie that leaves the
|
|
926
1395
|
// session looking healthy while every future turn silently dies, so it
|
|
927
1396
|
// projects exactly like a failed turn plus an explicit notice.
|
|
1397
|
+
const allowance = parseAllowanceExhaustedRefusal(payload);
|
|
1398
|
+
// The task's own usage limit refused the next model request. That is a
|
|
1399
|
+
// person's setting doing its job, not a failure or a billing problem:
|
|
1400
|
+
// the turn settles complete and one calm row says what stopped it.
|
|
1401
|
+
if (payload.segmentLimit === SESSION_USAGE_LIMIT_SEGMENT) {
|
|
1402
|
+
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
1403
|
+
takeAgentResponse(turnId);
|
|
1404
|
+
takePendingWaitOutcome(turnId);
|
|
1405
|
+
items.push(turnEndItem(event, "complete", null));
|
|
1406
|
+
items.push(sessionUsageLimitNotice(event, payload));
|
|
1407
|
+
break;
|
|
1408
|
+
}
|
|
1409
|
+
if (allowance) {
|
|
1410
|
+
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
1411
|
+
takeAgentResponse(turnId);
|
|
1412
|
+
takePendingWaitOutcome(turnId);
|
|
1413
|
+
items.push(turnEndItem(event, "failed", ALLOWANCE_TURN_END_TEXT));
|
|
1414
|
+
allowanceNotice(event, turnId);
|
|
1415
|
+
break;
|
|
1416
|
+
}
|
|
928
1417
|
if (isCreditExhaustionPayload(payload)) {
|
|
929
1418
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
1419
|
+
takeAgentResponse(turnId);
|
|
1420
|
+
takePendingWaitOutcome(turnId);
|
|
930
1421
|
items.push(turnEndItem(event, "failed", CREDIT_EXHAUSTION_MESSAGE));
|
|
931
1422
|
items.push({
|
|
932
1423
|
kind: "notice",
|
|
@@ -937,44 +1428,166 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
937
1428
|
});
|
|
938
1429
|
break;
|
|
939
1430
|
}
|
|
1431
|
+
const latestAgentResponse = takeAgentResponse(turnId);
|
|
1432
|
+
const finalOutput = stringValue(payload.output);
|
|
1433
|
+
const visibleFinalOutput = stripOpaqueCitationTokens(finalOutput).trim();
|
|
1434
|
+
const visibleTrackedResponse = stripOpaqueCitationTokens(
|
|
1435
|
+
latestAgentResponse?.item.text ?? "",
|
|
1436
|
+
).trim();
|
|
1437
|
+
const finalOutputMirrorsCommentary =
|
|
1438
|
+
latestAgentResponse?.item.phase === "commentary" &&
|
|
1439
|
+
visibleTrackedResponse === visibleFinalOutput;
|
|
1440
|
+
const hasAuthoritativeFinalOutput =
|
|
1441
|
+
Boolean(visibleFinalOutput) && !finalOutputMirrorsCommentary;
|
|
1442
|
+
const pendingWaitOutcome = takePendingWaitOutcome(turnId);
|
|
1443
|
+
if (hasAuthoritativeFinalOutput) {
|
|
1444
|
+
// The final message normally lands as its own completion before
|
|
1445
|
+
// `turn.completed` (older workers commit a copy with it), but legacy or
|
|
1446
|
+
// partially compacted ledgers may retain only the terminal output
|
|
1447
|
+
// receipt. Keep that authoritative response visible instead of
|
|
1448
|
+
// leaving it trapped in raw audit data.
|
|
1449
|
+
if (
|
|
1450
|
+
latestAgentResponse &&
|
|
1451
|
+
!latestAgentResponse.completed &&
|
|
1452
|
+
finalOutput.startsWith(latestAgentResponse.item.text)
|
|
1453
|
+
) {
|
|
1454
|
+
latestAgentResponse.item.text = finalOutput;
|
|
1455
|
+
latestAgentResponse.item.streaming = false;
|
|
1456
|
+
if (
|
|
1457
|
+
latestAgentResponse.item.startedAt === undefined &&
|
|
1458
|
+
latestAgentResponse.item.occurredAt !== event.occurredAt
|
|
1459
|
+
) {
|
|
1460
|
+
latestAgentResponse.item.startedAt = latestAgentResponse.item.occurredAt;
|
|
1461
|
+
}
|
|
1462
|
+
latestAgentResponse.item.occurredAt = event.occurredAt;
|
|
1463
|
+
const responseIndex = items.indexOf(latestAgentResponse.item);
|
|
1464
|
+
if (responseIndex >= 0 && responseIndex < items.length - 1) {
|
|
1465
|
+
items.splice(responseIndex, 1);
|
|
1466
|
+
items.push(latestAgentResponse.item);
|
|
1467
|
+
}
|
|
1468
|
+
} else if (visibleTrackedResponse !== visibleFinalOutput) {
|
|
1469
|
+
items.push({
|
|
1470
|
+
kind: "agent-message",
|
|
1471
|
+
id: `${event.id}-output-message`,
|
|
1472
|
+
turnId,
|
|
1473
|
+
text: finalOutput,
|
|
1474
|
+
streaming: false,
|
|
1475
|
+
occurredAt: event.occurredAt,
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
}
|
|
940
1479
|
finalizeOpen(turnId, "complete", event.occurredAt);
|
|
941
|
-
|
|
1480
|
+
const completedTurn = turnEndItem(event, "complete", null);
|
|
1481
|
+
items.push(completedTurn);
|
|
1482
|
+
if (turnCompletedWithEmptyFinalReply(payload)) {
|
|
1483
|
+
items.push({
|
|
1484
|
+
kind: "notice",
|
|
1485
|
+
id: `${event.id}-empty-final-reply`,
|
|
1486
|
+
tone: "input",
|
|
1487
|
+
text: EMPTY_FINAL_REPLY_NOTICE,
|
|
1488
|
+
recordedOutcome: true,
|
|
1489
|
+
occurredAt: event.occurredAt,
|
|
1490
|
+
});
|
|
1491
|
+
}
|
|
1492
|
+
const hasCompletedFinalResponse =
|
|
1493
|
+
latestAgentResponse?.completed === true &&
|
|
1494
|
+
latestAgentResponse.item.phase !== "commentary" &&
|
|
1495
|
+
Boolean(visibleTrackedResponse);
|
|
1496
|
+
if (!hasAuthoritativeFinalOutput && pendingWaitOutcome) {
|
|
1497
|
+
// Preserve existing prose, including delta-only streams, when a
|
|
1498
|
+
// trailing wait yields without a terminal output receipt.
|
|
1499
|
+
completedTurn.preserveWaitResponse = true;
|
|
1500
|
+
}
|
|
1501
|
+
if (!hasAuthoritativeFinalOutput && !hasCompletedFinalResponse && pendingWaitOutcome) {
|
|
1502
|
+
const notice: NoticeItem = {
|
|
1503
|
+
kind: "notice",
|
|
1504
|
+
id: `${pendingWaitOutcome.id}-visible-outcome`,
|
|
1505
|
+
tone: "waiting",
|
|
1506
|
+
text: waitingOutcomeText(pendingWaitOutcome.reason),
|
|
1507
|
+
recordedOutcome: true,
|
|
1508
|
+
...(pendingWorkerSessions.size > 0
|
|
1509
|
+
? { waitingAgents: pendingWorkerSessions.size }
|
|
1510
|
+
: {}),
|
|
1511
|
+
occurredAt: pendingWaitOutcome.occurredAt,
|
|
1512
|
+
};
|
|
1513
|
+
items.push(notice);
|
|
1514
|
+
openWaitNotice = notice;
|
|
1515
|
+
}
|
|
942
1516
|
break;
|
|
943
1517
|
}
|
|
944
1518
|
|
|
945
1519
|
case "turn.failed": {
|
|
1520
|
+
takeAgentResponse(turnId);
|
|
1521
|
+
takePendingWaitOutcome(turnId);
|
|
946
1522
|
const hadActivity = hasTurnActivity(items, turnId);
|
|
947
1523
|
// Credit death can hide behind fields `failureMessage` doesn't read
|
|
948
1524
|
// (detail/segmentLimit), so classify the whole payload before falling
|
|
949
1525
|
// back to the generic error/message extraction.
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
1526
|
+
const allowance = parseAllowanceExhaustedRefusal(payload);
|
|
1527
|
+
const failureText = allowance
|
|
1528
|
+
? ALLOWANCE_TURN_END_TEXT
|
|
1529
|
+
: isCreditExhaustionPayload(payload)
|
|
1530
|
+
? CREDIT_EXHAUSTION_MESSAGE
|
|
1531
|
+
: failureMessage(payload);
|
|
953
1532
|
// The TURN failed — the in-flight items did not. Chip doctrine: red is
|
|
954
1533
|
// spent once, on the turn-level outcome. Items caught mid-flight read
|
|
955
1534
|
// as calm "interrupted" (same as turn.cancelled); an item that itself
|
|
956
1535
|
// failed keeps its own failed status from its output event.
|
|
957
1536
|
finalizeOpen(turnId, "cancelled", event.occurredAt);
|
|
958
1537
|
items.push(turnEndItem(event, "failed", failureText));
|
|
959
|
-
|
|
1538
|
+
// A compaction failure names its outcome and keeps the recorded
|
|
1539
|
+
// provider text behind Details. The turn's own failed compaction
|
|
1540
|
+
// landmark is the one place that shows it: it receives the recorded
|
|
1541
|
+
// text, and no separate notice repeats it.
|
|
1542
|
+
// Retry reopens the same logical turn, so only a landmark from this
|
|
1543
|
+
// attempt (after the turn's previous settlement) can own this failure.
|
|
1544
|
+
const failure = allowance ? null : presentTurnFailureText(failureText);
|
|
1545
|
+
let landmarkIndex = -1;
|
|
1546
|
+
if (failure?.detail !== undefined && turnId !== null) {
|
|
1547
|
+
// Skip the turn-end just pushed for this failure.
|
|
1548
|
+
for (let index = items.length - 2; index >= 0; index -= 1) {
|
|
1549
|
+
const item = items[index];
|
|
1550
|
+
if (item?.kind === "turn-end" && item.turnId === turnId) break;
|
|
1551
|
+
if (
|
|
1552
|
+
item?.kind === "context-compaction" &&
|
|
1553
|
+
item.turnId === turnId &&
|
|
1554
|
+
item.phase === "skipped" &&
|
|
1555
|
+
item.skipReason === "summarization_failed"
|
|
1556
|
+
) {
|
|
1557
|
+
landmarkIndex = index;
|
|
1558
|
+
break;
|
|
1559
|
+
}
|
|
1560
|
+
}
|
|
1561
|
+
}
|
|
1562
|
+
const landmark = landmarkIndex >= 0 ? items[landmarkIndex] : undefined;
|
|
1563
|
+
if (landmark?.kind === "context-compaction" && failure?.detail !== undefined) {
|
|
1564
|
+
items[landmarkIndex] = { ...landmark, failureDetail: failure.detail };
|
|
1565
|
+
}
|
|
1566
|
+
if (allowance) {
|
|
1567
|
+
allowanceNotice(event, turnId);
|
|
1568
|
+
} else if (!hadActivity && !landmark) {
|
|
960
1569
|
items.push({
|
|
961
1570
|
kind: "notice",
|
|
962
1571
|
id: event.id,
|
|
963
1572
|
tone: "failed",
|
|
964
|
-
text:
|
|
1573
|
+
text: failure?.summary ?? "The turn failed.",
|
|
1574
|
+
...(failure?.detail ? { details: { label: "Details", value: failure.detail } } : {}),
|
|
965
1575
|
occurredAt: event.occurredAt,
|
|
966
1576
|
});
|
|
967
1577
|
}
|
|
968
1578
|
break;
|
|
969
1579
|
}
|
|
970
1580
|
|
|
1581
|
+
case "turn.superseded":
|
|
971
1582
|
case "turn.cancelled": {
|
|
972
1583
|
// A retraction of a never-started queued turn is not a turn ending —
|
|
973
1584
|
// the message was withdrawn before any work happened; show nothing.
|
|
974
1585
|
// A null turnId proves nothing, so it keeps the legacy finalize path.
|
|
975
|
-
if (turnId && !prescan.startedTurnIds.has(turnId)) {
|
|
1586
|
+
if (event.type === "turn.cancelled" && turnId && !prescan.startedTurnIds.has(turnId)) {
|
|
976
1587
|
break;
|
|
977
1588
|
}
|
|
1589
|
+
takeAgentResponse(turnId);
|
|
1590
|
+
takePendingWaitOutcome(turnId);
|
|
978
1591
|
const hadActivity = hasTurnActivity(items, turnId);
|
|
979
1592
|
finalizeOpen(turnId, "cancelled", event.occurredAt);
|
|
980
1593
|
items.push(turnEndItem(event, "cancelled", null));
|
|
@@ -990,6 +1603,45 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
990
1603
|
break;
|
|
991
1604
|
}
|
|
992
1605
|
|
|
1606
|
+
case "instruction.confirmation.recovered": {
|
|
1607
|
+
const receipt = asRecord(payload.receipt);
|
|
1608
|
+
if (receipt.outcome !== "published") break;
|
|
1609
|
+
closeStreamingTail();
|
|
1610
|
+
items.push({
|
|
1611
|
+
kind: "notice",
|
|
1612
|
+
id: event.id,
|
|
1613
|
+
tone: "input",
|
|
1614
|
+
text: "Your previously approved workspace instruction was saved.",
|
|
1615
|
+
occurredAt: event.occurredAt,
|
|
1616
|
+
});
|
|
1617
|
+
break;
|
|
1618
|
+
}
|
|
1619
|
+
case "knowledge.confirmation.recovered":
|
|
1620
|
+
case "knowledge.source.prepared":
|
|
1621
|
+
case "knowledge.source.failed": {
|
|
1622
|
+
const receipt = asRecord(payload.receipt);
|
|
1623
|
+
const outcome = event.type === "knowledge.source.failed" ? "failed" : receipt.outcome;
|
|
1624
|
+
if (
|
|
1625
|
+
(event.type !== "knowledge.confirmation.recovered" &&
|
|
1626
|
+
typeof payload.fileId !== "string") ||
|
|
1627
|
+
!["published", "pending", "rejected", "archived", "failed"].includes(String(outcome))
|
|
1628
|
+
)
|
|
1629
|
+
break;
|
|
1630
|
+
closeStreamingTail();
|
|
1631
|
+
items.push({
|
|
1632
|
+
kind: "knowledge",
|
|
1633
|
+
id: event.id,
|
|
1634
|
+
turnId,
|
|
1635
|
+
fileId: typeof payload.fileId === "string" ? payload.fileId : undefined,
|
|
1636
|
+
filename: typeof payload.filename === "string" ? payload.filename : undefined,
|
|
1637
|
+
entryId: typeof receipt.entryId === "string" ? receipt.entryId : undefined,
|
|
1638
|
+
status: outcome === "failed" ? "failed" : "complete",
|
|
1639
|
+
outcome: outcome as "published" | "pending" | "rejected" | "archived" | "failed",
|
|
1640
|
+
occurredAt: event.occurredAt,
|
|
1641
|
+
});
|
|
1642
|
+
break;
|
|
1643
|
+
}
|
|
1644
|
+
|
|
993
1645
|
case "memory.saved":
|
|
994
1646
|
case "memory.corrected": {
|
|
995
1647
|
// A first-party memory write is a discrete step, not streamed text, so it
|
|
@@ -1020,6 +1672,9 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
1020
1672
|
case "goal.cleared":
|
|
1021
1673
|
case "goal.held":
|
|
1022
1674
|
case "goal.continuation": {
|
|
1675
|
+
if (event.type === "goal.held" && payload.actor === "agent") {
|
|
1676
|
+
rememberPendingWaitOutcome(pendingWaitOutcomeByTurn, event, payload, turnId);
|
|
1677
|
+
}
|
|
1023
1678
|
// Agent tool mutations already appear as tool-call rows in the activity
|
|
1024
1679
|
// cluster. Re-emitting them as GoalRow landmarks splits "N steps" mid-turn.
|
|
1025
1680
|
if (shouldSuppressAgentGoalLandmark(event.type, payload)) {
|
|
@@ -1038,12 +1693,28 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
|
|
|
1038
1693
|
break;
|
|
1039
1694
|
}
|
|
1040
1695
|
|
|
1696
|
+
case "session.wait.started": {
|
|
1697
|
+
if (payload.actor === "agent") {
|
|
1698
|
+
rememberPendingWaitOutcome(pendingWaitOutcomeByTurn, event, payload, turnId);
|
|
1699
|
+
}
|
|
1700
|
+
break;
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1041
1703
|
default:
|
|
1042
1704
|
break;
|
|
1043
1705
|
}
|
|
1044
1706
|
}
|
|
1045
1707
|
|
|
1708
|
+
const sourceEventsById = new Map(events.map((event) => [event.id, event]));
|
|
1046
1709
|
for (const item of items) {
|
|
1710
|
+
const source = sourceEventsById.get(item.id);
|
|
1711
|
+
const canonical = "annotationSource" in item ? item.annotationSource : undefined;
|
|
1712
|
+
item.sourceEvents = [
|
|
1713
|
+
...(source ? [{ eventId: source.id, sequence: source.sequence }] : []),
|
|
1714
|
+
...(canonical && canonical.eventId !== source?.id
|
|
1715
|
+
? [{ eventId: canonical.eventId, sequence: canonical.sequence }]
|
|
1716
|
+
: []),
|
|
1717
|
+
];
|
|
1047
1718
|
if (item.kind === "agent-message") {
|
|
1048
1719
|
item.text = stripOpaqueCitationTokens(item.text);
|
|
1049
1720
|
}
|
|
@@ -1139,10 +1810,13 @@ function humanInputQuestions(value: unknown): HumanInputQuestion[] {
|
|
|
1139
1810
|
if (
|
|
1140
1811
|
!id ||
|
|
1141
1812
|
!prompt ||
|
|
1142
|
-
(kind !== "text" && kind !== "single_select" && kind !== "multi_select")
|
|
1813
|
+
(kind !== "text" && kind !== "single_select" && kind !== "multi_select" && kind !== "secret")
|
|
1143
1814
|
) {
|
|
1144
1815
|
return [];
|
|
1145
1816
|
}
|
|
1817
|
+
// Secret questions keep their spec (names and hints only; never a value).
|
|
1818
|
+
const secret = kind === "secret" ? humanInputSecretSpec(question.secret) : null;
|
|
1819
|
+
if (kind === "secret" && !secret) return [];
|
|
1146
1820
|
const options = Array.isArray(question.options)
|
|
1147
1821
|
? question.options.map(asRecord).flatMap((option) => {
|
|
1148
1822
|
const optionId = stringValue(option.id);
|
|
@@ -1162,20 +1836,54 @@ function humanInputQuestions(value: unknown): HumanInputQuestion[] {
|
|
|
1162
1836
|
options,
|
|
1163
1837
|
required: question.required === true,
|
|
1164
1838
|
allowOther: question.allowOther === true,
|
|
1839
|
+
...(secret ? { secret } : {}),
|
|
1165
1840
|
},
|
|
1166
1841
|
];
|
|
1167
1842
|
});
|
|
1168
1843
|
}
|
|
1169
1844
|
|
|
1845
|
+
function humanInputSecretSpec(value: unknown): HumanInputSecretSpec | null {
|
|
1846
|
+
const spec = asRecord(value);
|
|
1847
|
+
const item = stringValue(spec.item);
|
|
1848
|
+
if (!item) return null;
|
|
1849
|
+
return spec as unknown as HumanInputSecretSpec;
|
|
1850
|
+
}
|
|
1851
|
+
|
|
1852
|
+
function humanInputSecretResult(value: unknown): HumanInputSecretAnswerResult | null {
|
|
1853
|
+
if (value === undefined || value === null) return null;
|
|
1854
|
+
const result = asRecord(value);
|
|
1855
|
+
const reference = stringValue(result.reference);
|
|
1856
|
+
const vaultName = stringValue(result.vaultName);
|
|
1857
|
+
const itemName = stringValue(result.itemName);
|
|
1858
|
+
const saveTo = result.saveTo;
|
|
1859
|
+
if (
|
|
1860
|
+
!reference ||
|
|
1861
|
+
!vaultName ||
|
|
1862
|
+
!itemName ||
|
|
1863
|
+
(saveTo !== "user" && saveTo !== "task" && saveTo !== "workspace")
|
|
1864
|
+
) {
|
|
1865
|
+
return null;
|
|
1866
|
+
}
|
|
1867
|
+
return {
|
|
1868
|
+
reference,
|
|
1869
|
+
username: stringValue(result.username) || null,
|
|
1870
|
+
vaultName,
|
|
1871
|
+
itemName,
|
|
1872
|
+
saveTo,
|
|
1873
|
+
};
|
|
1874
|
+
}
|
|
1875
|
+
|
|
1170
1876
|
function humanInputAnswer(value: Record<string, unknown>): HumanInputAnswer | null {
|
|
1171
1877
|
const questionId = stringValue(value.questionId);
|
|
1172
1878
|
if (!questionId) return null;
|
|
1879
|
+
const secret = humanInputSecretResult(value.secret);
|
|
1173
1880
|
return {
|
|
1174
1881
|
questionId,
|
|
1175
1882
|
values: Array.isArray(value.values)
|
|
1176
1883
|
? value.values.filter((answer): answer is string => typeof answer === "string")
|
|
1177
1884
|
: [],
|
|
1178
1885
|
...(typeof value.other === "string" && value.other ? { other: value.other } : {}),
|
|
1886
|
+
...(secret ? { secret } : {}),
|
|
1179
1887
|
};
|
|
1180
1888
|
}
|
|
1181
1889
|
|
|
@@ -1194,6 +1902,7 @@ function humanInputAnswerSummary(
|
|
|
1194
1902
|
label: question?.label || question?.prompt || fallbackLabel,
|
|
1195
1903
|
prompt: question?.prompt || fallbackLabel,
|
|
1196
1904
|
values,
|
|
1905
|
+
...(answer.secret ? { secret: answer.secret } : {}),
|
|
1197
1906
|
};
|
|
1198
1907
|
}
|
|
1199
1908
|
|
|
@@ -1257,8 +1966,16 @@ export function stripOpaqueCitationTokens(text: string): string {
|
|
|
1257
1966
|
return text.replace(/\s*cite(?:[^]+)+/gu, "");
|
|
1258
1967
|
}
|
|
1259
1968
|
|
|
1969
|
+
/**
|
|
1970
|
+
* The turn summary's short outcome for a usage ceiling. The adjacent notice row
|
|
1971
|
+
* carries who can raise it and when it resets (and is host-customizable), so
|
|
1972
|
+
* the summary line stays a neutral fact instead of repeating remedy prose.
|
|
1973
|
+
*/
|
|
1974
|
+
const ALLOWANCE_TURN_END_TEXT = "Usage limit reached";
|
|
1975
|
+
|
|
1260
1976
|
/** The turn-end payload shape, as `isCreditExhaustion` wants it. */
|
|
1261
1977
|
function isCreditExhaustionPayload(payload: Record<string, unknown>): boolean {
|
|
1978
|
+
if (payload.code === "allowance_exhausted") return false;
|
|
1262
1979
|
return isCreditExhaustion({
|
|
1263
1980
|
error: typeof payload.error === "string" ? payload.error : null,
|
|
1264
1981
|
detail: typeof payload.detail === "string" ? payload.detail : null,
|
|
@@ -1309,12 +2026,16 @@ export function sessionStatusFromEvents(events: SessionEvent[]): SessionStatus |
|
|
|
1309
2026
|
non-user span folds behind a turn group, with activity blocks nested inside.
|
|
1310
2027
|
-------------------------------------------------------------------------- */
|
|
1311
2028
|
|
|
2029
|
+
/** Work items: every activity kind except assistant commentary. */
|
|
2030
|
+
type WorkActivityItem = Exclude<ActivityItem, AgentMessageItem>;
|
|
2031
|
+
|
|
1312
2032
|
/**
|
|
1313
2033
|
* Whether an item clusters into an activity block. A `switch` (not a stringly-
|
|
1314
2034
|
* typed set) so adding an {@link ActivityItem} kind is a compile-time prompt to
|
|
1315
|
-
* decide its grouping
|
|
2035
|
+
* decide its grouping, and it narrows `item` to a work item with no cast.
|
|
2036
|
+
* Assistant prose stays outside activity clusters.
|
|
1316
2037
|
*/
|
|
1317
|
-
function isActivityItem(item: TimelineItem): item is
|
|
2038
|
+
function isActivityItem(item: TimelineItem): item is WorkActivityItem {
|
|
1318
2039
|
switch (item.kind) {
|
|
1319
2040
|
case "reasoning":
|
|
1320
2041
|
case "tool-call":
|
|
@@ -1322,6 +2043,7 @@ function isActivityItem(item: TimelineItem): item is ActivityItem {
|
|
|
1322
2043
|
case "sandbox":
|
|
1323
2044
|
case "startup-phase":
|
|
1324
2045
|
case "memory":
|
|
2046
|
+
case "knowledge":
|
|
1325
2047
|
case "fleet-decision":
|
|
1326
2048
|
return true;
|
|
1327
2049
|
default:
|
|
@@ -1329,9 +2051,22 @@ function isActivityItem(item: TimelineItem): item is ActivityItem {
|
|
|
1329
2051
|
}
|
|
1330
2052
|
}
|
|
1331
2053
|
|
|
1332
|
-
export
|
|
2054
|
+
export type GroupTimelineOptions = {
|
|
2055
|
+
/** Readable assistant prose with an independent work summary for each turn. */
|
|
2056
|
+
readableTurns?: boolean;
|
|
2057
|
+
/** @deprecated Use readableTurns. Cross-turn exchange folding has been removed. */
|
|
2058
|
+
foldExchanges?: boolean;
|
|
2059
|
+
};
|
|
2060
|
+
|
|
2061
|
+
export function groupTimeline(
|
|
2062
|
+
items: TimelineItem[],
|
|
2063
|
+
options: GroupTimelineOptions = {},
|
|
2064
|
+
): TimelineGroup[] {
|
|
2065
|
+
if (options.readableTurns || options.foldExchanges) return groupReadableTurns(items);
|
|
1333
2066
|
const groups: TimelineGroup[] = [];
|
|
1334
2067
|
for (const item of items) {
|
|
2068
|
+
// A recovered capacity wait is over; it leaves no warning behind.
|
|
2069
|
+
if (item.kind === "notice" && item.capacityWait && item.resolvedAt) continue;
|
|
1335
2070
|
if (isActivityItem(item)) {
|
|
1336
2071
|
const open = groups[groups.length - 1];
|
|
1337
2072
|
if (open?.kind === "activity" && open.outcome === undefined) {
|
|
@@ -1355,6 +2090,641 @@ export function groupTimeline(items: TimelineItem[]): TimelineGroup[] {
|
|
|
1355
2090
|
return groups;
|
|
1356
2091
|
}
|
|
1357
2092
|
|
|
2093
|
+
/** Live prose stays primary; settled progress joins its own turn's work history. */
|
|
2094
|
+
function groupReadableTurns(items: TimelineItem[]): TimelineGroup[] {
|
|
2095
|
+
type Work = Extract<TimelineGroup, { kind: "activity" }>;
|
|
2096
|
+
let groups: TimelineGroup[] = [];
|
|
2097
|
+
const turns = new Map<string, Work>();
|
|
2098
|
+
// Turn identity and settlement exist even when a final-only turn has no
|
|
2099
|
+
// disclosure. A late receipt must not become a new turn or revive its clock.
|
|
2100
|
+
const seenTurns = new Set<string>();
|
|
2101
|
+
const settlements = new Map<
|
|
2102
|
+
string,
|
|
2103
|
+
{ endedAt: string; outcome?: Work["outcome"]; failureText?: string }
|
|
2104
|
+
>();
|
|
2105
|
+
const compactionTurns = new Map<ContextCompactionItem, string>();
|
|
2106
|
+
const messages = new Map<string, AgentMessageItem[]>();
|
|
2107
|
+
// Capacity waits per turn: their spans are not work, and a live one is
|
|
2108
|
+
// carried by the turn's work row instead of a separate warning.
|
|
2109
|
+
const capacityPauses = new Map<string, Array<{ startedAt: string; endedAt?: string }>>();
|
|
2110
|
+
const liveCapacityWaits = new Map<string, NoticeItem>();
|
|
2111
|
+
const inputs = new Map<string, Extract<TimelineItem, { kind: "machine-input-batch" }>>();
|
|
2112
|
+
const itemOrder = new Map(items.map((item, index) => [item.id, index]));
|
|
2113
|
+
// Turns that answer a person's message: the first turn after it, and a live
|
|
2114
|
+
// turn it steered. Their replies are never folded as mere progress.
|
|
2115
|
+
const answersPerson = new Set<string>();
|
|
2116
|
+
let personAwaitingTurn = false;
|
|
2117
|
+
// A live turn a person's message may have steered. If it keeps working after
|
|
2118
|
+
// the message, the steer was absorbed and no later turn inherits it; if it is
|
|
2119
|
+
// superseded instead, the next turn answers the person.
|
|
2120
|
+
let steeredTurn: string | undefined;
|
|
2121
|
+
let legacyTurn = "start";
|
|
2122
|
+
let currentTurn = legacyTurn;
|
|
2123
|
+
for (const item of items) {
|
|
2124
|
+
if (item.kind === "user-message") {
|
|
2125
|
+
legacyTurn = item.id;
|
|
2126
|
+
personAwaitingTurn = true;
|
|
2127
|
+
steeredTurn = undefined;
|
|
2128
|
+
if (seenTurns.has(currentTurn) && !settlements.has(currentTurn)) {
|
|
2129
|
+
answersPerson.add(currentTurn);
|
|
2130
|
+
steeredTurn = currentTurn;
|
|
2131
|
+
}
|
|
2132
|
+
}
|
|
2133
|
+
// A capacity wait belongs to the turn it blocks, even before that turn's
|
|
2134
|
+
// first step.
|
|
2135
|
+
const itemTurnId =
|
|
2136
|
+
item.kind === "notice" && item.capacityWait
|
|
2137
|
+
? item.capacityWait.turnId
|
|
2138
|
+
: "turnId" in item
|
|
2139
|
+
? item.turnId
|
|
2140
|
+
: null;
|
|
2141
|
+
const key = itemTurnId || legacyTurn;
|
|
2142
|
+
if (
|
|
2143
|
+
steeredTurn !== undefined &&
|
|
2144
|
+
key === steeredTurn &&
|
|
2145
|
+
(isActivityItem(item) || (item.kind === "agent-message" && item.text.trim()))
|
|
2146
|
+
) {
|
|
2147
|
+
personAwaitingTurn = false;
|
|
2148
|
+
steeredTurn = undefined;
|
|
2149
|
+
}
|
|
2150
|
+
// Legacy work still establishes a boundary using its prompt key.
|
|
2151
|
+
// A human message alone does not: it may be steering the existing turn.
|
|
2152
|
+
if (itemTurnId || isActivityItem(item) || (item.kind === "agent-message" && item.text.trim())) {
|
|
2153
|
+
if (key !== currentTurn && !seenTurns.has(key)) {
|
|
2154
|
+
const previous = turns.get(currentTurn);
|
|
2155
|
+
if (!settlements.has(currentTurn)) {
|
|
2156
|
+
settlements.set(currentTurn, { endedAt: item.occurredAt });
|
|
2157
|
+
}
|
|
2158
|
+
if (previous?.work && !previous.work.endedAt) previous.work.endedAt = item.occurredAt;
|
|
2159
|
+
currentTurn = key;
|
|
2160
|
+
if (personAwaitingTurn) answersPerson.add(key);
|
|
2161
|
+
personAwaitingTurn = false;
|
|
2162
|
+
steeredTurn = undefined;
|
|
2163
|
+
}
|
|
2164
|
+
seenTurns.add(key);
|
|
2165
|
+
}
|
|
2166
|
+
const workForTurn = () => {
|
|
2167
|
+
let group = turns.get(key);
|
|
2168
|
+
const firstMessage = messages.get(key)?.[0];
|
|
2169
|
+
const messageStart = firstMessage?.startedAt ?? firstMessage?.occurredAt;
|
|
2170
|
+
const activityStart =
|
|
2171
|
+
item.kind === "startup-phase"
|
|
2172
|
+
? (item.loadingStartedAt ?? item.occurredAt)
|
|
2173
|
+
: item.occurredAt;
|
|
2174
|
+
const startedAt =
|
|
2175
|
+
messageStart && Date.parse(messageStart) < Date.parse(activityStart)
|
|
2176
|
+
? messageStart
|
|
2177
|
+
: activityStart;
|
|
2178
|
+
if (!group) {
|
|
2179
|
+
const settlement = settlements.get(key);
|
|
2180
|
+
group = {
|
|
2181
|
+
kind: "activity",
|
|
2182
|
+
id: `work-${key}`,
|
|
2183
|
+
items: [],
|
|
2184
|
+
work: { startedAt, details: [] },
|
|
2185
|
+
};
|
|
2186
|
+
if (settlement) {
|
|
2187
|
+
group.work!.endedAt = settlement.endedAt;
|
|
2188
|
+
if (settlement.outcome) group.outcome = settlement.outcome;
|
|
2189
|
+
if (settlement.failureText) group.failureText = settlement.failureText;
|
|
2190
|
+
}
|
|
2191
|
+
turns.set(key, group);
|
|
2192
|
+
const firstMessageIndex =
|
|
2193
|
+
settlement && firstMessage
|
|
2194
|
+
? groups.findIndex((entry) => entry.kind === "item" && entry.item === firstMessage)
|
|
2195
|
+
: -1;
|
|
2196
|
+
if (firstMessageIndex >= 0) groups.splice(firstMessageIndex, 0, group);
|
|
2197
|
+
else groups.push(group);
|
|
2198
|
+
} else if (Date.parse(startedAt) < Date.parse(group.work!.startedAt)) {
|
|
2199
|
+
// Completion-only startup receipts can reveal an earlier phase start
|
|
2200
|
+
// after another activity has already established the work row.
|
|
2201
|
+
group.work!.startedAt = startedAt;
|
|
2202
|
+
}
|
|
2203
|
+
return group;
|
|
2204
|
+
};
|
|
2205
|
+
if (isActivityItem(item)) {
|
|
2206
|
+
const group = workForTurn();
|
|
2207
|
+
group.items.push(item);
|
|
2208
|
+
const previous = group.work!.details.at(-1);
|
|
2209
|
+
const preparing = (entry: ActivityItem) =>
|
|
2210
|
+
entry.kind === "startup-phase" || (entry.kind === "reasoning" && !entry.text.trim());
|
|
2211
|
+
if (
|
|
2212
|
+
preparing(item) &&
|
|
2213
|
+
previous?.kind === "activity" &&
|
|
2214
|
+
previous.items.every(preparing) &&
|
|
2215
|
+
previous.items.at(-1) === items[(itemOrder.get(item.id) ?? 0) - 1]
|
|
2216
|
+
)
|
|
2217
|
+
previous.items.push(item);
|
|
2218
|
+
else
|
|
2219
|
+
group.work!.details.push({ kind: "activity", id: `work-item-${item.id}`, items: [item] });
|
|
2220
|
+
delete group.work!.waiting;
|
|
2221
|
+
} else if (item.kind === "notice" && item.capacityWait) {
|
|
2222
|
+
const pauses = capacityPauses.get(key) ?? [];
|
|
2223
|
+
pauses.push({
|
|
2224
|
+
startedAt: item.occurredAt,
|
|
2225
|
+
...(item.resolvedAt ? { endedAt: item.resolvedAt } : {}),
|
|
2226
|
+
});
|
|
2227
|
+
capacityPauses.set(key, pauses);
|
|
2228
|
+
if (!item.resolvedAt) {
|
|
2229
|
+
if (settlements.has(key)) groups.push({ kind: "item", item });
|
|
2230
|
+
else {
|
|
2231
|
+
workForTurn();
|
|
2232
|
+
liveCapacityWaits.set(key, item);
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
} else if (item.kind === "turn-end") {
|
|
2236
|
+
if (!item.resumedAt) {
|
|
2237
|
+
settlements.set(key, {
|
|
2238
|
+
endedAt: item.occurredAt,
|
|
2239
|
+
outcome: item.outcome,
|
|
2240
|
+
...(item.failureText ? { failureText: item.failureText } : {}),
|
|
2241
|
+
});
|
|
2242
|
+
}
|
|
2243
|
+
const group = turns.get(key);
|
|
2244
|
+
if (group?.work) {
|
|
2245
|
+
if (item.resumedAt) {
|
|
2246
|
+
group.work.details.push({
|
|
2247
|
+
kind: "item",
|
|
2248
|
+
item: {
|
|
2249
|
+
kind: "notice",
|
|
2250
|
+
id: item.id,
|
|
2251
|
+
tone: "failed",
|
|
2252
|
+
text: item.failureText ?? "The turn failed before retrying.",
|
|
2253
|
+
occurredAt: item.occurredAt,
|
|
2254
|
+
},
|
|
2255
|
+
});
|
|
2256
|
+
continue;
|
|
2257
|
+
}
|
|
2258
|
+
group.outcome = item.outcome;
|
|
2259
|
+
if (item.failureText) group.failureText = item.failureText;
|
|
2260
|
+
group.work.endedAt = item.occurredAt;
|
|
2261
|
+
}
|
|
2262
|
+
} else if (item.kind === "context-compaction" && item.phase === "compacted") {
|
|
2263
|
+
// A maintenance checkpoint alone is not conversational work. Resolve its
|
|
2264
|
+
// attachment after real activity has established this turn's work row.
|
|
2265
|
+
compactionTurns.set(item, key);
|
|
2266
|
+
groups.push({ kind: "item", item });
|
|
2267
|
+
} else if (
|
|
2268
|
+
item.kind === "machine-input-batch" &&
|
|
2269
|
+
item.members.every((member) => ROUTINE_MACHINE_INPUT_KINDS.has(member.kind))
|
|
2270
|
+
) {
|
|
2271
|
+
const previous = inputs.get(key);
|
|
2272
|
+
if (previous) {
|
|
2273
|
+
previous.members.push(
|
|
2274
|
+
...item.members.filter((member) => !previous.members.some((old) => old.id === member.id)),
|
|
2275
|
+
);
|
|
2276
|
+
previous.sourceEvents = [...(previous.sourceEvents ?? []), ...(item.sourceEvents ?? [])];
|
|
2277
|
+
} else {
|
|
2278
|
+
const batch = { ...item, compact: true, members: [...item.members] };
|
|
2279
|
+
inputs.set(key, batch);
|
|
2280
|
+
groups.push({ kind: "item", item: batch });
|
|
2281
|
+
}
|
|
2282
|
+
} else if (
|
|
2283
|
+
item.kind === "worker-completion" ||
|
|
2284
|
+
(item.kind === "goal" && item.action === "continuation")
|
|
2285
|
+
) {
|
|
2286
|
+
const group = turns.get(currentTurn);
|
|
2287
|
+
if (group?.work) group.work.details.push({ kind: "item", item });
|
|
2288
|
+
else groups.push({ kind: "item", item });
|
|
2289
|
+
} else {
|
|
2290
|
+
const current = turns.get(currentTurn)?.work;
|
|
2291
|
+
// A live approval wait is carried by the turn header ("Waiting for you")
|
|
2292
|
+
// and decided in the host's approval surface; a second in-timeline banner
|
|
2293
|
+
// only repeats it. Once resolved it returns as the quiet recorded marker.
|
|
2294
|
+
const liveApprovalWait =
|
|
2295
|
+
item.kind === "notice" &&
|
|
2296
|
+
item.tone === "waiting" &&
|
|
2297
|
+
!item.recordedOutcome &&
|
|
2298
|
+
!item.resolvedAt &&
|
|
2299
|
+
item.text.startsWith("Approval needed") &&
|
|
2300
|
+
current !== undefined &&
|
|
2301
|
+
!current.endedAt;
|
|
2302
|
+
// Likewise the live "waiting on you" divider: the open turn's header
|
|
2303
|
+
// already reads "Waiting for you · <elapsed>". It returns, resolved, as
|
|
2304
|
+
// the "work resumed" divider once the session runs again.
|
|
2305
|
+
const liveStatusWait =
|
|
2306
|
+
item.kind === "session-status" &&
|
|
2307
|
+
item.status === "requires_action" &&
|
|
2308
|
+
!item.blocked &&
|
|
2309
|
+
!item.resolvedAt &&
|
|
2310
|
+
current !== undefined &&
|
|
2311
|
+
!current.endedAt;
|
|
2312
|
+
if (!liveApprovalWait && !liveStatusWait) groups.push({ kind: "item", item });
|
|
2313
|
+
if (current && !current.endedAt) {
|
|
2314
|
+
if (
|
|
2315
|
+
item.kind === "notice" &&
|
|
2316
|
+
item.tone === "waiting" &&
|
|
2317
|
+
!item.recordedOutcome &&
|
|
2318
|
+
!item.resolvedAt
|
|
2319
|
+
) {
|
|
2320
|
+
current.waiting = {
|
|
2321
|
+
label: item.text.startsWith("Approval needed") ? "Waiting for you" : "Waiting",
|
|
2322
|
+
since: item.occurredAt,
|
|
2323
|
+
};
|
|
2324
|
+
} else if (
|
|
2325
|
+
item.kind === "session-status" &&
|
|
2326
|
+
item.status === "requires_action" &&
|
|
2327
|
+
!item.blocked &&
|
|
2328
|
+
!item.resolvedAt
|
|
2329
|
+
) {
|
|
2330
|
+
current.waiting = { label: "Waiting for you", since: item.occurredAt };
|
|
2331
|
+
} else if (
|
|
2332
|
+
item.kind === "session-status" &&
|
|
2333
|
+
["paused", "cancelled", "failed"].includes(item.status) &&
|
|
2334
|
+
!item.resolvedAt
|
|
2335
|
+
) {
|
|
2336
|
+
current.endedAt = item.occurredAt;
|
|
2337
|
+
delete current.waiting;
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
if (item.kind === "agent-message" && item.text.trim()) {
|
|
2341
|
+
const prose = messages.get(key) ?? [];
|
|
2342
|
+
prose.push(item);
|
|
2343
|
+
messages.set(key, prose);
|
|
2344
|
+
// A text-only turn still has one overall activity surface. An explicit
|
|
2345
|
+
// final-only response does not manufacture an empty work disclosure.
|
|
2346
|
+
if (
|
|
2347
|
+
(item.phase !== "final_answer" || prose.length > 1) &&
|
|
2348
|
+
(turns.has(key) || !settlements.has(key))
|
|
2349
|
+
)
|
|
2350
|
+
workForTurn();
|
|
2351
|
+
}
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
for (const [key, group] of turns) {
|
|
2355
|
+
const work = group.work!;
|
|
2356
|
+
const live = liveCapacityWaits.get(key);
|
|
2357
|
+
// Re-assert after the loop: a late receipt for the blocked turn must not
|
|
2358
|
+
// turn "Limit reached" back into "Working" while the wait is still open.
|
|
2359
|
+
if (live?.capacityWait && !work.endedAt) {
|
|
2360
|
+
work.waiting = {
|
|
2361
|
+
label: live.capacityWait.label,
|
|
2362
|
+
since: live.occurredAt,
|
|
2363
|
+
detail: live.capacityWait.detail,
|
|
2364
|
+
// Its span is not work and may last until a quota resets: no clock.
|
|
2365
|
+
untimed: true,
|
|
2366
|
+
};
|
|
2367
|
+
}
|
|
2368
|
+
const pausedMs = (capacityPauses.get(key) ?? []).reduce((total, pause) => {
|
|
2369
|
+
if (!pause.endedAt) return total;
|
|
2370
|
+
const start = Math.max(Date.parse(work.startedAt), Date.parse(pause.startedAt));
|
|
2371
|
+
const end = Math.min(
|
|
2372
|
+
work.endedAt ? Date.parse(work.endedAt) : Number.POSITIVE_INFINITY,
|
|
2373
|
+
Date.parse(pause.endedAt),
|
|
2374
|
+
);
|
|
2375
|
+
return Number.isFinite(start) && Number.isFinite(end) && end > start
|
|
2376
|
+
? total + end - start
|
|
2377
|
+
: total;
|
|
2378
|
+
}, 0);
|
|
2379
|
+
if (pausedMs > 0) work.pausedMs = pausedMs;
|
|
2380
|
+
}
|
|
2381
|
+
groups = groups.filter((entry) => {
|
|
2382
|
+
if (entry.kind !== "item" || entry.item.kind !== "context-compaction") return true;
|
|
2383
|
+
const key = compactionTurns.get(entry.item);
|
|
2384
|
+
const work = key === undefined ? undefined : turns.get(key)?.work;
|
|
2385
|
+
if (!work) return true;
|
|
2386
|
+
work.details.push(entry);
|
|
2387
|
+
return false;
|
|
2388
|
+
});
|
|
2389
|
+
// A declared answer starts the response clock immediately. Without a phase,
|
|
2390
|
+
// only settlement identifies the last response; text length never does.
|
|
2391
|
+
const beforeRows = new Map<TimelineGroup, Work>();
|
|
2392
|
+
const afterRows = new Map<TimelineGroup, Work>();
|
|
2393
|
+
const movedRows = new Set<Work>();
|
|
2394
|
+
const foldedProse = new Set<AgentMessageItem>();
|
|
2395
|
+
const positions = new Map<TimelineGroup | TimelineItem, number>();
|
|
2396
|
+
const nextBoundary = new Map<Work, number>();
|
|
2397
|
+
let boundary = groups.length;
|
|
2398
|
+
for (let index = groups.length - 1; index >= 0; index -= 1) {
|
|
2399
|
+
const entry = groups[index]!;
|
|
2400
|
+
positions.set(entry.kind === "item" ? entry.item : entry, index);
|
|
2401
|
+
if (entry.kind === "activity" && entry.work) {
|
|
2402
|
+
nextBoundary.set(entry, boundary);
|
|
2403
|
+
boundary = index;
|
|
2404
|
+
} else if (
|
|
2405
|
+
entry.kind === "item" &&
|
|
2406
|
+
(entry.item.kind === "user-message" || entry.item.kind === "machine-input-batch")
|
|
2407
|
+
) {
|
|
2408
|
+
boundary = index;
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
for (const [key, group] of turns) {
|
|
2412
|
+
const prose = messages.get(key) ?? [];
|
|
2413
|
+
const settledAt = group.work!.endedAt;
|
|
2414
|
+
const responseCandidates = settledAt
|
|
2415
|
+
? prose.filter(
|
|
2416
|
+
(message) => Date.parse(message.startedAt ?? message.occurredAt) <= Date.parse(settledAt),
|
|
2417
|
+
)
|
|
2418
|
+
: prose;
|
|
2419
|
+
const response =
|
|
2420
|
+
responseCandidates.filter((message) => message.phase === "final_answer").at(-1) ??
|
|
2421
|
+
(settledAt
|
|
2422
|
+
? (responseCandidates.at(-1) ??
|
|
2423
|
+
prose.filter((message) => message.phase === "final_answer").at(-1))
|
|
2424
|
+
: undefined);
|
|
2425
|
+
const responses = new Set<AgentMessageItem>(response ? [response] : []);
|
|
2426
|
+
// A turn that answers a person but ends with only commentary (typically by
|
|
2427
|
+
// yielding to a wait) has no single authoritative reply. Its first message
|
|
2428
|
+
// is the immediate answer and its last is the latest word; only the
|
|
2429
|
+
// progress narration between them folds.
|
|
2430
|
+
const firstCandidate = responseCandidates[0];
|
|
2431
|
+
if (
|
|
2432
|
+
settledAt &&
|
|
2433
|
+
firstCandidate &&
|
|
2434
|
+
answersPerson.has(key) &&
|
|
2435
|
+
responseCandidates.every((message) => message.phase === "commentary")
|
|
2436
|
+
) {
|
|
2437
|
+
responses.add(firstCandidate);
|
|
2438
|
+
}
|
|
2439
|
+
if (settledAt) {
|
|
2440
|
+
for (const message of responseCandidates) {
|
|
2441
|
+
// Markdown image syntax also carries retained video/audio previews.
|
|
2442
|
+
// Keep potential primary media visible rather than guessing whether a
|
|
2443
|
+
// partial/reference-style embed can safely disappear into history.
|
|
2444
|
+
if (responses.has(message) || message.text.includes("![")) continue;
|
|
2445
|
+
foldedProse.add(message);
|
|
2446
|
+
group.work!.details.push({ kind: "item", item: message });
|
|
2447
|
+
}
|
|
2448
|
+
} else {
|
|
2449
|
+
// Live progress stays primary above the work row, and is also listed in
|
|
2450
|
+
// the work history so an expanded disclosure reads like the settled one.
|
|
2451
|
+
// The timeline folds the outside copies only while that disclosure is open.
|
|
2452
|
+
for (const message of prose) {
|
|
2453
|
+
if (message.phase === "final_answer" || message.text.includes("![")) continue;
|
|
2454
|
+
group.work!.details.push({ kind: "item", item: message });
|
|
2455
|
+
(group.work!.liveNoteIds ??= []).push(message.id);
|
|
2456
|
+
}
|
|
2457
|
+
}
|
|
2458
|
+
// Preserve event chronology, not completion timestamps or activity kinds.
|
|
2459
|
+
group.work!.details.sort((a, b) => {
|
|
2460
|
+
const first = (entry: TimelineGroup) =>
|
|
2461
|
+
entry.kind === "item"
|
|
2462
|
+
? entry.item.id
|
|
2463
|
+
: entry.kind === "activity"
|
|
2464
|
+
? entry.items[0]?.id
|
|
2465
|
+
: undefined;
|
|
2466
|
+
return (itemOrder.get(first(a) ?? "") ?? 0) - (itemOrder.get(first(b) ?? "") ?? 0);
|
|
2467
|
+
});
|
|
2468
|
+
if (response) {
|
|
2469
|
+
const responseAt = response.startedAt ?? response.occurredAt;
|
|
2470
|
+
// A delayed completion-only receipt cannot extend a settled work clock.
|
|
2471
|
+
group.work!.responseStartedAt =
|
|
2472
|
+
settledAt && Date.parse(responseAt) > Date.parse(settledAt) ? settledAt : responseAt;
|
|
2473
|
+
// A late completion may belong to an older turn. Keep its work at its
|
|
2474
|
+
// original boundary rather than moving it across a newer turn's input.
|
|
2475
|
+
// The work row leads the first visible reply of the turn.
|
|
2476
|
+
const anchor = Math.min(
|
|
2477
|
+
...[...responses].map((message) => positions.get(message) ?? groups.length),
|
|
2478
|
+
);
|
|
2479
|
+
if (settledAt && anchor < (nextBoundary.get(group) ?? groups.length)) {
|
|
2480
|
+
beforeRows.set(groups[anchor]!, group);
|
|
2481
|
+
movedRows.add(group);
|
|
2482
|
+
}
|
|
2483
|
+
}
|
|
2484
|
+
if (!settledAt) {
|
|
2485
|
+
// Keep the same row at the tail of this turn, after every live progress
|
|
2486
|
+
// message and attention surface, without crossing a newer input/turn.
|
|
2487
|
+
// A visible steer is not a turn boundary when later prose still belongs
|
|
2488
|
+
// to this turn. New input without that evidence remains a hard boundary.
|
|
2489
|
+
const lastProseIndex = Math.max(...prose.map((message) => positions.get(message) ?? -1));
|
|
2490
|
+
let tailIndex = groups.length - 1;
|
|
2491
|
+
for (let index = (positions.get(group) ?? 0) + 1; index <= tailIndex; index += 1) {
|
|
2492
|
+
const entry = groups[index]!;
|
|
2493
|
+
if (
|
|
2494
|
+
(entry.kind === "activity" && entry.work) ||
|
|
2495
|
+
(entry.kind === "item" &&
|
|
2496
|
+
(("turnId" in entry.item && entry.item.turnId && entry.item.turnId !== key) ||
|
|
2497
|
+
(index > lastProseIndex &&
|
|
2498
|
+
(entry.item.kind === "user-message" || entry.item.kind === "machine-input-batch"))))
|
|
2499
|
+
) {
|
|
2500
|
+
tailIndex = index - 1;
|
|
2501
|
+
break;
|
|
2502
|
+
}
|
|
2503
|
+
}
|
|
2504
|
+
const tail = groups[tailIndex];
|
|
2505
|
+
if (tail && tail !== group) {
|
|
2506
|
+
afterRows.set(tail, group);
|
|
2507
|
+
movedRows.add(group);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
}
|
|
2511
|
+
// The work of every turn that answers a person stays visible even when it
|
|
2512
|
+
// said nothing; quiet-cycle folding must never take it.
|
|
2513
|
+
const personWork = new Set<TimelineGroup>(
|
|
2514
|
+
[...answersPerson].flatMap((key) => {
|
|
2515
|
+
const work = turns.get(key);
|
|
2516
|
+
return work ? [work] : [];
|
|
2517
|
+
}),
|
|
2518
|
+
);
|
|
2519
|
+
return compactQuietCycles(
|
|
2520
|
+
groups.flatMap((group) => {
|
|
2521
|
+
if (group.kind === "activity" && movedRows.has(group)) return [];
|
|
2522
|
+
const hidden =
|
|
2523
|
+
group.kind === "item" && group.item.kind === "agent-message" && foldedProse.has(group.item);
|
|
2524
|
+
const before = beforeRows.get(group);
|
|
2525
|
+
const after = afterRows.get(group);
|
|
2526
|
+
return [...(before ? [before] : []), ...(hidden ? [] : [group]), ...(after ? [after] : [])];
|
|
2527
|
+
}),
|
|
2528
|
+
personWork,
|
|
2529
|
+
);
|
|
2530
|
+
}
|
|
2531
|
+
|
|
2532
|
+
type WorkGroup = Extract<TimelineGroup, { kind: "activity" }> & {
|
|
2533
|
+
work: NonNullable<Extract<TimelineGroup, { kind: "activity" }>["work"]>;
|
|
2534
|
+
};
|
|
2535
|
+
|
|
2536
|
+
/**
|
|
2537
|
+
* Settled, successful work whose turn left no visible reply, image or blocker.
|
|
2538
|
+
* A failure it recovered from, a failed startup phase or a context compaction
|
|
2539
|
+
* is something to see, so that work is not quiet either.
|
|
2540
|
+
*/
|
|
2541
|
+
function isQuietWork(group: TimelineGroup): group is WorkGroup {
|
|
2542
|
+
if (group.kind !== "activity" || !group.work?.endedAt || group.work.waiting) return false;
|
|
2543
|
+
if (group.work.cycles || (group.outcome && group.outcome !== "complete")) return false;
|
|
2544
|
+
if (
|
|
2545
|
+
group.items.some(
|
|
2546
|
+
(item) =>
|
|
2547
|
+
item.kind === "startup-phase" && (item.status === "failed" || item.status === "cancelled"),
|
|
2548
|
+
)
|
|
2549
|
+
) {
|
|
2550
|
+
return false;
|
|
2551
|
+
}
|
|
2552
|
+
const quiet = (entry: TimelineGroup): boolean =>
|
|
2553
|
+
entry.kind === "item"
|
|
2554
|
+
? entry.item.kind !== "auth-needed" &&
|
|
2555
|
+
entry.item.kind !== "context-compaction" &&
|
|
2556
|
+
entry.item.kind !== "human-input" &&
|
|
2557
|
+
!(entry.item.kind === "notice" && entry.item.tone !== "waiting") &&
|
|
2558
|
+
!(entry.item.kind === "agent-message" && entry.item.text.includes("!["))
|
|
2559
|
+
: entry.kind === "activity"
|
|
2560
|
+
? isQuietDetail(entry, quiet)
|
|
2561
|
+
: false;
|
|
2562
|
+
return !publishesOutput(group) && group.work.details.every(quiet);
|
|
2563
|
+
}
|
|
2564
|
+
|
|
2565
|
+
function isQuietDetail(
|
|
2566
|
+
group: Extract<TimelineGroup, { kind: "activity" }>,
|
|
2567
|
+
quiet: (entry: TimelineGroup) => boolean,
|
|
2568
|
+
): boolean {
|
|
2569
|
+
return (
|
|
2570
|
+
(!group.outcome || group.outcome === "complete") && (group.work?.details ?? []).every(quiet)
|
|
2571
|
+
);
|
|
2572
|
+
}
|
|
2573
|
+
|
|
2574
|
+
/**
|
|
2575
|
+
* Work that generated an image or published a file produced output worth
|
|
2576
|
+
* seeing. Checked by tool name only, so the projection stays free of the
|
|
2577
|
+
* renderer's image presentation rules.
|
|
2578
|
+
*/
|
|
2579
|
+
function publishesOutput(group: TimelineGroup): boolean {
|
|
2580
|
+
if (group.kind !== "activity") return false;
|
|
2581
|
+
return (
|
|
2582
|
+
group.items.some((item) => {
|
|
2583
|
+
if (item.kind !== "tool-call") return false;
|
|
2584
|
+
const name = mcpToolLeaf(item.name);
|
|
2585
|
+
return (
|
|
2586
|
+
name === "generate_image" ||
|
|
2587
|
+
name === "image_generation_call" ||
|
|
2588
|
+
name === "sandbox_file_publish"
|
|
2589
|
+
);
|
|
2590
|
+
}) || (group.work?.details ?? []).some(publishesOutput)
|
|
2591
|
+
);
|
|
2592
|
+
}
|
|
2593
|
+
|
|
2594
|
+
/**
|
|
2595
|
+
* Whether the work at `index` follows a person's message or answer with only
|
|
2596
|
+
* routine input in between. Complements the turn bookkeeping for legacy rows.
|
|
2597
|
+
*/
|
|
2598
|
+
function answersPersonAt(groups: TimelineGroup[], index: number): boolean {
|
|
2599
|
+
for (let cursor = index - 1; cursor >= 0; cursor -= 1) {
|
|
2600
|
+
const group = groups[cursor]!;
|
|
2601
|
+
if (
|
|
2602
|
+
group.kind === "item" &&
|
|
2603
|
+
(group.item.kind === "user-message" || group.item.kind === "human-input")
|
|
2604
|
+
) {
|
|
2605
|
+
return true;
|
|
2606
|
+
}
|
|
2607
|
+
if (!isRoutineInput(group)) return false;
|
|
2608
|
+
}
|
|
2609
|
+
return false;
|
|
2610
|
+
}
|
|
2611
|
+
|
|
2612
|
+
/**
|
|
2613
|
+
* A routine machine input (agent update, child result, wait timeout, ...). An
|
|
2614
|
+
* agent that failed, paused or needs action is news, not routine.
|
|
2615
|
+
*/
|
|
2616
|
+
function isRoutineInput(group: TimelineGroup): boolean {
|
|
2617
|
+
return (
|
|
2618
|
+
group.kind === "item" &&
|
|
2619
|
+
group.item.kind === "machine-input-batch" &&
|
|
2620
|
+
!!group.item.compact &&
|
|
2621
|
+
group.item.members.every(
|
|
2622
|
+
(member) =>
|
|
2623
|
+
member.kind !== "child_paused" &&
|
|
2624
|
+
member.classification !== "failure" &&
|
|
2625
|
+
// A parent's direction is always recorded as action_required for the
|
|
2626
|
+
// agent; it is routine orchestration, not something a person must do.
|
|
2627
|
+
(member.classification !== "action_required" || member.kind === "agent_steer_instruction"),
|
|
2628
|
+
)
|
|
2629
|
+
);
|
|
2630
|
+
}
|
|
2631
|
+
|
|
2632
|
+
/** A recorded wait that later input already ended. */
|
|
2633
|
+
function isFinishedWait(group: TimelineGroup): group is Extract<TimelineGroup, { kind: "item" }> & {
|
|
2634
|
+
item: NoticeItem;
|
|
2635
|
+
} {
|
|
2636
|
+
return (
|
|
2637
|
+
group.kind === "item" &&
|
|
2638
|
+
group.item.kind === "notice" &&
|
|
2639
|
+
group.item.tone === "waiting" &&
|
|
2640
|
+
!!group.item.recordedOutcome &&
|
|
2641
|
+
!!group.item.waitEndedAt
|
|
2642
|
+
);
|
|
2643
|
+
}
|
|
2644
|
+
|
|
2645
|
+
/**
|
|
2646
|
+
* Long orchestration runs repeat "agent update / worked / waited" many times
|
|
2647
|
+
* without saying anything. Fold two or more such consecutive quiet cycles into
|
|
2648
|
+
* one work row: "7 updates over 6h 12m", with the latest wait reason under it.
|
|
2649
|
+
* Expanding it shows the original rows unchanged. Visible replies, people's
|
|
2650
|
+
* messages, failures, approvals, live work and the current wait are never folded
|
|
2651
|
+
* and end a run.
|
|
2652
|
+
*/
|
|
2653
|
+
function compactQuietCycles(
|
|
2654
|
+
groups: TimelineGroup[],
|
|
2655
|
+
personWork: ReadonlySet<TimelineGroup>,
|
|
2656
|
+
): TimelineGroup[] {
|
|
2657
|
+
const result: TimelineGroup[] = [];
|
|
2658
|
+
let index = 0;
|
|
2659
|
+
while (index < groups.length) {
|
|
2660
|
+
// A run starts at routine input, never at a leading wait (it belongs to
|
|
2661
|
+
// the reply before it) or at bare work (it belongs to the visible row
|
|
2662
|
+
// before it, such as an agent's failure). Later cycles may lack input,
|
|
2663
|
+
// for example after a wait timeout.
|
|
2664
|
+
let end = index;
|
|
2665
|
+
let lastCycleEnd = index;
|
|
2666
|
+
let cycles = 0;
|
|
2667
|
+
while (end < groups.length) {
|
|
2668
|
+
const input = isRoutineInput(groups[end]!) ? end : -1;
|
|
2669
|
+
if (input < 0 && cycles === 0) break;
|
|
2670
|
+
const workIndex = input >= 0 ? end + 1 : end;
|
|
2671
|
+
const work = groups[workIndex];
|
|
2672
|
+
if (!work || !isQuietWork(work)) break;
|
|
2673
|
+
// Work that follows a person's message is that message's turn, even
|
|
2674
|
+
// when it said nothing; it stays visible.
|
|
2675
|
+
if (personWork.has(work) || answersPersonAt(groups, workIndex)) break;
|
|
2676
|
+
const next = groups[workIndex + 1];
|
|
2677
|
+
// Work directly followed by its reply is not quiet.
|
|
2678
|
+
if (next?.kind === "item" && next.item.kind === "agent-message") break;
|
|
2679
|
+
if (!next || !isFinishedWait(next)) break;
|
|
2680
|
+
cycles += 1;
|
|
2681
|
+
end = workIndex + 2;
|
|
2682
|
+
lastCycleEnd = end;
|
|
2683
|
+
}
|
|
2684
|
+
if (cycles < 2) {
|
|
2685
|
+
result.push(groups[index]!);
|
|
2686
|
+
index += 1;
|
|
2687
|
+
continue;
|
|
2688
|
+
}
|
|
2689
|
+
const run = groups.slice(index, lastCycleEnd);
|
|
2690
|
+
const works = run.filter((group): group is WorkGroup => isQuietWork(group));
|
|
2691
|
+
const waits = run.filter(isFinishedWait);
|
|
2692
|
+
const first = works[0]!;
|
|
2693
|
+
const lastWait = waits.at(-1)!;
|
|
2694
|
+
result.push({
|
|
2695
|
+
kind: "activity",
|
|
2696
|
+
id: `cycles-${first.id}`,
|
|
2697
|
+
items: works.flatMap((group) => group.items),
|
|
2698
|
+
outcome: "complete",
|
|
2699
|
+
work: {
|
|
2700
|
+
startedAt: first.work.startedAt,
|
|
2701
|
+
endedAt: lastWait.item.waitEndedAt!,
|
|
2702
|
+
details: run,
|
|
2703
|
+
cycles: {
|
|
2704
|
+
count: cycles,
|
|
2705
|
+
...(lastWait.item.text.trim() ? { summary: lastWait.item.text.trim() } : {}),
|
|
2706
|
+
},
|
|
2707
|
+
},
|
|
2708
|
+
});
|
|
2709
|
+
index = lastCycleEnd;
|
|
2710
|
+
}
|
|
2711
|
+
return result;
|
|
2712
|
+
}
|
|
2713
|
+
|
|
2714
|
+
/** Machine inputs that continue the current exchange rather than start one. */
|
|
2715
|
+
const ROUTINE_MACHINE_INPUT_KINDS: ReadonlySet<MachineInputMember["kind"]> = new Set([
|
|
2716
|
+
"goal_continuation",
|
|
2717
|
+
"background_command_result",
|
|
2718
|
+
"session_wait_timeout",
|
|
2719
|
+
"agent_message",
|
|
2720
|
+
"agent_steer_instruction",
|
|
2721
|
+
"child_terminal_result",
|
|
2722
|
+
"child_requires_action_resolved",
|
|
2723
|
+
"child_paused",
|
|
2724
|
+
"child_waiting_capacity",
|
|
2725
|
+
"child_progress",
|
|
2726
|
+
]);
|
|
2727
|
+
|
|
1358
2728
|
/* --- helpers ---------------------------------------------------------------- */
|
|
1359
2729
|
|
|
1360
2730
|
type TurnAnchorPrescan = {
|
|
@@ -1366,6 +2736,22 @@ type TurnAnchorPrescan = {
|
|
|
1366
2736
|
startedTurnIds: Set<string>;
|
|
1367
2737
|
};
|
|
1368
2738
|
|
|
2739
|
+
/** Navigation uses the same queue/start/withdrawal rules as transcript ordering. */
|
|
2740
|
+
export function timelineQuestionPlacement(question: SessionEvent, evidence: SessionEvent[]) {
|
|
2741
|
+
const anchors = prescanTurnAnchors([question, ...evidence]);
|
|
2742
|
+
const direct = anchors.directChatSequenceByTrigger.get(question.id);
|
|
2743
|
+
if (direct !== undefined) return { kind: "visible" as const, sequence: direct };
|
|
2744
|
+
if (anchors.cancelledBeforeStartTriggers.has(question.id)) return { kind: "withdrawn" as const };
|
|
2745
|
+
const start = anchors.startSeqByTrigger.get(question.id);
|
|
2746
|
+
if (start !== undefined) return { kind: "visible" as const, sequence: start };
|
|
2747
|
+
if (
|
|
2748
|
+
anchors.queuedTurnByTrigger.has(question.id) ||
|
|
2749
|
+
anchors.explicitQueuedTriggers.has(question.id)
|
|
2750
|
+
)
|
|
2751
|
+
return { kind: "pending" as const };
|
|
2752
|
+
return { kind: "visible" as const, sequence: question.sequence };
|
|
2753
|
+
}
|
|
2754
|
+
|
|
1369
2755
|
function prescanTurnAnchors(events: SessionEvent[]): TurnAnchorPrescan {
|
|
1370
2756
|
const ordered = [...events].sort((a, b) => a.sequence - b.sequence);
|
|
1371
2757
|
const queuedTurnByTrigger = new Map<string, string>();
|
|
@@ -1554,7 +2940,12 @@ function pushInsertion(
|
|
|
1554
2940
|
}
|
|
1555
2941
|
|
|
1556
2942
|
function isAgentActivityEvent(type: string): boolean {
|
|
1557
|
-
return
|
|
2943
|
+
return (
|
|
2944
|
+
type.startsWith("agent.") ||
|
|
2945
|
+
type.startsWith("sandbox.") ||
|
|
2946
|
+
type === "subscription.account.selected" ||
|
|
2947
|
+
type === "subscription.account.switched"
|
|
2948
|
+
);
|
|
1558
2949
|
}
|
|
1559
2950
|
|
|
1560
2951
|
/**
|
|
@@ -1564,11 +2955,15 @@ function isAgentActivityEvent(type: string): boolean {
|
|
|
1564
2955
|
* steering, or deleting a waiting row must never make its prompt appear in the
|
|
1565
2956
|
* transcript as though inference had begun.
|
|
1566
2957
|
*/
|
|
1567
|
-
function isTurnExecutionEvidence(type: string): boolean {
|
|
2958
|
+
export function isTurnExecutionEvidence(type: string): boolean {
|
|
1568
2959
|
return (
|
|
1569
2960
|
isAgentActivityEvent(type) ||
|
|
1570
2961
|
type === "turn.completed" ||
|
|
1571
2962
|
type === "turn.failed" ||
|
|
2963
|
+
// An admission refusal can settle a queued turn before it ever starts:
|
|
2964
|
+
// its prompt still belongs above the "usage limit reached" row.
|
|
2965
|
+
type === "usage.exhausted" ||
|
|
2966
|
+
type === "turn.superseded" ||
|
|
1572
2967
|
type === "turn.recovery.requested" ||
|
|
1573
2968
|
type === "turn.capacity_waiting" ||
|
|
1574
2969
|
type === "session.requiresAction" ||
|
|
@@ -1597,6 +2992,35 @@ function turnEndItem(
|
|
|
1597
2992
|
};
|
|
1598
2993
|
}
|
|
1599
2994
|
|
|
2995
|
+
/** Plain text for a usage-limit stop whose marker carried no readable amounts. */
|
|
2996
|
+
const SESSION_USAGE_LIMIT_STOP_TEXT =
|
|
2997
|
+
"This task's usage limit is reached. A person can raise or remove the limit to continue.";
|
|
2998
|
+
|
|
2999
|
+
/**
|
|
3000
|
+
* The stop row for a `segmentLimit: "session_usage_limit"` completion. Only the
|
|
3001
|
+
* typed amounts are kept (never upstream prose), and a malformed marker still
|
|
3002
|
+
* reads as the same neutral stop instead of a credit or failure notice.
|
|
3003
|
+
*/
|
|
3004
|
+
function sessionUsageLimitNotice(
|
|
3005
|
+
event: SessionEvent,
|
|
3006
|
+
payload: Record<string, unknown>,
|
|
3007
|
+
): NoticeItem {
|
|
3008
|
+
const refusal = parseSessionUsageLimitReachedRefusal(payload);
|
|
3009
|
+
return {
|
|
3010
|
+
kind: "notice",
|
|
3011
|
+
id: `${event.id}-session-usage-limit`,
|
|
3012
|
+
// Neutral: not a failure, and not an open wait the turn header should claim.
|
|
3013
|
+
tone: "cancelled",
|
|
3014
|
+
text: refusal ? sessionUsageLimitReachedMessage(refusal) : SESSION_USAGE_LIMIT_STOP_TEXT,
|
|
3015
|
+
sessionUsageLimit: {
|
|
3016
|
+
rootSessionId: refusal?.rootSessionId ?? null,
|
|
3017
|
+
limitMicros: refusal?.limitMicros ?? null,
|
|
3018
|
+
usedMicros: refusal?.usedMicros ?? null,
|
|
3019
|
+
},
|
|
3020
|
+
occurredAt: event.occurredAt,
|
|
3021
|
+
};
|
|
3022
|
+
}
|
|
3023
|
+
|
|
1600
3024
|
function hasTurnActivity(items: TimelineItem[], turnId: string | null): boolean {
|
|
1601
3025
|
if (turnId) {
|
|
1602
3026
|
return items.some((item) => isActivityItem(item) && item.turnId === turnId);
|
|
@@ -1678,14 +3102,58 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
|
|
|
1678
3102
|
if (collected.length === 0) {
|
|
1679
3103
|
return;
|
|
1680
3104
|
}
|
|
1681
|
-
|
|
1682
|
-
|
|
3105
|
+
const collectedLength = collected.length;
|
|
3106
|
+
|
|
3107
|
+
// Resolve only inside this turn's collected suffix. A user/machine-input
|
|
3108
|
+
// boundary or an interleaved foreign turn must never donate its answer.
|
|
3109
|
+
const waitMessages = turnEnd.preserveWaitResponse
|
|
3110
|
+
? collected.filter(
|
|
3111
|
+
(group): group is Extract<TimelineGroup, { kind: "item" }> =>
|
|
3112
|
+
group.kind === "item" &&
|
|
3113
|
+
group.item.kind === "agent-message" &&
|
|
3114
|
+
!group.item.streaming &&
|
|
3115
|
+
group.item.text.trim().length > 0 &&
|
|
3116
|
+
belongsToTurn(group.item, turnEnd.turnId),
|
|
3117
|
+
)
|
|
3118
|
+
: [];
|
|
3119
|
+
// A completed answer outranks later commentary. Otherwise use the latest
|
|
3120
|
+
// visible existing prose, ignoring whitespace and opaque-only stream tails.
|
|
3121
|
+
const waitResponse =
|
|
3122
|
+
waitMessages
|
|
3123
|
+
.slice()
|
|
3124
|
+
.reverse()
|
|
3125
|
+
.find(
|
|
3126
|
+
(group) =>
|
|
3127
|
+
group.item.kind === "agent-message" &&
|
|
3128
|
+
group.item.phase !== "commentary" &&
|
|
3129
|
+
group.item.annotationSource?.eventType === "agent.message.completed",
|
|
3130
|
+
) ?? waitMessages.at(-1);
|
|
3131
|
+
const finalMessage = waitResponse ?? extractFinalAgentMessage(collected, turnEnd);
|
|
1683
3132
|
const fallbackMessage =
|
|
1684
3133
|
finalMessage || hasOrdinaryFinalAgentMessage(collected, turnEnd)
|
|
1685
3134
|
? null
|
|
1686
3135
|
: extractLatestCompletedCommentary(collected, turnEnd);
|
|
1687
3136
|
const visibleMessage = finalMessage ?? fallbackMessage;
|
|
1688
|
-
const
|
|
3137
|
+
const visibleMessages = visibleMessage ? [visibleMessage] : [];
|
|
3138
|
+
// A turn that answers a person but declares no final answer (typically one
|
|
3139
|
+
// that yields to a wait) keeps its first message visible beside the latest:
|
|
3140
|
+
// that is the immediate answer, not progress narration.
|
|
3141
|
+
if (
|
|
3142
|
+
visibleMessage &&
|
|
3143
|
+
!hasOrdinaryFinalAgentMessage(collected, turnEnd) &&
|
|
3144
|
+
turnAnswersPerson(groups, startIndex)
|
|
3145
|
+
) {
|
|
3146
|
+
const first = collected.find(
|
|
3147
|
+
(group): group is Extract<TimelineGroup, { kind: "item" }> =>
|
|
3148
|
+
group.kind === "item" &&
|
|
3149
|
+
group.item.kind === "agent-message" &&
|
|
3150
|
+
!group.item.streaming &&
|
|
3151
|
+
group.item.text.trim().length > 0 &&
|
|
3152
|
+
belongsToTurn(group.item, turnEnd.turnId),
|
|
3153
|
+
);
|
|
3154
|
+
if (first && first !== visibleMessage) visibleMessages.unshift(first);
|
|
3155
|
+
}
|
|
3156
|
+
const body = collected.filter((group) => !visibleMessages.includes(group as never));
|
|
1689
3157
|
if (body.length === 0) {
|
|
1690
3158
|
return;
|
|
1691
3159
|
}
|
|
@@ -1711,11 +3179,25 @@ function foldSettledTurn(groups: TimelineGroup[], turnEnd: TurnEndItem): void {
|
|
|
1711
3179
|
turnGroup.failureText = turnEnd.failureText;
|
|
1712
3180
|
}
|
|
1713
3181
|
|
|
1714
|
-
groups.splice(
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
3182
|
+
groups.splice(startIndex, collectedLength, turnGroup, ...visibleMessages);
|
|
3183
|
+
}
|
|
3184
|
+
|
|
3185
|
+
/** Whether the input boundary before a turn includes a person's message. */
|
|
3186
|
+
function turnAnswersPerson(groups: TimelineGroup[], startIndex: number): boolean {
|
|
3187
|
+
for (let index = startIndex - 1; index >= 0; index -= 1) {
|
|
3188
|
+
const group = groups[index];
|
|
3189
|
+
if (group?.kind !== "item") return false;
|
|
3190
|
+
if (group.item.kind === "user-message") return true;
|
|
3191
|
+
if (
|
|
3192
|
+
group.item.kind !== "machine-input-batch" &&
|
|
3193
|
+
group.item.kind !== "session-status" &&
|
|
3194
|
+
group.item.kind !== "context-compaction" &&
|
|
3195
|
+
!(group.item.kind === "notice" && group.item.tone === "input")
|
|
3196
|
+
) {
|
|
3197
|
+
return false;
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
return false;
|
|
1719
3201
|
}
|
|
1720
3202
|
|
|
1721
3203
|
function isTurnBoundary(group: TimelineGroup | undefined): boolean {
|
|
@@ -1725,8 +3207,7 @@ function isTurnBoundary(group: TimelineGroup | undefined): boolean {
|
|
|
1725
3207
|
(group.item.kind === "user-message" ||
|
|
1726
3208
|
group.item.kind === "human-input" ||
|
|
1727
3209
|
group.item.kind === "context-compaction" ||
|
|
1728
|
-
|
|
1729
|
-
group.item.members.some((member) => member.kind === "media_generation_result")) ||
|
|
3210
|
+
group.item.kind === "machine-input-batch" ||
|
|
1730
3211
|
(group.item.kind === "notice" && group.item.tone === "input")))
|
|
1731
3212
|
);
|
|
1732
3213
|
}
|
|
@@ -1838,6 +3319,29 @@ function numberOrNull(value: unknown): number | null {
|
|
|
1838
3319
|
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
1839
3320
|
}
|
|
1840
3321
|
|
|
3322
|
+
/**
|
|
3323
|
+
* Read the closed provider-rejection record a `summarization_failed` skip
|
|
3324
|
+
* carries. Only bounded identifiers are accepted; a provider message is never
|
|
3325
|
+
* part of the payload and would be ignored here anyway.
|
|
3326
|
+
*/
|
|
3327
|
+
function compactionProviderRejection(
|
|
3328
|
+
payload: Record<string, unknown>,
|
|
3329
|
+
): ContextCompactionItem["providerRejection"] {
|
|
3330
|
+
const record = asRecord(payload.providerRejection);
|
|
3331
|
+
if (typeof record.httpStatus !== "number" || !Number.isFinite(record.httpStatus)) {
|
|
3332
|
+
return null;
|
|
3333
|
+
}
|
|
3334
|
+
const text = (value: unknown): string | null =>
|
|
3335
|
+
typeof value === "string" && value.trim() ? value : null;
|
|
3336
|
+
return {
|
|
3337
|
+
httpStatus: record.httpStatus,
|
|
3338
|
+
type: text(record.type),
|
|
3339
|
+
code: text(record.code),
|
|
3340
|
+
param: text(record.param),
|
|
3341
|
+
requestId: text(record.requestId),
|
|
3342
|
+
};
|
|
3343
|
+
}
|
|
3344
|
+
|
|
1841
3345
|
function compactionTrigger(payload: Record<string, unknown>): ContextCompactionItem["trigger"] {
|
|
1842
3346
|
const trigger = payload.trigger;
|
|
1843
3347
|
return trigger === "auto" ||
|
|
@@ -1893,6 +3397,8 @@ function machineInputMembers(value: unknown): MachineInputBatchItem["members"] {
|
|
|
1893
3397
|
const kinds = new Set<MachineInputBatchItem["members"][number]["kind"]>([
|
|
1894
3398
|
"scheduled_occurrence",
|
|
1895
3399
|
"goal_continuation",
|
|
3400
|
+
"background_command_result",
|
|
3401
|
+
"session_wait_timeout",
|
|
1896
3402
|
"agent_message",
|
|
1897
3403
|
"agent_steer_instruction",
|
|
1898
3404
|
"child_terminal_result",
|
|
@@ -1948,6 +3454,8 @@ const SESSION_STATUSES: readonly SessionStatus[] = [
|
|
|
1948
3454
|
"running",
|
|
1949
3455
|
"idle",
|
|
1950
3456
|
"requires_action",
|
|
3457
|
+
"recovering",
|
|
3458
|
+
"waiting_capacity",
|
|
1951
3459
|
"failed",
|
|
1952
3460
|
"cancelled",
|
|
1953
3461
|
];
|
|
@@ -1973,6 +3481,15 @@ function resourceRefs(value: unknown): import("@opengeni/sdk").ResourceRef[] {
|
|
|
1973
3481
|
});
|
|
1974
3482
|
}
|
|
1975
3483
|
|
|
3484
|
+
/** The person behind a message, when its initiator is a signed-in subject. */
|
|
3485
|
+
function messageSender(value: unknown): import("./types").MessageSender | undefined {
|
|
3486
|
+
const record = asRecord(value);
|
|
3487
|
+
if (record.kind !== "subject" || typeof record.subjectId !== "string" || !record.subjectId)
|
|
3488
|
+
return undefined;
|
|
3489
|
+
const label = typeof record.label === "string" && record.label.trim() ? record.label : null;
|
|
3490
|
+
return { subjectId: record.subjectId, label };
|
|
3491
|
+
}
|
|
3492
|
+
|
|
1976
3493
|
function timelineAnnotations(value: unknown): TimelineAnnotation[] {
|
|
1977
3494
|
if (!Array.isArray(value)) return [];
|
|
1978
3495
|
return value.filter((candidate): candidate is TimelineAnnotation => {
|
|
@@ -2091,6 +3608,10 @@ function findOpenCall(
|
|
|
2091
3608
|
if (byId) {
|
|
2092
3609
|
return byId as ToolCallItem | WorkerItem;
|
|
2093
3610
|
}
|
|
3611
|
+
// An explicit provider correlation id is authoritative. Falling back to
|
|
3612
|
+
// an unrelated running call corrupts its status/output when compact pages
|
|
3613
|
+
// or interleaved providers deliver an unmatched historical result.
|
|
3614
|
+
return undefined;
|
|
2094
3615
|
}
|
|
2095
3616
|
return reversed.find(
|
|
2096
3617
|
(item): item is ToolCallItem | WorkerItem => isCall(item) && item.status === "running",
|
|
@@ -2116,6 +3637,34 @@ function startupPhaseFromPayload(value: unknown): StartupPhase | null {
|
|
|
2116
3637
|
: null;
|
|
2117
3638
|
}
|
|
2118
3639
|
|
|
3640
|
+
function startupLifecycleWaitReason(
|
|
3641
|
+
payload: Record<string, unknown>,
|
|
3642
|
+
): StartupPhaseItem["blockedReason"] {
|
|
3643
|
+
// The first model request waited for context compaction: an expected step
|
|
3644
|
+
// of preparation that reopens after compacting, never a failure.
|
|
3645
|
+
if (
|
|
3646
|
+
payload.expectedTransition === true &&
|
|
3647
|
+
payload.failureStage === "context_compaction" &&
|
|
3648
|
+
payload.failureCategory === "context_compaction" &&
|
|
3649
|
+
payload.failureCode === "compaction_required"
|
|
3650
|
+
) {
|
|
3651
|
+
return "compaction_required";
|
|
3652
|
+
}
|
|
3653
|
+
if (
|
|
3654
|
+
payload.expectedTransition !== true ||
|
|
3655
|
+
payload.failureStage !== "lifecycle_wait" ||
|
|
3656
|
+
payload.failureCategory !== "drain_capture_wait"
|
|
3657
|
+
) {
|
|
3658
|
+
return undefined;
|
|
3659
|
+
}
|
|
3660
|
+
const code = payload.failureCode;
|
|
3661
|
+
return code === "capture_in_progress" ||
|
|
3662
|
+
code === "rotation_in_progress" ||
|
|
3663
|
+
code === "provider_recovery_in_progress"
|
|
3664
|
+
? code
|
|
3665
|
+
: undefined;
|
|
3666
|
+
}
|
|
3667
|
+
|
|
2119
3668
|
const SANDBOX_STARTUP_PHASES: Record<string, StartupPhase> = {
|
|
2120
3669
|
"sandbox.provision": "sandbox",
|
|
2121
3670
|
"repository-clone": "repository",
|
|
@@ -2139,15 +3688,7 @@ function elapsedDurationMs(startedAt: string, completedAt: string): number | nul
|
|
|
2139
3688
|
}
|
|
2140
3689
|
|
|
2141
3690
|
function failureMessage(payload: Record<string, unknown>): string | null {
|
|
2142
|
-
|
|
2143
|
-
const value = payload[key];
|
|
2144
|
-
if (typeof value === "string" && value.trim().length > 0) {
|
|
2145
|
-
// Auth/quota provider errors are rewritten for the right audience
|
|
2146
|
-
// (raw text remains in the event payload for debug surfaces).
|
|
2147
|
-
return humanizeFailureReason(value);
|
|
2148
|
-
}
|
|
2149
|
-
}
|
|
2150
|
-
return null;
|
|
3691
|
+
return presentFailure(payload).reason;
|
|
2151
3692
|
}
|
|
2152
3693
|
|
|
2153
3694
|
function goalText(payload: Record<string, unknown>): string | null {
|
|
@@ -2164,11 +3705,108 @@ function goalText(payload: Record<string, unknown>): string | null {
|
|
|
2164
3705
|
return null;
|
|
2165
3706
|
}
|
|
2166
3707
|
|
|
3708
|
+
function rememberPendingWaitOutcome(
|
|
3709
|
+
pending: Map<string | null, PendingWaitOutcome>,
|
|
3710
|
+
event: SessionEvent,
|
|
3711
|
+
payload: Record<string, unknown>,
|
|
3712
|
+
eventTurnId: string | null,
|
|
3713
|
+
): void {
|
|
3714
|
+
const reason = stringValue(payload.reason).trim();
|
|
3715
|
+
if (!reason) return;
|
|
3716
|
+
const turnId =
|
|
3717
|
+
eventTurnId || stringValue(payload.waitTurnId) || stringValue(payload.turnId) || null;
|
|
3718
|
+
pending.set(turnId, { id: event.id, reason, occurredAt: event.occurredAt });
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3721
|
+
/** Wait reasons that need a person or a policy change, not only time. */
|
|
3722
|
+
const ACTION_CAPACITY_WAIT_REASONS: ReadonlySet<string> = new Set([
|
|
3723
|
+
"pinned_account_disabled",
|
|
3724
|
+
"pinned_account_ineligible",
|
|
3725
|
+
"model_not_allowed",
|
|
3726
|
+
// Only sending the work again ends it (design 5.3 decision 4).
|
|
3727
|
+
"accepted_authority_unavailable",
|
|
3728
|
+
]);
|
|
3729
|
+
|
|
3730
|
+
/**
|
|
3731
|
+
* Recorded wait codes, for any subscription provider, that a person must
|
|
3732
|
+
* resolve: every account disabled for allocation, or the serving account
|
|
3733
|
+
* needing reconnection or refusing the request. None of them is a limit.
|
|
3734
|
+
*/
|
|
3735
|
+
const ACTION_CAPACITY_WAIT_CODE = /_(?:allocator_disabled|relogin_required|account_forbidden)$/;
|
|
3736
|
+
|
|
3737
|
+
/**
|
|
3738
|
+
* A quota or capacity wait reads as "Usage limit reached" with one plain line
|
|
3739
|
+
* of guidance. It never promises that the work continues by itself: the
|
|
3740
|
+
* reader decides whether to wait for the quota to reset or to switch. Only a
|
|
3741
|
+
* wait that a person must resolve (reconnect, re-enable, choose another
|
|
3742
|
+
* account) keeps its recorded, actionable reason and is not labelled a limit.
|
|
3743
|
+
*/
|
|
3744
|
+
function capacityWaitPresentation(
|
|
3745
|
+
type: string,
|
|
3746
|
+
payload: Record<string, unknown>,
|
|
3747
|
+
turnId: string | null,
|
|
3748
|
+
): { turnId: string | null; label: string; detail: string; text: string } {
|
|
3749
|
+
if (
|
|
3750
|
+
ACTION_CAPACITY_WAIT_REASONS.has(stringValue(payload.waitReason)) ||
|
|
3751
|
+
ACTION_CAPACITY_WAIT_CODE.test(stringValue(payload.code))
|
|
3752
|
+
) {
|
|
3753
|
+
// Claude/SuperGrok waits record the readable sentence as `error` and an
|
|
3754
|
+
// internal note as `detail`; Codex waits keep their recorded order.
|
|
3755
|
+
const [first, second] =
|
|
3756
|
+
type === "turn.capacity_waiting"
|
|
3757
|
+
? [payload.error, payload.detail]
|
|
3758
|
+
: [payload.detail, payload.error];
|
|
3759
|
+
const detail = stringValue(first) || stringValue(second) || "Waiting for an account.";
|
|
3760
|
+
return {
|
|
3761
|
+
turnId,
|
|
3762
|
+
label:
|
|
3763
|
+
stringValue(payload.waitReason) === "pinned_account_disabled" ? "Needs you" : "Waiting",
|
|
3764
|
+
detail,
|
|
3765
|
+
text: detail,
|
|
3766
|
+
};
|
|
3767
|
+
}
|
|
3768
|
+
return {
|
|
3769
|
+
turnId,
|
|
3770
|
+
label: CAPACITY_LIMIT_LABEL,
|
|
3771
|
+
detail: CAPACITY_LIMIT_DETAIL,
|
|
3772
|
+
text: `${CAPACITY_LIMIT_LABEL}. ${CAPACITY_LIMIT_DETAIL}`,
|
|
3773
|
+
};
|
|
3774
|
+
}
|
|
3775
|
+
|
|
3776
|
+
const CAPACITY_LIMIT_LABEL = "Usage limit reached";
|
|
3777
|
+
const CAPACITY_LIMIT_DETAIL =
|
|
3778
|
+
"Try again after your quota resets, or switch to another model or provider.";
|
|
3779
|
+
|
|
3780
|
+
/** Lifecycle evidence that a capacity wait is over. */
|
|
3781
|
+
function endsCapacityWait(event: SessionEvent, payload: Record<string, unknown>): boolean {
|
|
3782
|
+
if (event.duplicateOfEventId || (event.turnAssociation && event.turnAssociation !== "current"))
|
|
3783
|
+
return false;
|
|
3784
|
+
switch (event.type) {
|
|
3785
|
+
case "codex.capacity.resumed":
|
|
3786
|
+
case "codex.capacity.superseded":
|
|
3787
|
+
case "turn.completed":
|
|
3788
|
+
case "turn.failed":
|
|
3789
|
+
case "turn.cancelled":
|
|
3790
|
+
case "turn.superseded":
|
|
3791
|
+
return true;
|
|
3792
|
+
case "session.status.changed":
|
|
3793
|
+
return isSessionStatus(payload.status) && payload.status !== "waiting_capacity";
|
|
3794
|
+
default:
|
|
3795
|
+
return false;
|
|
3796
|
+
}
|
|
3797
|
+
}
|
|
3798
|
+
|
|
3799
|
+
function waitingOutcomeText(reason: string): string {
|
|
3800
|
+
return /^waiting\b/i.test(reason) ? reason : `Waiting: ${reason}`;
|
|
3801
|
+
}
|
|
3802
|
+
|
|
2167
3803
|
/**
|
|
2168
3804
|
* Agent-owned goal mutations already have an in-cluster tool row. Suppress the
|
|
2169
|
-
* breakaway landmark for those only.
|
|
2170
|
-
* and
|
|
2171
|
-
*
|
|
3805
|
+
* breakaway landmark for those only. Empty wait turns retain the durable hold
|
|
3806
|
+
* reason and surface it after the folded steps. `goal.completed` has no actor
|
|
3807
|
+
* field today and is only emitted by the agent tool, so it is always
|
|
3808
|
+
* suppressed. API / system / create-session / continuation landmarks stay
|
|
3809
|
+
* visible.
|
|
2172
3810
|
*/
|
|
2173
3811
|
function shouldSuppressAgentGoalLandmark(type: string, payload: Record<string, unknown>): boolean {
|
|
2174
3812
|
if (type === "goal.completed") {
|
|
@@ -2231,6 +3869,7 @@ const AUTH_NEEDED_REASONS: ReadonlySet<string> = new Set([
|
|
|
2231
3869
|
"personal_authority_unavailable",
|
|
2232
3870
|
"unsupported_auth",
|
|
2233
3871
|
"resource_scope_unavailable",
|
|
3872
|
+
"designated_credential_unavailable",
|
|
2234
3873
|
]);
|
|
2235
3874
|
|
|
2236
3875
|
function authNeededReason(value: unknown): AuthNeededItem["reason"] {
|
|
@@ -2250,7 +3889,7 @@ function capabilityAuthorizationRequest(
|
|
|
2250
3889
|
if (
|
|
2251
3890
|
typeof record.id !== "string" ||
|
|
2252
3891
|
typeof record.name !== "string" ||
|
|
2253
|
-
!["
|
|
3892
|
+
!["mcp", "api", "skill", "plugin"].includes(String(kind)) ||
|
|
2254
3893
|
!["built_in", "library", "configured", "public_registry", "registry", "manual"].includes(
|
|
2255
3894
|
String(source),
|
|
2256
3895
|
) ||
|
|
@@ -2270,12 +3909,30 @@ function capabilityAuthorizationRequest(
|
|
|
2270
3909
|
};
|
|
2271
3910
|
}
|
|
2272
3911
|
|
|
3912
|
+
function customMcpSetupRequest(value: unknown): NonNullable<AuthNeededItem["setupRequest"]> | null {
|
|
3913
|
+
return parseCustomMcpSetupRequest(value);
|
|
3914
|
+
}
|
|
3915
|
+
|
|
2273
3916
|
function stringList(value: unknown): string[] {
|
|
2274
3917
|
return Array.isArray(value)
|
|
2275
3918
|
? value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
|
|
2276
3919
|
: [];
|
|
2277
3920
|
}
|
|
2278
3921
|
|
|
3922
|
+
/**
|
|
3923
|
+
* Some providers stream each reasoning summary part as its own delta
|
|
3924
|
+
* ("**Checking the workspace**", then "**Inspecting guidance**"). Joined
|
|
3925
|
+
* as-is the parts run together and their bold markers collide ("****"), so a
|
|
3926
|
+
* new part that opens bold right after one that closed bold starts a new
|
|
3927
|
+
* paragraph. Ordinary token deltas are joined unchanged. The worker's
|
|
3928
|
+
* write-time merge and the read-side coalescer apply the same rule
|
|
3929
|
+
* (`reasoningDeltaSeparator` in @opengeni/events), so stored, coalesced and
|
|
3930
|
+
* live fragments render identically.
|
|
3931
|
+
*/
|
|
3932
|
+
function reasoningDeltaJoiner(previous: string, next: string): string {
|
|
3933
|
+
return previous.endsWith("**") && next.startsWith("**") ? "\n\n" : "";
|
|
3934
|
+
}
|
|
3935
|
+
|
|
2279
3936
|
function reasoningText(payload: unknown): string {
|
|
2280
3937
|
const record = asRecord(payload);
|
|
2281
3938
|
if (typeof record.text === "string") {
|
|
@@ -2305,6 +3962,19 @@ function workerPrompt(args: unknown): string | null {
|
|
|
2305
3962
|
return null;
|
|
2306
3963
|
}
|
|
2307
3964
|
|
|
3965
|
+
const WORKER_TITLE_MAX_LENGTH = 120;
|
|
3966
|
+
|
|
3967
|
+
/** The optional `session_create` title, as one bounded display line. */
|
|
3968
|
+
function workerTitle(args: unknown): string | null {
|
|
3969
|
+
const record = asRecord(typeof args === "string" ? tryParseJson(args) : args);
|
|
3970
|
+
if (typeof record.title !== "string") return null;
|
|
3971
|
+
const title = record.title.replace(/\s+/g, " ").trim();
|
|
3972
|
+
if (!title) return null;
|
|
3973
|
+
return title.length > WORKER_TITLE_MAX_LENGTH
|
|
3974
|
+
? `${title.slice(0, WORKER_TITLE_MAX_LENGTH - 1).trimEnd()}…`
|
|
3975
|
+
: title;
|
|
3976
|
+
}
|
|
3977
|
+
|
|
2308
3978
|
function boundedWorkerFailureMessage(value: string): string | null {
|
|
2309
3979
|
const normalized = value.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
|
|
2310
3980
|
if (!normalized) return null;
|