@opengeni/react 1.0.1 → 1.0.2-canary.37202341988001
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 +660 -21
- package/dist/accounts.d.ts +64 -0
- package/dist/accounts.js +822 -0
- package/dist/accounts.js.map +1 -0
- package/dist/approvals.d.ts +2 -1
- package/dist/artifacts-document.js +3 -2
- package/dist/artifacts-presentation.js +3 -2
- package/dist/artifacts-spreadsheet.js +2 -2
- package/dist/artifacts.d.ts +4 -1
- package/dist/artifacts.js +518 -42
- package/dist/artifacts.js.map +1 -1
- package/dist/browser-viewer-SHYKQRNX.js +16 -0
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/capability-logo-fallback.d.ts +3 -0
- package/dist/chat-interactive-block-UGBTNB3L.js +11 -0
- package/dist/chat-interactive-block-UGBTNB3L.js.map +1 -0
- package/dist/chunk-26ZNDDLZ.js +453 -0
- package/dist/chunk-26ZNDDLZ.js.map +1 -0
- package/dist/chunk-3CTRFMAK.js +359 -0
- package/dist/chunk-3CTRFMAK.js.map +1 -0
- package/dist/chunk-447JLCBU.js +209 -0
- package/dist/chunk-447JLCBU.js.map +1 -0
- package/dist/chunk-4RVYOY4O.js +379 -0
- package/dist/chunk-4RVYOY4O.js.map +1 -0
- package/dist/chunk-5CVXZA3Y.js +292 -0
- package/dist/chunk-5CVXZA3Y.js.map +1 -0
- package/dist/chunk-5J4FOW6Q.js +108 -0
- package/dist/chunk-5J4FOW6Q.js.map +1 -0
- package/dist/chunk-742CZ3L3.js +481 -0
- package/dist/chunk-742CZ3L3.js.map +1 -0
- package/dist/chunk-A2PI6UMY.js +41 -0
- package/dist/chunk-A2PI6UMY.js.map +1 -0
- package/dist/{chunk-7WCLAEFV.js → chunk-A4UJB2MM.js} +37 -27
- package/dist/chunk-A4UJB2MM.js.map +1 -0
- package/dist/chunk-B3V6TG77.js +41 -0
- package/dist/chunk-B3V6TG77.js.map +1 -0
- package/dist/chunk-BRM6FVZA.js +302 -0
- package/dist/chunk-BRM6FVZA.js.map +1 -0
- package/dist/chunk-C5X5AJXK.js +182 -0
- package/dist/chunk-C5X5AJXK.js.map +1 -0
- package/dist/{chunk-6KLX2RH5.js → chunk-DO544OSV.js} +1075 -79
- package/dist/chunk-DO544OSV.js.map +1 -0
- package/dist/{chunk-R3AZOAB5.js → chunk-DYWBUX5Q.js} +29 -123
- package/dist/chunk-DYWBUX5Q.js.map +1 -0
- package/dist/{chunk-HA46CYQK.js → chunk-EESD3PY3.js} +792 -537
- package/dist/chunk-EESD3PY3.js.map +1 -0
- package/dist/{chunk-5UQPNLIO.js → chunk-EN3YTHKR.js} +4117 -3896
- package/dist/chunk-EN3YTHKR.js.map +1 -0
- package/dist/chunk-ERGYJIBB.js +21 -0
- package/dist/chunk-ERGYJIBB.js.map +1 -0
- package/dist/chunk-FZCP527Y.js +116 -0
- package/dist/chunk-FZCP527Y.js.map +1 -0
- package/dist/chunk-GIMRELBW.js +14 -0
- package/dist/chunk-GIMRELBW.js.map +1 -0
- package/dist/chunk-GYVRFZDY.js +2733 -0
- package/dist/chunk-GYVRFZDY.js.map +1 -0
- package/dist/{chunk-LLOLUHVG.js → chunk-HDJWDK7W.js} +1358 -2746
- package/dist/chunk-HDJWDK7W.js.map +1 -0
- package/dist/chunk-I7IWRQLN.js +1608 -0
- package/dist/chunk-I7IWRQLN.js.map +1 -0
- package/dist/chunk-IT4BJUGF.js +13055 -0
- package/dist/chunk-IT4BJUGF.js.map +1 -0
- package/dist/chunk-IWIDV7SB.js +1 -0
- package/dist/chunk-IWIDV7SB.js.map +1 -0
- package/dist/chunk-J2QWVSQY.js +312 -0
- package/dist/chunk-J2QWVSQY.js.map +1 -0
- package/dist/chunk-J6DBS4T3.js +371 -0
- package/dist/chunk-J6DBS4T3.js.map +1 -0
- package/dist/{chunk-B6WPZSTT.js → chunk-JIQYUGVL.js} +1331 -1163
- package/dist/chunk-JIQYUGVL.js.map +1 -0
- package/dist/chunk-JT4JW7BC.js +54 -0
- package/dist/chunk-JT4JW7BC.js.map +1 -0
- package/dist/chunk-K3HQAKQX.js +24 -0
- package/dist/chunk-K3HQAKQX.js.map +1 -0
- package/dist/{chunk-PEI6RANI.js → chunk-PNY3JKBJ.js} +309 -74
- package/dist/chunk-PNY3JKBJ.js.map +1 -0
- package/dist/chunk-RHREZ7BO.js +11 -0
- package/dist/chunk-RHREZ7BO.js.map +1 -0
- package/dist/chunk-RLUSC2WA.js +299 -0
- package/dist/chunk-RLUSC2WA.js.map +1 -0
- package/dist/{chunk-IGC6A2ZI.js → chunk-RSI7XYXL.js} +777 -246
- package/dist/chunk-RSI7XYXL.js.map +1 -0
- package/dist/chunk-SCOTPE2I.js +589 -0
- package/dist/chunk-SCOTPE2I.js.map +1 -0
- package/dist/{chunk-C5ZPSSS5.js → chunk-TKOTKRWY.js} +5 -4
- package/dist/{chunk-C5ZPSSS5.js.map → chunk-TKOTKRWY.js.map} +1 -1
- package/dist/chunk-TMYGL6X5.js +742 -0
- package/dist/chunk-TMYGL6X5.js.map +1 -0
- package/dist/chunk-UGLEYEMP.js +33 -0
- package/dist/chunk-UGLEYEMP.js.map +1 -0
- package/dist/chunk-UK4TINKT.js +27 -0
- package/dist/chunk-UK4TINKT.js.map +1 -0
- package/dist/chunk-UTRTG6LH.js +220 -0
- package/dist/chunk-UTRTG6LH.js.map +1 -0
- package/dist/chunk-WEHMPOXK.js +39 -0
- package/dist/chunk-WEHMPOXK.js.map +1 -0
- package/dist/chunk-WGXVEJO5.js +319 -0
- package/dist/chunk-WGXVEJO5.js.map +1 -0
- package/dist/chunk-WRHIZYZF.js +988 -0
- package/dist/chunk-WRHIZYZF.js.map +1 -0
- package/dist/{chunk-HUIKIDM5.js → chunk-WUISZ3WU.js} +223 -115
- package/dist/chunk-WUISZ3WU.js.map +1 -0
- package/dist/chunk-XCHWDJ22.js +107 -0
- package/dist/chunk-XCHWDJ22.js.map +1 -0
- package/dist/{chunk-KCGXPP5V.js → chunk-Y2PRMYLC.js} +11 -6
- package/dist/chunk-Y2PRMYLC.js.map +1 -0
- package/dist/chunk-YBXQDQFX.js +1772 -0
- package/dist/chunk-YBXQDQFX.js.map +1 -0
- package/dist/chunk-YM2DE7BQ.js +3124 -0
- package/dist/chunk-YM2DE7BQ.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-YQEAA6LT.js} +7 -3
- package/dist/chunk-YQEAA6LT.js.map +1 -0
- package/dist/chunk-YV4L3ILF.js +9 -0
- package/dist/chunk-YV4L3ILF.js.map +1 -0
- package/dist/chunk-ZYRGEKWS.js +216 -0
- package/dist/chunk-ZYRGEKWS.js.map +1 -0
- package/dist/client.d.ts +7 -4
- package/dist/clipboard.js +1 -0
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/anthropic-mark.d.ts +3 -0
- package/dist/components/approval-surface.d.ts +15 -0
- package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
- package/dist/components/artifacts/artifact-sandbox.d.ts +27 -0
- package/dist/components/artifacts/artifact-surface.d.ts +3 -1
- 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 +46 -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 +2 -1
- package/dist/components/artifacts/index.d.ts +8 -1
- 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 +48 -3
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +23 -0
- package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -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 +10 -1
- package/dist/components/chat-composer.d.ts +21 -3
- package/dist/components/child-session-link.d.ts +5 -0
- package/dist/components/claude-mark.d.ts +3 -0
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/composer.d.ts +45 -4
- package/dist/components/computer-viewer.d.ts +4 -1
- package/dist/components/copy-button.d.ts +3 -1
- package/dist/components/desktop-viewer.d.ts +1 -1
- package/dist/components/enrollment-consent.d.ts +6 -3
- package/dist/components/file-browser.d.ts +10 -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 +21 -2
- package/dist/components/human-input-surface.d.ts +6 -3
- package/dist/components/machine-card.d.ts +4 -1
- package/dist/components/machines/machine-detail.d.ts +4 -1
- package/dist/components/machines/machine-operation-policy.d.ts +11 -0
- package/dist/components/machines-dashboard.d.ts +3 -1
- package/dist/components/markdown-table-layout.d.ts +12 -0
- package/dist/components/markdown.d.ts +47 -3
- package/dist/components/message-timeline.d.ts +72 -9
- 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 +62 -13
- package/dist/components/open-geni-chat.d.ts +58 -0
- package/dist/components/open-geni-links.d.ts +68 -0
- package/dist/components/openrouter-mark.d.ts +6 -0
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/preview-loading.d.ts +2 -0
- package/dist/components/queue-item-content.d.ts +9 -0
- package/dist/components/sandbox-files.d.ts +13 -2
- package/dist/components/sandbox-workspace.d.ts +43 -3
- package/dist/components/session-capability-frame.d.ts +35 -0
- package/dist/components/session-chrome.d.ts +66 -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 +89 -0
- package/dist/components/session-list.d.ts +36 -0
- package/dist/components/session-mcp-capability-card.d.ts +41 -0
- package/dist/components/session-proxy-scope.d.ts +21 -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/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/components/workspace-dock.d.ts +6 -2
- package/dist/composer.d.ts +5 -4
- package/dist/composer.js +15 -9
- package/dist/computer-viewer-6SRGWRUX.js +19 -0
- package/dist/computer-viewer-6SRGWRUX.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 +16 -0
- package/dist/connect.d.ts +21 -0
- package/dist/connect.js +2129 -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 +26 -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 +14 -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 +21 -0
- package/dist/editor.js.map +1 -0
- package/dist/embedded-session-client.d.ts +23 -0
- package/dist/file-node-visibility.d.ts +15 -0
- package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-YZ3ZKWMM.js} +2 -1
- package/dist/fleet-decision-row-YZ3ZKWMM.js.map +1 -0
- package/dist/hooks/channel-cache.d.ts +23 -0
- package/dist/hooks/latest-question.d.ts +25 -0
- package/dist/hooks/shared-machines-list.d.ts +14 -0
- package/dist/hooks/use-attached-browsers.d.ts +2 -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-channels.d.ts +1 -0
- package/dist/hooks/use-codex-accounts.d.ts +25 -7
- package/dist/hooks/use-composer.d.ts +45 -9
- 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 +2 -0
- package/dist/hooks/use-connect.d.ts +22 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +21 -2
- package/dist/hooks/use-machine-chip.d.ts +3 -3
- package/dist/hooks/use-machines.d.ts +19 -1
- package/dist/hooks/use-sandbox-files.d.ts +5 -1
- package/dist/hooks/use-session-background-commands.d.ts +14 -0
- package/dist/hooks/use-session-events.d.ts +30 -8
- package/dist/hooks/use-session-lineage.d.ts +4 -0
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session.d.ts +6 -0
- package/dist/hooks/use-turn-queue.d.ts +12 -0
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/hooks/use-workspace-edit.d.ts +4 -0
- package/dist/hooks/use-workspace-sessions.d.ts +27 -5
- package/dist/human-input.d.ts +3 -1
- package/dist/identity-link-accounts.d.ts +11 -0
- package/dist/identity-link-consent.d.ts +18 -0
- package/dist/index.d.ts +60 -17
- package/dist/index.js +2238 -6201
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +35 -184
- package/dist/interaction.js.map +1 -1
- package/dist/latest-question-HTRIS4KJ.js +117 -0
- package/dist/latest-question-HTRIS4KJ.js.map +1 -0
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +36 -2
- package/dist/lib/host-theme.d.ts +70 -0
- package/dist/lib/interaction-errors.d.ts +6 -0
- package/dist/lib/menu-styles.d.ts +22 -0
- package/dist/lib/pierre-diffs-loader.d.ts +20 -0
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/lib/xterm-theme.d.ts +7 -5
- package/dist/machines.d.ts +4 -2
- package/dist/machines.js +14 -4
- package/dist/markdown-table-layout-QXSLK4BY.js +106 -0
- package/dist/markdown-table-layout-QXSLK4BY.js.map +1 -0
- package/dist/mcp-connection-status.d.ts +14 -0
- package/dist/model-policy-picker-menu-M54ISBMN.js +14 -0
- package/dist/model-policy-picker-menu-M54ISBMN.js.map +1 -0
- package/dist/model-policy.d.ts +19 -3
- package/dist/model-policy.js +9 -1
- package/dist/older-history.d.ts +30 -0
- package/dist/platform-activity-row-627BFZ46.js +182 -0
- package/dist/platform-activity-row-627BFZ46.js.map +1 -0
- package/dist/plugin-details.d.ts +14 -0
- package/dist/plugin-discovery.d.ts +25 -0
- package/dist/provider.d.ts +13 -1
- package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-ZHFI5JBP.js} +131 -44
- package/dist/queue-surface-implementation-ZHFI5JBP.js.map +1 -0
- package/dist/realtime/realtime-control.d.ts +6 -3
- package/dist/realtime.js +133 -74
- package/dist/realtime.js.map +1 -1
- package/dist/sandbox-files-GORZOUUJ.js +20 -0
- package/dist/sandbox-files-GORZOUUJ.js.map +1 -0
- package/dist/session-auth-recommendation.d.ts +5 -0
- package/dist/session-capability-policy.d.ts +12 -0
- package/dist/session-ui.d.ts +30 -2
- package/dist/session-ui.js +85 -7
- package/dist/session-ui.js.map +1 -1
- package/dist/session.d.ts +8 -2
- package/dist/session.js +30 -19
- package/dist/sites-ui.d.ts +34 -0
- package/dist/sites.d.ts +3 -0
- package/dist/sites.js +11 -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 +31 -0
- package/dist/terminal.js.map +1 -0
- package/dist/timeline/activity-rail.d.ts +11 -2
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/compaction-copy.d.ts +10 -0
- package/dist/timeline/entrance.d.ts +13 -1
- package/dist/timeline/genie-loading.d.ts +34 -0
- package/dist/timeline/index.d.ts +6 -2
- package/dist/timeline/knowledge-receipt.d.ts +17 -0
- package/dist/timeline/platform-activity-row.d.ts +14 -0
- package/dist/timeline/presented-image.d.ts +6 -0
- package/dist/timeline/projection.d.ts +31 -2
- package/dist/timeline/registry.d.ts +2 -0
- package/dist/timeline/retained-image.d.ts +18 -0
- package/dist/timeline/rolling-activity.d.ts +11 -0
- package/dist/timeline/screenshot-lightbox.d.ts +16 -1
- package/dist/timeline/shared.d.ts +8 -3
- 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 +2 -1
- package/dist/timeline/turn-summary.d.ts +34 -3
- package/dist/timeline/types.d.ts +147 -7
- package/dist/timeline-annotation-markers-GNDHTGBD.js +165 -0
- package/dist/timeline-annotation-markers-GNDHTGBD.js.map +1 -0
- package/dist/timeline-annotation-selection-DMLMM2TX.js +241 -0
- package/dist/timeline-annotation-selection-DMLMM2TX.js.map +1 -0
- package/dist/timeline-annotations-dialog-ELRDBHTS.js +273 -0
- package/dist/timeline-annotations-dialog-ELRDBHTS.js.map +1 -0
- package/dist/types/machines.d.ts +2 -2
- package/dist/usage/allowance-copy.d.ts +25 -0
- package/dist/usage/summary.d.ts +35 -0
- package/dist/usage.d.ts +13 -0
- package/dist/usage.js +1074 -0
- package/dist/usage.js.map +1 -0
- package/package.json +44 -4
- package/src/accounts.tsx +1072 -0
- package/src/approvals.ts +8 -1
- package/src/artifacts.ts +42 -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 +22 -13
- 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 +171 -37
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +185 -0
- package/src/components/artifacts/artifact-surface.tsx +39 -19
- 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 +187 -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 +5 -2
- package/src/components/artifacts/index.ts +44 -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 +338 -5
- package/src/components/artifacts/session-artifact-viewer.tsx +376 -0
- package/src/components/artifacts/site-view.tsx +116 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
- package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +12 -13
- 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 +1054 -314
- package/src/components/chat-composer.tsx +131 -85
- package/src/components/child-session-link.tsx +27 -0
- package/src/components/claude-mark.tsx +10 -0
- package/src/components/code-editor.tsx +13 -16
- package/src/components/composer-transcription-control.tsx +7 -5
- package/src/components/composer.tsx +376 -49
- package/src/components/computer-viewer.tsx +1041 -329
- package/src/components/copy-button.tsx +13 -5
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/enrollment-consent.tsx +65 -7
- package/src/components/enrollment-device-flow.tsx +1 -1
- package/src/components/file-browser.tsx +158 -34
- package/src/components/fleet-tile.tsx +8 -16
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/grok-mark.tsx +33 -0
- package/src/components/human-input-form.tsx +455 -114
- package/src/components/human-input-surface.tsx +18 -7
- package/src/components/interaction-intervention-banner.tsx +2 -2
- package/src/components/machine-card.tsx +119 -4
- package/src/components/machine-dock-bar.tsx +1 -1
- package/src/components/machine-input-display.ts +23 -2
- package/src/components/machines/machine-detail.tsx +52 -2
- package/src/components/machines/machine-operation-policy.tsx +179 -0
- package/src/components/machines-dashboard.tsx +8 -17
- package/src/components/markdown-table-layout.ts +132 -0
- package/src/components/markdown.tsx +473 -44
- package/src/components/message-timeline.tsx +2754 -665
- package/src/components/model-mark.tsx +90 -0
- package/src/components/model-picker.tsx +15 -4
- package/src/components/model-policy-picker-menu.tsx +424 -0
- package/src/components/model-policy-picker.tsx +337 -381
- package/src/components/open-geni-chat.tsx +338 -0
- package/src/components/open-geni-links.tsx +179 -0
- package/src/components/openrouter-mark.tsx +13 -0
- package/src/components/pierre-diff.tsx +41 -16
- package/src/components/pierre-file.tsx +36 -11
- package/src/components/preview-loading.tsx +154 -0
- package/src/components/queue-item-content.ts +14 -0
- package/src/components/queue-surface-implementation.tsx +132 -42
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/sandbox-files.tsx +267 -35
- package/src/components/sandbox-terminal.tsx +48 -11
- package/src/components/sandbox-workspace.tsx +337 -135
- package/src/components/session-capability-frame.tsx +192 -0
- package/src/components/session-chrome.tsx +1027 -328
- 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 +526 -0
- package/src/components/session-list.tsx +250 -0
- package/src/components/session-mcp-capability-card.tsx +680 -0
- package/src/components/session-proxy-scope.tsx +92 -0
- package/src/components/session-status.tsx +1 -1
- 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 +70 -7
- 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 +194 -49
- package/src/components/workspace-dock.tsx +141 -175
- 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 +307 -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 +235 -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/embedded-session-client.ts +104 -0
- package/src/file-node-visibility.ts +66 -0
- package/src/hooks/channel-cache.ts +104 -0
- package/src/hooks/internal.ts +3 -2
- package/src/hooks/latest-question.ts +164 -0
- package/src/hooks/shared-machines-list.ts +239 -0
- package/src/hooks/use-attached-browsers.ts +5 -1
- package/src/hooks/use-available-models.ts +20 -14
- package/src/hooks/use-browser-frame-stream.ts +25 -3
- package/src/hooks/use-browser-session.ts +271 -71
- package/src/hooks/use-browser-sessions.ts +7 -1
- package/src/hooks/use-channels.ts +32 -10
- package/src/hooks/use-codex-accounts.ts +78 -21
- package/src/hooks/use-composer.ts +1034 -320
- package/src/hooks/use-computer-frame-stream.ts +39 -16
- package/src/hooks/use-computer-input-posture.ts +94 -0
- package/src/hooks/use-computer-session.ts +110 -32
- package/src/hooks/use-computer-sessions.ts +8 -1
- package/src/hooks/use-connect.ts +23 -0
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-file-attachments.ts +118 -10
- package/src/hooks/use-goal.ts +36 -9
- package/src/hooks/use-human-input.ts +31 -4
- package/src/hooks/use-last-started-turn-policy.ts +15 -13
- package/src/hooks/use-machine-chip.ts +23 -6
- package/src/hooks/use-machines.ts +104 -6
- package/src/hooks/use-sandbox-files.ts +253 -150
- package/src/hooks/use-sandbox-git.ts +58 -29
- package/src/hooks/use-session-background-commands.ts +67 -0
- package/src/hooks/use-session-capabilities.ts +21 -2
- package/src/hooks/use-session-events.ts +759 -433
- package/src/hooks/use-session-lineage.ts +28 -6
- package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
- package/src/hooks/use-session.ts +75 -16
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-turn-queue.ts +113 -13
- package/src/hooks/use-usage.ts +100 -0
- package/src/hooks/use-video-artifact-playback.ts +1 -1
- package/src/hooks/use-voice-input.ts +12 -1
- 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 +99 -11
- package/src/human-input.ts +12 -1
- package/src/identity-link-accounts.tsx +148 -0
- package/src/identity-link-consent.tsx +192 -0
- package/src/index.ts +157 -12
- package/src/interaction.ts +1 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +150 -14
- package/src/lib/host-theme.ts +511 -0
- package/src/lib/interaction-errors.ts +33 -0
- package/src/lib/menu-styles.ts +44 -0
- package/src/lib/pierre-diffs-loader.ts +53 -0
- package/src/lib/workbench-peers.ts +62 -0
- package/src/lib/xterm-theme.ts +23 -21
- package/src/machines.ts +9 -1
- package/src/mcp-connection-status.ts +35 -0
- package/src/model-policy.ts +223 -69
- package/src/older-history.ts +87 -0
- package/src/plugin-details.tsx +185 -0
- package/src/plugin-discovery.tsx +196 -0
- package/src/provider.tsx +55 -14
- package/src/realtime/dropdown-menu.tsx +19 -12
- package/src/realtime/realtime-control.tsx +133 -46
- package/src/session-auth-recommendation.ts +48 -0
- package/src/session-capability-policy.ts +83 -0
- package/src/session-context.ts +4 -3
- package/src/session-ui.ts +66 -2
- package/src/session.ts +18 -1
- package/src/sites-ui.tsx +402 -0
- package/src/sites.ts +3 -0
- package/src/skill-discovery.tsx +208 -0
- package/src/terminal.ts +26 -0
- package/src/timeline/activity-rail.tsx +171 -80
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/compaction-copy.ts +42 -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/genie-loading.tsx +136 -0
- package/src/timeline/index.ts +11 -0
- package/src/timeline/knowledge-receipt.tsx +100 -0
- package/src/timeline/parsers.ts +2 -2
- package/src/timeline/platform-activity-row.tsx +216 -0
- package/src/timeline/presented-image.ts +49 -0
- package/src/timeline/projection.ts +1609 -84
- package/src/timeline/registry.ts +15 -3
- package/src/timeline/retained-image.ts +97 -0
- package/src/timeline/rolling-activity.tsx +92 -0
- package/src/timeline/screenshot-lightbox.tsx +115 -17
- package/src/timeline/shared.tsx +47 -15
- package/src/timeline/startup-preference.ts +33 -0
- package/src/timeline/startup-timings.tsx +145 -0
- package/src/timeline/tool-display-name.ts +8 -6
- package/src/timeline/tool-renderers.tsx +497 -128
- package/src/timeline/turn-summary.tsx +218 -44
- package/src/timeline/types.ts +192 -7
- package/src/types/machines.ts +10 -0
- package/src/usage/allowance-copy.ts +64 -0
- package/src/usage/summary.ts +93 -0
- package/src/usage.ts +50 -0
- package/styles/compiled.css +2098 -757
- package/styles/connect.css +284 -0
- package/styles/connect.d.ts +1 -0
- package/styles/effective-tokens.css +1 -0
- package/styles/index.css +201 -9
- package/styles/responsive.css +27 -1
- package/styles/session-capability.css +44 -0
- package/styles/tokens.css +135 -65
- package/dist/chunk-5UQPNLIO.js.map +0 -1
- package/dist/chunk-6KLX2RH5.js.map +0 -1
- package/dist/chunk-7WCLAEFV.js.map +0 -1
- package/dist/chunk-B6WPZSTT.js.map +0 -1
- package/dist/chunk-FK6VG4LL.js +0 -90
- package/dist/chunk-FK6VG4LL.js.map +0 -1
- package/dist/chunk-HA46CYQK.js.map +0 -1
- package/dist/chunk-HUIKIDM5.js.map +0 -1
- package/dist/chunk-IGC6A2ZI.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-LLOLUHVG.js.map +0 -1
- package/dist/chunk-MKPIAGVL.js +0 -465
- package/dist/chunk-MKPIAGVL.js.map +0 -1
- package/dist/chunk-PEI6RANI.js.map +0 -1
- package/dist/chunk-R3AZOAB5.js.map +0 -1
- package/dist/chunk-WSK7G5LE.js +0 -170
- package/dist/chunk-WSK7G5LE.js.map +0 -1
- package/dist/chunk-XBZWOM2K.js +0 -218
- package/dist/chunk-XBZWOM2K.js.map +0 -1
- package/dist/chunk-Y7OEDUJ5.js +0 -8810
- package/dist/chunk-Y7OEDUJ5.js.map +0 -1
- package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
- package/dist/hooks/use-packs.d.ts +0 -32
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
- package/dist/queue-surface-implementation-2KVJTYXH.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-packs.ts +0 -157
- /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-SHYKQRNX.js.map} +0 -0
|
@@ -4,17 +4,38 @@ import {
|
|
|
4
4
|
type DraftTimelineAnnotation,
|
|
5
5
|
type EffectiveControlResumeOption,
|
|
6
6
|
type EffectiveSessionControl,
|
|
7
|
-
type
|
|
7
|
+
type LatencyMode,
|
|
8
|
+
OpenGeniApiError,
|
|
9
|
+
type ReasoningEffort,
|
|
8
10
|
type ResourceRef,
|
|
9
11
|
type SaveComposerDraftRequest,
|
|
10
12
|
type SendMessageInput,
|
|
11
13
|
type SessionEvent,
|
|
14
|
+
type SubmitComposerDraftResponse,
|
|
12
15
|
} from "@opengeni/sdk";
|
|
13
16
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
14
17
|
import { useEmbeddedSession, type EmbeddedSessionClientOverride } from "../session-context";
|
|
18
|
+
import {
|
|
19
|
+
COMPOSER_MODEL_UNAVAILABLE_MESSAGE,
|
|
20
|
+
ComposerReconciliationRequiredError,
|
|
21
|
+
ComposerStateError,
|
|
22
|
+
ComposerWorkspaceControlUnavailableError,
|
|
23
|
+
composerSubmissionCanRetry,
|
|
24
|
+
composerSubmissionErrorMessage,
|
|
25
|
+
} from "../lib/format";
|
|
26
|
+
import { normalizeError, useErrorMessage } from "../lib/error-message";
|
|
15
27
|
import { useSessionEventTrigger, type SessionEventFeedOptions } from "./internal";
|
|
16
28
|
|
|
17
|
-
export type
|
|
29
|
+
export type ComposerPolicy = {
|
|
30
|
+
model: string;
|
|
31
|
+
reasoningEffort: ReasoningEffort;
|
|
32
|
+
latencyMode: LatencyMode;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ComposerSendExtras = Omit<
|
|
36
|
+
SendMessageInput,
|
|
37
|
+
"text" | "clientEventId" | "annotations" | "model" | "reasoningEffort" | "latencyMode"
|
|
38
|
+
>;
|
|
18
39
|
|
|
19
40
|
export type UseComposerOptions = EmbeddedSessionClientOverride &
|
|
20
41
|
SessionEventFeedOptions & {
|
|
@@ -22,10 +43,13 @@ export type UseComposerOptions = EmbeddedSessionClientOverride &
|
|
|
22
43
|
onSubmitted?: ((text: string, input: SendMessageInput) => void) | undefined;
|
|
23
44
|
/** Called with the exact accepted wire input after a successful send. */
|
|
24
45
|
onSent?: ((text: string, input: SendMessageInput) => void) | undefined;
|
|
46
|
+
/** Called with the exact wire input after a delivery failure. */
|
|
47
|
+
onDeliveryError?:
|
|
48
|
+
| ((error: Error, input: SendMessageInput, delivery: "send" | "steer") => void)
|
|
49
|
+
| undefined;
|
|
25
50
|
/**
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* surrounding UI state (attachment pickers, model selectors, ...).
|
|
51
|
+
* Non-policy message fields merged into every send. Durable policy belongs
|
|
52
|
+
* to this composer draft; attachments and credentials may remain host-owned.
|
|
29
53
|
*/
|
|
30
54
|
sendExtras?: ComposerSendExtras | (() => ComposerSendExtras) | undefined;
|
|
31
55
|
/**
|
|
@@ -36,16 +60,19 @@ export type UseComposerOptions = EmbeddedSessionClientOverride &
|
|
|
36
60
|
sendBlocked?: (() => boolean) | undefined;
|
|
37
61
|
/** Latest server-derived workstream control; bound into Send/Steer OCC. */
|
|
38
62
|
effectiveControl?: EffectiveSessionControl | null | undefined;
|
|
39
|
-
/**
|
|
40
|
-
|
|
63
|
+
/** Immediate user-visible destination for an ordinary Send before admission replies. */
|
|
64
|
+
sendDestination?: (() => "chat" | "queue") | undefined;
|
|
41
65
|
/** Disable remote composer-draft reads and writes for embedded hosts. */
|
|
42
66
|
draftPersistence?: "durable" | "disabled" | undefined;
|
|
67
|
+
/** Required explicit authority when durable draft persistence is disabled. */
|
|
68
|
+
initialPolicy?: ComposerPolicy | undefined;
|
|
43
69
|
};
|
|
44
70
|
|
|
45
71
|
type ComposerDraftShadow = {
|
|
46
72
|
text: string;
|
|
47
73
|
resources: ResourceRef[];
|
|
48
74
|
annotations: DraftTimelineAnnotation[];
|
|
75
|
+
policy?: ComposerPolicy | undefined;
|
|
49
76
|
};
|
|
50
77
|
|
|
51
78
|
type PendingComposerOperation = {
|
|
@@ -68,12 +95,21 @@ type StoredPendingComposerOperation = Omit<PendingComposerOperation, "input" | "
|
|
|
68
95
|
|
|
69
96
|
export type ComposerOptimisticMessage = {
|
|
70
97
|
clientEventId: string;
|
|
98
|
+
delivery: "send" | "steer";
|
|
99
|
+
destination: "chat" | "queue";
|
|
71
100
|
text: string;
|
|
72
101
|
annotations: DraftTimelineAnnotation[];
|
|
73
102
|
resources: ResourceRef[];
|
|
74
103
|
occurredAt: string;
|
|
75
104
|
state: "sending" | "queued" | "failed";
|
|
105
|
+
turnId?: string | null | undefined;
|
|
106
|
+
triggerEventId?: string | null | undefined;
|
|
107
|
+
appliedQueueVersion?: number | null | undefined;
|
|
76
108
|
error?: string | undefined;
|
|
109
|
+
/** Bounded support reference retained independently of display copy across remounts. */
|
|
110
|
+
correlationId?: string | undefined;
|
|
111
|
+
/** False for a definitive refusal that requires changing payment or allowance settings. */
|
|
112
|
+
retryable?: boolean | undefined;
|
|
77
113
|
outcomeUnknown?: boolean | undefined;
|
|
78
114
|
};
|
|
79
115
|
|
|
@@ -92,7 +128,6 @@ type StoredOptimisticSendOperation = Omit<OptimisticSendOperation, "input" | "ca
|
|
|
92
128
|
|
|
93
129
|
const PENDING_COMPOSER_STORAGE_PREFIX = "opengeni.pending-composer.v1:";
|
|
94
130
|
const OPTIMISTIC_SEND_STORAGE_PREFIX = "opengeni.optimistic-sends.v1:";
|
|
95
|
-
|
|
96
131
|
// A remount must not manufacture a new operation while the previous mutation
|
|
97
132
|
// is still outcome-unknown. Keep only non-credential request fields here; the
|
|
98
133
|
// mounted hook retains the exact input, including any credential updates. The
|
|
@@ -151,6 +186,8 @@ function restoreOptimisticSendOperations(key: string | null): OptimisticSendOper
|
|
|
151
186
|
}
|
|
152
187
|
return stored.map(({ hasMcpCredentialUpdates, ...operation }) => ({
|
|
153
188
|
...operation,
|
|
189
|
+
delivery: "send",
|
|
190
|
+
destination: operation.destination === "queue" ? "queue" : "chat",
|
|
154
191
|
newerShadow: operation.newerShadow ?? { text: "", resources: [], annotations: [] },
|
|
155
192
|
state:
|
|
156
193
|
operation.state === "sending" || operation.state === "queued" ? "sending" : operation.state,
|
|
@@ -158,15 +195,43 @@ function restoreOptimisticSendOperations(key: string | null): OptimisticSendOper
|
|
|
158
195
|
operation.state === "sending" || operation.state === "queued"
|
|
159
196
|
? true
|
|
160
197
|
: operation.outcomeUnknown,
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
198
|
+
correlationId: restoredCorrelationId(operation.correlationId),
|
|
199
|
+
error: restoredErrorReference(
|
|
200
|
+
operation.correlationId,
|
|
201
|
+
hasMcpCredentialUpdates && (operation.state === "sending" || operation.outcomeUnknown)
|
|
202
|
+
? composerSubmissionErrorMessage(new ComposerReconciliationRequiredError())
|
|
203
|
+
: operation.state === "sending"
|
|
204
|
+
? "Delivery was interrupted; retry to reconcile this message."
|
|
205
|
+
: operation.outcomeUnknown
|
|
206
|
+
? "The request could not be confirmed. Check its status before retrying."
|
|
207
|
+
: operation.error
|
|
208
|
+
? composerSubmissionErrorMessage(new Error(operation.error))
|
|
209
|
+
: undefined,
|
|
210
|
+
),
|
|
211
|
+
retryable:
|
|
212
|
+
operation.state === "sending" || operation.outcomeUnknown
|
|
213
|
+
? true
|
|
214
|
+
: (operation.retryable ??
|
|
215
|
+
(operation.error ? composerSubmissionCanRetry(new Error(operation.error)) : true)),
|
|
165
216
|
input: operation.input,
|
|
166
217
|
canRetry: !hasMcpCredentialUpdates,
|
|
167
218
|
}));
|
|
168
219
|
}
|
|
169
220
|
|
|
221
|
+
function restoredCorrelationId(value: unknown): string | undefined {
|
|
222
|
+
return typeof value === "string" && /^[\w.:-]{1,128}$/u.test(value) ? value : undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function restoredErrorReference(
|
|
226
|
+
reference: unknown,
|
|
227
|
+
message: string | undefined,
|
|
228
|
+
): string | undefined {
|
|
229
|
+
const correlationId = restoredCorrelationId(reference);
|
|
230
|
+
return message && correlationId && message !== COMPOSER_MODEL_UNAVAILABLE_MESSAGE
|
|
231
|
+
? `${message} Reference: ${correlationId}.`
|
|
232
|
+
: message;
|
|
233
|
+
}
|
|
234
|
+
|
|
170
235
|
function rememberOptimisticSendOperations(
|
|
171
236
|
key: string | null,
|
|
172
237
|
operations: OptimisticSendOperation[],
|
|
@@ -183,6 +248,15 @@ function rememberOptimisticSendOperations(
|
|
|
183
248
|
hasMcpCredentialUpdates: input.mcpCredentialUpdates !== undefined,
|
|
184
249
|
} satisfies StoredOptimisticSendOperation;
|
|
185
250
|
});
|
|
251
|
+
if (stored.length === 0) {
|
|
252
|
+
optimisticSendOperations.delete(key);
|
|
253
|
+
try {
|
|
254
|
+
pendingComposerStorage()?.removeItem(optimisticSendStorageKey(key));
|
|
255
|
+
} catch {
|
|
256
|
+
// Best effort; the in-memory entry is already gone.
|
|
257
|
+
}
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
186
260
|
optimisticSendOperations.set(key, stored);
|
|
187
261
|
try {
|
|
188
262
|
pendingComposerStorage()?.setItem(optimisticSendStorageKey(key), JSON.stringify(stored));
|
|
@@ -344,6 +418,7 @@ function rememberPendingComposerOperation(
|
|
|
344
418
|
text: operation.newerShadow.text,
|
|
345
419
|
resources: [...operation.newerShadow.resources],
|
|
346
420
|
annotations: cloneAnnotations(operation.newerShadow.annotations),
|
|
421
|
+
...(operation.newerShadow.policy ? { policy: { ...operation.newerShadow.policy } } : {}),
|
|
347
422
|
},
|
|
348
423
|
} satisfies StoredPendingComposerOperation;
|
|
349
424
|
pendingComposerOperations.set(key, stored);
|
|
@@ -390,6 +465,8 @@ export type ComposerState = {
|
|
|
390
465
|
/** Newly captured annotation the review surface should focus. */
|
|
391
466
|
annotationReviewTargetId?: string | null | undefined;
|
|
392
467
|
clearAnnotationReviewTarget?: (() => void) | undefined;
|
|
468
|
+
/** Focus an annotation, or the first incomplete note when omitted. */
|
|
469
|
+
requestAnnotationReview?: ((id?: string) => void) | undefined;
|
|
393
470
|
/** Read the current draft synchronously before a destructive replacement. */
|
|
394
471
|
hasDraftContent: () => boolean;
|
|
395
472
|
/** Append the draft behind prompts already visible in the queue. */
|
|
@@ -397,6 +474,8 @@ export type ComposerState = {
|
|
|
397
474
|
/** Locally acknowledged ordinary sends awaiting durable timeline reconciliation. */
|
|
398
475
|
optimisticMessages?: ComposerOptimisticMessage[] | undefined;
|
|
399
476
|
retryOptimisticMessage?: ((clientEventId: string) => void) | undefined;
|
|
477
|
+
/** Move a definitively refused message into an empty composer without resending it. */
|
|
478
|
+
restoreOptimisticMessage?: ((clientEventId: string) => void) | undefined;
|
|
400
479
|
removeOptimisticMessage?: ((clientEventId: string) => void) | undefined;
|
|
401
480
|
/** Supersede current direction with the draft. */
|
|
402
481
|
steer: (text?: string) => Promise<boolean>;
|
|
@@ -412,11 +491,18 @@ export type ComposerState = {
|
|
|
412
491
|
resume: (reason?: string) => Promise<void>;
|
|
413
492
|
resumeScope: (option: EffectiveControlResumeOption) => Promise<void>;
|
|
414
493
|
resuming: boolean;
|
|
494
|
+
/** Mutation-confirmed control while the streamed queue projection catches up. */
|
|
495
|
+
effectiveControl?: EffectiveSessionControl | null | undefined;
|
|
415
496
|
draft: ComposerDraft | null;
|
|
416
497
|
draftRevision: number;
|
|
417
498
|
draftLoading: boolean;
|
|
418
499
|
draftSaving: boolean;
|
|
419
500
|
draftConflict: Error | null;
|
|
501
|
+
/** Exact policy owned by this actor/session composer. Null while hydrating. */
|
|
502
|
+
policy?: ComposerPolicy | null | undefined;
|
|
503
|
+
setModel?: ((model: string) => void) | undefined;
|
|
504
|
+
setReasoningEffort?: ((effort: ReasoningEffort) => void) | undefined;
|
|
505
|
+
setLatencyMode?: ((mode: LatencyMode) => void) | undefined;
|
|
420
506
|
/** Whether this controller owns a durable server-side draft. */
|
|
421
507
|
draftPersistence?: "durable" | "disabled" | undefined;
|
|
422
508
|
/** Apply an atomic queue Edit checkout without a second read. */
|
|
@@ -429,19 +515,23 @@ export type ComposerState = {
|
|
|
429
515
|
clearError: () => void;
|
|
430
516
|
};
|
|
431
517
|
|
|
518
|
+
export type ComposerControllerState = ComposerState & {
|
|
519
|
+
policy: ComposerPolicy | null;
|
|
520
|
+
setModel: (model: string) => void;
|
|
521
|
+
setReasoningEffort: (effort: ReasoningEffort) => void;
|
|
522
|
+
setLatencyMode: (mode: LatencyMode) => void;
|
|
523
|
+
};
|
|
524
|
+
|
|
432
525
|
export type ComposerSteeringState = {
|
|
433
|
-
phase: "submitting" | "accepted";
|
|
526
|
+
phase: "submitting" | "accepted" | "failed";
|
|
434
527
|
text: string;
|
|
435
528
|
clientEventId: string | null;
|
|
436
529
|
triggerEventId: string | null;
|
|
437
530
|
turnId: string | null;
|
|
438
531
|
/** True only when the accepted Steer durably interrupted a live attempt. */
|
|
439
532
|
stoppingPreviousAttempt?: boolean | undefined;
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
type ComposerControlStoppingState = {
|
|
443
|
-
controlVersion: number;
|
|
444
|
-
controlEtag: string;
|
|
533
|
+
error?: string | undefined;
|
|
534
|
+
outcomeUnknown?: boolean | undefined;
|
|
445
535
|
};
|
|
446
536
|
|
|
447
537
|
const STEERING_SETTLEMENT_EVENT_TYPES = new Set([
|
|
@@ -468,6 +558,32 @@ function steeringAcceptedEvent(
|
|
|
468
558
|
);
|
|
469
559
|
}
|
|
470
560
|
|
|
561
|
+
function promptTurnIdForTrigger(
|
|
562
|
+
triggerEventId: string | null | undefined,
|
|
563
|
+
events: readonly SessionEvent[],
|
|
564
|
+
): string | null {
|
|
565
|
+
if (!triggerEventId) return null;
|
|
566
|
+
const queued = events.find((event) => {
|
|
567
|
+
if (event.type !== "turn.queued") return false;
|
|
568
|
+
const payload = event.payload;
|
|
569
|
+
return (
|
|
570
|
+
typeof payload === "object" &&
|
|
571
|
+
payload !== null &&
|
|
572
|
+
"triggerEventId" in payload &&
|
|
573
|
+
payload.triggerEventId === triggerEventId
|
|
574
|
+
);
|
|
575
|
+
});
|
|
576
|
+
if (!queued) return null;
|
|
577
|
+
if (queued.turnId) return queued.turnId;
|
|
578
|
+
const payload = queued.payload;
|
|
579
|
+
return typeof payload === "object" &&
|
|
580
|
+
payload !== null &&
|
|
581
|
+
"turnId" in payload &&
|
|
582
|
+
typeof payload.turnId === "string"
|
|
583
|
+
? payload.turnId
|
|
584
|
+
: null;
|
|
585
|
+
}
|
|
586
|
+
|
|
471
587
|
function steeringSettledByEvents(
|
|
472
588
|
steering: ComposerSteeringState,
|
|
473
589
|
events: readonly SessionEvent[],
|
|
@@ -489,6 +605,72 @@ function steeringSettledByEvents(
|
|
|
489
605
|
});
|
|
490
606
|
}
|
|
491
607
|
|
|
608
|
+
function reconcileOptimisticSendFromEvents(
|
|
609
|
+
operation: OptimisticSendOperation,
|
|
610
|
+
events: readonly SessionEvent[],
|
|
611
|
+
): { operation: OptimisticSendOperation; admitted: boolean; settled: boolean } {
|
|
612
|
+
const accepted = events.find(
|
|
613
|
+
(event) => event.type === "user.message" && event.clientEventId === operation.clientEventId,
|
|
614
|
+
);
|
|
615
|
+
const triggerEventId = operation.triggerEventId ?? accepted?.id ?? null;
|
|
616
|
+
const turnId = operation.turnId ?? promptTurnIdForTrigger(triggerEventId, events);
|
|
617
|
+
const acceptedRouting = promptRoutingFromAcceptedEvent(accepted ?? null);
|
|
618
|
+
const destination =
|
|
619
|
+
acceptedRouting === "queued_for_execution"
|
|
620
|
+
? "queue"
|
|
621
|
+
: acceptedRouting === "accepted_for_execution" || acceptedRouting === "accepted_for_steering"
|
|
622
|
+
? "chat"
|
|
623
|
+
: operation.destination;
|
|
624
|
+
const needsAdmissionUpdate =
|
|
625
|
+
accepted !== undefined &&
|
|
626
|
+
(operation.state !== "queued" ||
|
|
627
|
+
operation.destination !== destination ||
|
|
628
|
+
operation.triggerEventId !== triggerEventId ||
|
|
629
|
+
operation.turnId !== turnId ||
|
|
630
|
+
operation.error !== undefined ||
|
|
631
|
+
operation.outcomeUnknown !== false);
|
|
632
|
+
const reconciled: OptimisticSendOperation = needsAdmissionUpdate
|
|
633
|
+
? {
|
|
634
|
+
...operation,
|
|
635
|
+
state: "queued",
|
|
636
|
+
destination,
|
|
637
|
+
triggerEventId,
|
|
638
|
+
turnId,
|
|
639
|
+
error: undefined,
|
|
640
|
+
outcomeUnknown: false,
|
|
641
|
+
}
|
|
642
|
+
: operation;
|
|
643
|
+
const withdrawnFromQueue = events.some((event) => {
|
|
644
|
+
if (event.type !== "session.queue.changed" || !turnId) return false;
|
|
645
|
+
const payload = event.payload;
|
|
646
|
+
if (typeof payload !== "object" || payload === null || Array.isArray(payload)) return false;
|
|
647
|
+
const record = payload as Record<string, unknown>;
|
|
648
|
+
return (
|
|
649
|
+
record.turnId === turnId && (record.operation === "edit" || record.operation === "delete")
|
|
650
|
+
);
|
|
651
|
+
});
|
|
652
|
+
return {
|
|
653
|
+
operation: reconciled,
|
|
654
|
+
admitted: accepted !== undefined,
|
|
655
|
+
// Edit/Delete atomically withdraw the queued turn. Keeping its optimistic
|
|
656
|
+
// send alive after that point lets its old `newerShadow` become the newest
|
|
657
|
+
// composer draft again when a later turn settles, duplicating submitted
|
|
658
|
+
// text. The queue mutation is the durable terminal fact for this send.
|
|
659
|
+
settled:
|
|
660
|
+
withdrawnFromQueue ||
|
|
661
|
+
steeringSettledByEvents(
|
|
662
|
+
{
|
|
663
|
+
phase: "accepted",
|
|
664
|
+
text: reconciled.text,
|
|
665
|
+
clientEventId: reconciled.clientEventId,
|
|
666
|
+
triggerEventId: reconciled.triggerEventId ?? null,
|
|
667
|
+
turnId: reconciled.turnId ?? null,
|
|
668
|
+
},
|
|
669
|
+
events,
|
|
670
|
+
),
|
|
671
|
+
};
|
|
672
|
+
}
|
|
673
|
+
|
|
492
674
|
/**
|
|
493
675
|
* Draft + send + Pause/Resume state for the chat composer — the only
|
|
494
676
|
* human-to-agent input surface. The draft survives a failed send (nothing is
|
|
@@ -498,9 +680,13 @@ function steeringSettledByEvents(
|
|
|
498
680
|
export function useComposer(
|
|
499
681
|
sessionId: string | null | undefined,
|
|
500
682
|
options: UseComposerOptions = {},
|
|
501
|
-
):
|
|
683
|
+
): ComposerControllerState {
|
|
502
684
|
const { client, workspaceId, registerSessionReconciler } = useEmbeddedSession(options);
|
|
685
|
+
const formatError = useErrorMessage();
|
|
503
686
|
const durableDrafts = options.draftPersistence !== "disabled";
|
|
687
|
+
if (!durableDrafts && !options.initialPolicy) {
|
|
688
|
+
throw new Error("useComposer requires initialPolicy when draft persistence is disabled");
|
|
689
|
+
}
|
|
504
690
|
const targetKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${durableDrafts ? "durable" : "disabled"}`;
|
|
505
691
|
const pendingOperationKey = pendingComposerOperationKey(workspaceId, sessionId);
|
|
506
692
|
const initialPendingOperation = restorePendingComposerOperation(pendingOperationKey);
|
|
@@ -521,22 +707,29 @@ export function useComposer(
|
|
|
521
707
|
const [steering, setSteering] = useState<ComposerSteeringState | null>(() =>
|
|
522
708
|
initialPendingOperation?.delivery === "steer"
|
|
523
709
|
? {
|
|
524
|
-
phase: "
|
|
710
|
+
phase: "failed",
|
|
525
711
|
text: initialPendingOperation.input.text,
|
|
526
712
|
clientEventId: initialPendingOperation.input.clientEventId ?? null,
|
|
527
713
|
triggerEventId: null,
|
|
528
714
|
turnId: null,
|
|
715
|
+
error: "Not confirmed. Retry to check the same direction change.",
|
|
716
|
+
outcomeUnknown: true,
|
|
529
717
|
}
|
|
530
718
|
: null,
|
|
531
719
|
);
|
|
532
720
|
const [pausing, setPausing] = useState(false);
|
|
533
|
-
const [
|
|
721
|
+
const [acceptedControl, setAcceptedControl] = useState<EffectiveSessionControl | null>(null);
|
|
534
722
|
const [resuming, setResuming] = useState(false);
|
|
535
723
|
const [error, setError] = useState<Error | null>(null);
|
|
724
|
+
// Background draft reads recover independently of Send/Steer/control failures.
|
|
725
|
+
const [draftReadError, setDraftReadError] = useState<Error | null>(null);
|
|
536
726
|
const [draft, setDraft] = useState<ComposerDraft | null>(null);
|
|
537
727
|
const [draftLoading, setDraftLoading] = useState(Boolean(sessionId) && durableDrafts);
|
|
538
728
|
const [draftSaving, setDraftSaving] = useState(false);
|
|
539
729
|
const [draftConflict, setDraftConflict] = useState<Error | null>(null);
|
|
730
|
+
const [policy, setPolicy] = useState<ComposerPolicy | null>(
|
|
731
|
+
() => initialShadow?.policy ?? options.initialPolicy ?? null,
|
|
732
|
+
);
|
|
540
733
|
const [restoredResources, setRestoredResources] = useState<ResourceRef[]>(
|
|
541
734
|
() => initialShadow?.resources ?? [],
|
|
542
735
|
);
|
|
@@ -546,29 +739,33 @@ export function useComposer(
|
|
|
546
739
|
const optimisticCallbackIdsRef = useRef(new Set<string>());
|
|
547
740
|
const steeringSettlementEventsRef = useRef<SessionEvent[]>([]);
|
|
548
741
|
const steeringRef = useRef(steering);
|
|
742
|
+
const steeringOccurredAtRef = useRef(new Date().toISOString());
|
|
549
743
|
const pendingClientEventId = useRef<string | null>(
|
|
550
744
|
initialPendingOperation?.input.clientEventId ?? null,
|
|
551
745
|
);
|
|
552
746
|
const valueRef = useRef(initialShadow?.text ?? "");
|
|
553
747
|
const annotationsRef = useRef<DraftTimelineAnnotation[]>(initialShadow?.annotations ?? []);
|
|
748
|
+
const policyRef = useRef<ComposerPolicy | null>(
|
|
749
|
+
initialShadow?.policy ?? options.initialPolicy ?? null,
|
|
750
|
+
);
|
|
554
751
|
const draftRef = useRef<ComposerDraft | null>(null);
|
|
555
752
|
const restoredResourcesRef = useRef<ResourceRef[]>(initialShadow?.resources ?? []);
|
|
556
753
|
const localEditRevision = useRef(initialShadow ? 1 : 0);
|
|
557
754
|
const targetGeneration = useRef(0);
|
|
558
755
|
const draftReadGeneration = useRef(0);
|
|
756
|
+
const draftReadInFlightRef = useRef<string | null>(null);
|
|
757
|
+
const draftReadAbortRef = useRef<AbortController | null>(null);
|
|
758
|
+
const draftReadRetryRef = useRef<{
|
|
759
|
+
targetKey: string;
|
|
760
|
+
failures: number;
|
|
761
|
+
timer: ReturnType<typeof setTimeout> | null;
|
|
762
|
+
}>({ targetKey, failures: 0, timer: null });
|
|
559
763
|
const lastSavedSignature = useRef<string | null>(null);
|
|
560
764
|
const saveChain = useRef<Promise<void>>(Promise.resolve());
|
|
561
765
|
const onSent = options.onSent;
|
|
562
766
|
const onSubmitted = options.onSubmitted;
|
|
563
|
-
const
|
|
564
|
-
|
|
565
|
-
// apply callback without invalidating the draft loader and re-running its
|
|
566
|
-
// initial-load effect. Publish only committed callbacks: a suspended target
|
|
567
|
-
// render must not retarget an in-flight read owned by the committed session.
|
|
568
|
-
const onDraftAppliedRef = useRef(onDraftApplied);
|
|
569
|
-
useLayoutEffect(() => {
|
|
570
|
-
onDraftAppliedRef.current = onDraftApplied;
|
|
571
|
-
}, [onDraftApplied]);
|
|
767
|
+
const onDeliveryErrorRef = useRef(options.onDeliveryError);
|
|
768
|
+
onDeliveryErrorRef.current = options.onDeliveryError;
|
|
572
769
|
useLayoutEffect(() => {
|
|
573
770
|
steeringRef.current = steering;
|
|
574
771
|
}, [steering]);
|
|
@@ -576,8 +773,14 @@ export function useComposer(
|
|
|
576
773
|
// callers passing inline functions) does not invalidate `send`.
|
|
577
774
|
const sendExtrasRef = useRef(options.sendExtras);
|
|
578
775
|
sendExtrasRef.current = options.sendExtras;
|
|
776
|
+
const sendDestinationRef = useRef(options.sendDestination);
|
|
777
|
+
sendDestinationRef.current = options.sendDestination;
|
|
579
778
|
const sendBlockedRef = useRef(options.sendBlocked);
|
|
580
779
|
sendBlockedRef.current = options.sendBlocked;
|
|
780
|
+
const effectiveControlRef = useRef<EffectiveSessionControl | null>(
|
|
781
|
+
acceptedControl ?? options.effectiveControl ?? null,
|
|
782
|
+
);
|
|
783
|
+
effectiveControlRef.current = acceptedControl ?? options.effectiveControl ?? null;
|
|
581
784
|
const liveExtrasVersion = JSON.stringify(resolveSendExtras(options.sendExtras));
|
|
582
785
|
|
|
583
786
|
// A composer is bound to one session: switching targets must not leak the
|
|
@@ -585,6 +788,12 @@ export function useComposer(
|
|
|
585
788
|
const targetKeyRef = useRef(targetKey);
|
|
586
789
|
useLayoutEffect(() => {
|
|
587
790
|
if (targetKeyRef.current === targetKey) return;
|
|
791
|
+
if (draftReadRetryRef.current.timer !== null) {
|
|
792
|
+
clearTimeout(draftReadRetryRef.current.timer);
|
|
793
|
+
}
|
|
794
|
+
draftReadAbortRef.current?.abort();
|
|
795
|
+
draftReadAbortRef.current = null;
|
|
796
|
+
draftReadRetryRef.current = { targetKey, failures: 0, timer: null };
|
|
588
797
|
targetKeyRef.current = targetKey;
|
|
589
798
|
targetGeneration.current += 1;
|
|
590
799
|
draftReadGeneration.current += 1;
|
|
@@ -600,6 +809,7 @@ export function useComposer(
|
|
|
600
809
|
localEditRevision.current = shadow ? 1 : 0;
|
|
601
810
|
valueRef.current = shadow?.text ?? "";
|
|
602
811
|
annotationsRef.current = shadow?.annotations ?? [];
|
|
812
|
+
policyRef.current = shadow?.policy ?? options.initialPolicy ?? null;
|
|
603
813
|
draftRef.current = null;
|
|
604
814
|
restoredResourcesRef.current = shadow?.resources ?? [];
|
|
605
815
|
lastSavedSignature.current = null;
|
|
@@ -609,30 +819,47 @@ export function useComposer(
|
|
|
609
819
|
setStateTargetKey(targetKey);
|
|
610
820
|
setValue(shadow?.text ?? "");
|
|
611
821
|
setAnnotations(shadow?.annotations ?? []);
|
|
822
|
+
setPolicy(policyRef.current);
|
|
612
823
|
setAnnotationReviewTargetId(null);
|
|
613
824
|
setSending(false);
|
|
614
825
|
setOptimisticSends(restoredOptimisticSends);
|
|
615
826
|
setSteering(
|
|
616
827
|
pendingOperationRef.current?.delivery === "steer"
|
|
617
828
|
? {
|
|
618
|
-
phase: "
|
|
829
|
+
phase: "failed",
|
|
619
830
|
text: pendingOperationRef.current.input.text,
|
|
620
831
|
clientEventId: pendingOperationRef.current.input.clientEventId ?? null,
|
|
621
832
|
triggerEventId: null,
|
|
622
833
|
turnId: null,
|
|
834
|
+
error: "Not confirmed. Retry to check the same direction change.",
|
|
835
|
+
outcomeUnknown: true,
|
|
623
836
|
}
|
|
624
837
|
: null,
|
|
625
838
|
);
|
|
626
839
|
setPausing(false);
|
|
627
|
-
|
|
840
|
+
setAcceptedControl(null);
|
|
628
841
|
setResuming(false);
|
|
629
842
|
setError(null);
|
|
843
|
+
setDraftReadError(null);
|
|
630
844
|
setDraft(null);
|
|
631
845
|
setDraftLoading(Boolean(sessionId) && durableDrafts);
|
|
632
846
|
setDraftSaving(false);
|
|
633
847
|
setDraftConflict(null);
|
|
634
848
|
setRestoredResources(shadow?.resources ?? []);
|
|
635
|
-
}, [durableDrafts, pendingOperationKey, sessionId, targetKey]);
|
|
849
|
+
}, [durableDrafts, options.initialPolicy, pendingOperationKey, sessionId, targetKey]);
|
|
850
|
+
useEffect(
|
|
851
|
+
() => () => {
|
|
852
|
+
if (draftReadRetryRef.current.timer !== null) {
|
|
853
|
+
clearTimeout(draftReadRetryRef.current.timer);
|
|
854
|
+
}
|
|
855
|
+
targetGeneration.current += 1;
|
|
856
|
+
draftReadGeneration.current += 1;
|
|
857
|
+
draftReadInFlightRef.current = null;
|
|
858
|
+
draftReadAbortRef.current?.abort();
|
|
859
|
+
draftReadAbortRef.current = null;
|
|
860
|
+
},
|
|
861
|
+
[],
|
|
862
|
+
);
|
|
636
863
|
|
|
637
864
|
const setOptimisticDraftShadow = useCallback(
|
|
638
865
|
(shadow: ComposerDraftShadow): void => {
|
|
@@ -643,6 +870,7 @@ export function useComposer(
|
|
|
643
870
|
text: shadow.text,
|
|
644
871
|
resources: [...shadow.resources],
|
|
645
872
|
annotations: cloneAnnotations(shadow.annotations),
|
|
873
|
+
...(shadow.policy ? { policy: { ...shadow.policy } } : {}),
|
|
646
874
|
},
|
|
647
875
|
}));
|
|
648
876
|
optimisticSendsRef.current = next;
|
|
@@ -656,15 +884,18 @@ export function useComposer(
|
|
|
656
884
|
(next: ComposerDraft): void => {
|
|
657
885
|
if (targetKeyRef.current !== targetKey) return;
|
|
658
886
|
if (!durableDrafts) {
|
|
887
|
+
const nextPolicy = policyFromDraft(next);
|
|
659
888
|
localEditRevision.current += 1;
|
|
660
889
|
valueRef.current = next.text;
|
|
661
890
|
annotationsRef.current = next.annotations ?? [];
|
|
891
|
+
policyRef.current = nextPolicy;
|
|
662
892
|
restoredResourcesRef.current = next.resources;
|
|
663
893
|
draftRef.current = null;
|
|
664
894
|
lastSavedSignature.current = null;
|
|
665
895
|
setDraft(null);
|
|
666
896
|
setValue(next.text);
|
|
667
897
|
setAnnotations(next.annotations ?? []);
|
|
898
|
+
setPolicy(nextPolicy);
|
|
668
899
|
setRestoredResources(next.resources);
|
|
669
900
|
pendingOperationRef.current = updatePendingComposerShadow(
|
|
670
901
|
pendingOperationKey,
|
|
@@ -676,6 +907,7 @@ export function useComposer(
|
|
|
676
907
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
677
908
|
),
|
|
678
909
|
annotations: next.annotations ?? [],
|
|
910
|
+
policy: nextPolicy,
|
|
679
911
|
},
|
|
680
912
|
);
|
|
681
913
|
setOptimisticDraftShadow({
|
|
@@ -685,12 +917,15 @@ export function useComposer(
|
|
|
685
917
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
686
918
|
),
|
|
687
919
|
annotations: next.annotations ?? [],
|
|
920
|
+
policy: nextPolicy,
|
|
688
921
|
});
|
|
689
922
|
setDraftConflict(null);
|
|
690
923
|
return;
|
|
691
924
|
}
|
|
925
|
+
const nextPolicy = policyFromDraft(next);
|
|
692
926
|
valueRef.current = next.text;
|
|
693
927
|
annotationsRef.current = next.annotations ?? [];
|
|
928
|
+
policyRef.current = nextPolicy;
|
|
694
929
|
draftRef.current = next;
|
|
695
930
|
restoredResourcesRef.current = next.resources;
|
|
696
931
|
lastSavedSignature.current = draftSignature(draftPayload(next));
|
|
@@ -698,6 +933,7 @@ export function useComposer(
|
|
|
698
933
|
setDraft(next);
|
|
699
934
|
setValue(next.text);
|
|
700
935
|
setAnnotations(next.annotations ?? []);
|
|
936
|
+
setPolicy(nextPolicy);
|
|
701
937
|
setRestoredResources(next.resources);
|
|
702
938
|
pendingOperationRef.current = updatePendingComposerShadow(
|
|
703
939
|
pendingOperationKey,
|
|
@@ -709,6 +945,7 @@ export function useComposer(
|
|
|
709
945
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
710
946
|
),
|
|
711
947
|
annotations: next.annotations ?? [],
|
|
948
|
+
policy: nextPolicy,
|
|
712
949
|
},
|
|
713
950
|
);
|
|
714
951
|
setOptimisticDraftShadow({
|
|
@@ -718,9 +955,9 @@ export function useComposer(
|
|
|
718
955
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
719
956
|
),
|
|
720
957
|
annotations: next.annotations ?? [],
|
|
958
|
+
policy: nextPolicy,
|
|
721
959
|
});
|
|
722
960
|
setDraftConflict(null);
|
|
723
|
-
onDraftAppliedRef.current?.(next);
|
|
724
961
|
},
|
|
725
962
|
[durableDrafts, pendingOperationKey, setOptimisticDraftShadow, targetKey],
|
|
726
963
|
);
|
|
@@ -730,23 +967,54 @@ export function useComposer(
|
|
|
730
967
|
if (targetKeyRef.current !== targetKey) return;
|
|
731
968
|
if (!sessionId || !durableDrafts) {
|
|
732
969
|
setDraftLoading(false);
|
|
970
|
+
setDraftReadError(null);
|
|
733
971
|
return;
|
|
734
972
|
}
|
|
973
|
+
let retry = draftReadRetryRef.current;
|
|
974
|
+
if (retry.targetKey !== targetKey) {
|
|
975
|
+
if (retry.timer !== null) clearTimeout(retry.timer);
|
|
976
|
+
retry = { targetKey, failures: 0, timer: null };
|
|
977
|
+
draftReadRetryRef.current = retry;
|
|
978
|
+
}
|
|
979
|
+
const scheduleRetry = (): void => {
|
|
980
|
+
if (retry.timer !== null) return;
|
|
981
|
+
retry.timer = setTimeout(
|
|
982
|
+
() => {
|
|
983
|
+
if (draftReadRetryRef.current !== retry || targetKeyRef.current !== targetKey) return;
|
|
984
|
+
retry.timer = null;
|
|
985
|
+
void loadDraft(false);
|
|
986
|
+
},
|
|
987
|
+
Math.min(25_000, 1_000 * 2 ** (retry.failures - 1)) * (0.8 + Math.random() * 0.4),
|
|
988
|
+
);
|
|
989
|
+
};
|
|
990
|
+
if (!replaceLocal && retry.timer !== null) return;
|
|
991
|
+
if (draftReadInFlightRef.current === targetKey) return;
|
|
992
|
+
if (retry.timer !== null) {
|
|
993
|
+
clearTimeout(retry.timer);
|
|
994
|
+
retry.timer = null;
|
|
995
|
+
}
|
|
996
|
+
draftReadInFlightRef.current = targetKey;
|
|
735
997
|
const generation = targetGeneration.current;
|
|
736
998
|
const readTicket = ++draftReadGeneration.current;
|
|
999
|
+
const requestAbort = new AbortController();
|
|
1000
|
+
draftReadAbortRef.current?.abort();
|
|
1001
|
+
draftReadAbortRef.current = requestAbort;
|
|
737
1002
|
const localAtStart = localEditRevision.current;
|
|
738
1003
|
const baseAtStart = draftRef.current;
|
|
739
|
-
const
|
|
1004
|
+
const policyAtStart = policyRef.current;
|
|
740
1005
|
const localSignatureAtStart = baseAtStart
|
|
741
|
-
?
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
1006
|
+
? policyAtStart
|
|
1007
|
+
? draftSignature(
|
|
1008
|
+
composerDraftPayload(
|
|
1009
|
+
baseAtStart,
|
|
1010
|
+
valueRef.current,
|
|
1011
|
+
restoredResourcesRef.current,
|
|
1012
|
+
annotationsRef.current,
|
|
1013
|
+
policyAtStart,
|
|
1014
|
+
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
1015
|
+
),
|
|
1016
|
+
)
|
|
1017
|
+
: null
|
|
750
1018
|
: null;
|
|
751
1019
|
const localWasDirtyAtStart =
|
|
752
1020
|
localSignatureAtStart === null
|
|
@@ -760,7 +1028,9 @@ export function useComposer(
|
|
|
760
1028
|
setDraftLoading(true);
|
|
761
1029
|
}
|
|
762
1030
|
try {
|
|
763
|
-
const fetched = await client.getComposerDraft(workspaceId, sessionId
|
|
1031
|
+
const fetched = await client.getComposerDraft(workspaceId, sessionId, {
|
|
1032
|
+
signal: requestAbort.signal,
|
|
1033
|
+
});
|
|
764
1034
|
if (
|
|
765
1035
|
generation !== targetGeneration.current ||
|
|
766
1036
|
targetKeyRef.current !== targetKey ||
|
|
@@ -768,8 +1038,17 @@ export function useComposer(
|
|
|
768
1038
|
) {
|
|
769
1039
|
return;
|
|
770
1040
|
}
|
|
1041
|
+
if (retry.timer !== null) clearTimeout(retry.timer);
|
|
1042
|
+
retry.failures = 0;
|
|
1043
|
+
retry.timer = null;
|
|
1044
|
+
// Recovery also counts when the server returns the same draft revision.
|
|
1045
|
+
setDraftReadError(null);
|
|
771
1046
|
const currentRevision = draftRef.current?.revision ?? -1;
|
|
772
|
-
|
|
1047
|
+
// Reconnect and client-generation effects can legitimately ask for the
|
|
1048
|
+
// draft again. A same-revision response is not new authority: publishing
|
|
1049
|
+
// its freshly decoded object back into React creates a response -> render
|
|
1050
|
+
// -> read feedback loop. Hard reload remains the explicit exception.
|
|
1051
|
+
if (replaceLocal || fetched.revision > currentRevision) {
|
|
773
1052
|
draftRef.current = fetched;
|
|
774
1053
|
setDraft(fetched);
|
|
775
1054
|
setDraftConflict(null);
|
|
@@ -782,42 +1061,64 @@ export function useComposer(
|
|
|
782
1061
|
valueRef.current = shadow.text;
|
|
783
1062
|
restoredResourcesRef.current = shadow.resources;
|
|
784
1063
|
annotationsRef.current = shadow.annotations;
|
|
1064
|
+
if (shadow.policy) policyRef.current = shadow.policy;
|
|
785
1065
|
localEditRevision.current ||= 1;
|
|
786
1066
|
setValue(shadow.text);
|
|
787
1067
|
setRestoredResources(shadow.resources);
|
|
788
1068
|
setAnnotations(shadow.annotations);
|
|
1069
|
+
if (shadow.policy) setPolicy(shadow.policy);
|
|
789
1070
|
} else if (
|
|
790
1071
|
replaceLocal ||
|
|
791
1072
|
(!localWasDirtyAtStart && localAtStart === localEditRevision.current)
|
|
792
1073
|
) {
|
|
793
|
-
|
|
794
|
-
// localEditRevision). If
|
|
795
|
-
// the picker changed during this fetch, skip onDraftApplied so a
|
|
796
|
-
// stale server policy cannot undo the operator's pick.
|
|
797
|
-
const extrasNow = resolveSendExtras(sendExtrasRef.current);
|
|
798
|
-
const pickerChangedDuringFetch =
|
|
799
|
-
extrasNow.model !== extrasAtStart.model ||
|
|
800
|
-
extrasNow.reasoningEffort !== extrasAtStart.reasoningEffort ||
|
|
801
|
-
extrasNow.latencyMode !== extrasAtStart.latencyMode;
|
|
1074
|
+
const fetchedPolicy = policyFromDraft(fetched);
|
|
802
1075
|
valueRef.current = fetched.text;
|
|
803
1076
|
restoredResourcesRef.current = fetched.resources;
|
|
804
1077
|
annotationsRef.current = fetched.annotations ?? [];
|
|
1078
|
+
policyRef.current = fetchedPolicy;
|
|
805
1079
|
lastSavedSignature.current = draftSignature(draftPayload(fetched));
|
|
806
1080
|
setValue(fetched.text);
|
|
807
1081
|
setRestoredResources(fetched.resources);
|
|
808
1082
|
setAnnotations(fetched.annotations ?? []);
|
|
809
|
-
|
|
810
|
-
|
|
1083
|
+
setPolicy(fetchedPolicy);
|
|
1084
|
+
} else {
|
|
1085
|
+
// Keep the in-flight edit, but the fetched row is still the OCC
|
|
1086
|
+
// base. First hydrate has no local policy until this read lands;
|
|
1087
|
+
// without it autosave cannot persist the typed text.
|
|
1088
|
+
lastSavedSignature.current = draftSignature(draftPayload(fetched));
|
|
1089
|
+
if (!policyRef.current) {
|
|
1090
|
+
const fetchedPolicy = policyFromDraft(fetched);
|
|
1091
|
+
policyRef.current = fetchedPolicy;
|
|
1092
|
+
setPolicy(fetchedPolicy);
|
|
811
1093
|
}
|
|
812
1094
|
}
|
|
813
1095
|
}
|
|
814
1096
|
} catch (cause) {
|
|
815
1097
|
if (
|
|
1098
|
+
!requestAbort.signal.aborted &&
|
|
816
1099
|
generation === targetGeneration.current &&
|
|
817
1100
|
targetKeyRef.current === targetKey &&
|
|
818
1101
|
readTicket === draftReadGeneration.current
|
|
819
1102
|
) {
|
|
820
|
-
|
|
1103
|
+
const problem = asError(cause);
|
|
1104
|
+
const timedOut = problem.name === "TimeoutError";
|
|
1105
|
+
setDraftReadError(
|
|
1106
|
+
timedOut
|
|
1107
|
+
? new ComposerStateError("Draft sync timed out. Retrying…", { cause })
|
|
1108
|
+
: problem,
|
|
1109
|
+
);
|
|
1110
|
+
if (
|
|
1111
|
+
timedOut ||
|
|
1112
|
+
cause instanceof TypeError ||
|
|
1113
|
+
(cause && typeof cause === "object" && "retryable" in cause && cause.retryable === true)
|
|
1114
|
+
) {
|
|
1115
|
+
retry.failures += 1;
|
|
1116
|
+
scheduleRetry();
|
|
1117
|
+
} else {
|
|
1118
|
+
if (retry.timer !== null) clearTimeout(retry.timer);
|
|
1119
|
+
retry.failures = 0;
|
|
1120
|
+
retry.timer = null;
|
|
1121
|
+
}
|
|
821
1122
|
}
|
|
822
1123
|
} finally {
|
|
823
1124
|
if (
|
|
@@ -827,6 +1128,12 @@ export function useComposer(
|
|
|
827
1128
|
) {
|
|
828
1129
|
setDraftLoading(false);
|
|
829
1130
|
}
|
|
1131
|
+
if (draftReadInFlightRef.current === targetKey) {
|
|
1132
|
+
draftReadInFlightRef.current = null;
|
|
1133
|
+
}
|
|
1134
|
+
if (draftReadAbortRef.current === requestAbort) {
|
|
1135
|
+
draftReadAbortRef.current = null;
|
|
1136
|
+
}
|
|
830
1137
|
}
|
|
831
1138
|
},
|
|
832
1139
|
[client, durableDrafts, sessionId, targetKey, workspaceId],
|
|
@@ -930,32 +1237,41 @@ export function useComposer(
|
|
|
930
1237
|
}, [options.events, steering]);
|
|
931
1238
|
|
|
932
1239
|
useEffect(() => {
|
|
933
|
-
if (!
|
|
1240
|
+
if (!acceptedControl) return;
|
|
934
1241
|
const effectiveControl = options.effectiveControl;
|
|
935
|
-
if (!effectiveControl || effectiveControl.controlVersion <
|
|
936
|
-
return;
|
|
937
|
-
}
|
|
938
|
-
if (
|
|
939
|
-
effectiveControl.controlVersion === controlStopping.controlVersion &&
|
|
940
|
-
effectiveControl.controlEtag === controlStopping.controlEtag &&
|
|
941
|
-
effectiveControl.settlement !== null
|
|
942
|
-
) {
|
|
1242
|
+
if (!effectiveControl || effectiveControl.controlVersion < acceptedControl.controlVersion) {
|
|
943
1243
|
return;
|
|
944
1244
|
}
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
// mutation receipt. SessionChrome then falls back to durable queue truth.
|
|
948
|
-
setControlStopping((current) => (current === controlStopping ? null : current));
|
|
949
|
-
}, [controlStopping, options.effectiveControl]);
|
|
1245
|
+
setAcceptedControl((current) => (current === acceptedControl ? null : current));
|
|
1246
|
+
}, [acceptedControl, options.effectiveControl]);
|
|
950
1247
|
|
|
951
1248
|
const currentDraftPayload = useCallback((): SaveComposerDraftRequest | null => {
|
|
952
1249
|
if (!durableDrafts || targetKeyRef.current !== targetKey) return null;
|
|
953
1250
|
const base = draftRef.current;
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
return composerDraftPayload(
|
|
1251
|
+
const currentPolicy = policyRef.current;
|
|
1252
|
+
if (!base || !currentPolicy) return null;
|
|
1253
|
+
return composerDraftPayload(
|
|
1254
|
+
base,
|
|
1255
|
+
value,
|
|
1256
|
+
restoredResources,
|
|
1257
|
+
annotations,
|
|
1258
|
+
currentPolicy,
|
|
1259
|
+
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
1260
|
+
);
|
|
957
1261
|
}, [annotations, durableDrafts, restoredResources, targetKey, value]);
|
|
958
1262
|
|
|
1263
|
+
const adoptDraftBase = useCallback(
|
|
1264
|
+
(next: ComposerDraft): void => {
|
|
1265
|
+
if (targetKeyRef.current !== targetKey) return;
|
|
1266
|
+
draftRef.current = next;
|
|
1267
|
+
setDraft(next);
|
|
1268
|
+
lastSavedSignature.current = draftSignature(draftPayload(next));
|
|
1269
|
+
setDraftConflict(null);
|
|
1270
|
+
setError(null);
|
|
1271
|
+
},
|
|
1272
|
+
[targetKey],
|
|
1273
|
+
);
|
|
1274
|
+
|
|
959
1275
|
const persistPayload = useCallback(
|
|
960
1276
|
async (payload: SaveComposerDraftRequest): Promise<boolean> => {
|
|
961
1277
|
const ownedTargetKey = targetKey;
|
|
@@ -981,30 +1297,14 @@ export function useComposer(
|
|
|
981
1297
|
}
|
|
982
1298
|
setDraftSaving(true);
|
|
983
1299
|
try {
|
|
984
|
-
const saved = await
|
|
985
|
-
client,
|
|
986
|
-
workspaceId,
|
|
987
|
-
sessionId,
|
|
988
|
-
request,
|
|
989
|
-
onAdoptRemote: (remote) => {
|
|
990
|
-
draftRef.current = remote;
|
|
991
|
-
setDraft(remote);
|
|
992
|
-
},
|
|
993
|
-
});
|
|
1300
|
+
const saved = await client.saveComposerDraft(workspaceId, sessionId, request);
|
|
994
1301
|
if (
|
|
995
1302
|
targetKeyRef.current !== ownedTargetKey ||
|
|
996
1303
|
targetGeneration.current !== ownedGeneration
|
|
997
1304
|
) {
|
|
998
1305
|
return;
|
|
999
1306
|
}
|
|
1000
|
-
|
|
1001
|
-
setDraft(saved);
|
|
1002
|
-
lastSavedSignature.current = draftSignature({
|
|
1003
|
-
...request,
|
|
1004
|
-
expectedRevision: saved.revision,
|
|
1005
|
-
});
|
|
1006
|
-
setDraftConflict(null);
|
|
1007
|
-
setError(null);
|
|
1307
|
+
adoptDraftBase(saved);
|
|
1008
1308
|
success = true;
|
|
1009
1309
|
} catch (cause) {
|
|
1010
1310
|
if (
|
|
@@ -1028,7 +1328,7 @@ export function useComposer(
|
|
|
1028
1328
|
await saveChain.current;
|
|
1029
1329
|
return success;
|
|
1030
1330
|
},
|
|
1031
|
-
[client, durableDrafts, sessionId, targetKey, workspaceId],
|
|
1331
|
+
[adoptDraftBase, client, durableDrafts, sessionId, targetKey, workspaceId],
|
|
1032
1332
|
);
|
|
1033
1333
|
|
|
1034
1334
|
const replaceOptimisticSends = useCallback(
|
|
@@ -1058,17 +1358,46 @@ export function useComposer(
|
|
|
1058
1358
|
);
|
|
1059
1359
|
|
|
1060
1360
|
useEffect(() => {
|
|
1061
|
-
const
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1361
|
+
const events = options.events ?? [];
|
|
1362
|
+
if (events.length === 0 || optimisticSendsRef.current.length === 0) return;
|
|
1363
|
+
const reconciled = optimisticSendsRef.current.map((operation) =>
|
|
1364
|
+
reconcileOptimisticSendFromEvents(operation, events),
|
|
1365
|
+
);
|
|
1366
|
+
const settledIds = new Set(
|
|
1367
|
+
reconciled.filter((item) => item.settled).map((item) => item.operation.clientEventId),
|
|
1065
1368
|
);
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1369
|
+
const changed = reconciled.some(
|
|
1370
|
+
(item, index) =>
|
|
1371
|
+
item.operation !== optimisticSendsRef.current[index] &&
|
|
1372
|
+
!settledIds.has(item.operation.clientEventId),
|
|
1069
1373
|
);
|
|
1070
|
-
for (const
|
|
1071
|
-
|
|
1374
|
+
for (const item of reconciled) {
|
|
1375
|
+
if (item.admitted && !optimisticCallbackIdsRef.current.has(item.operation.clientEventId)) {
|
|
1376
|
+
optimisticCallbackIdsRef.current.add(item.operation.clientEventId);
|
|
1377
|
+
onSent?.(item.operation.input.text, item.operation.input);
|
|
1378
|
+
}
|
|
1379
|
+
if (item.settled) markOptimisticAccepted(item.operation);
|
|
1380
|
+
}
|
|
1381
|
+
if (changed) {
|
|
1382
|
+
replaceOptimisticSends((current) =>
|
|
1383
|
+
current
|
|
1384
|
+
.map(
|
|
1385
|
+
(operation) =>
|
|
1386
|
+
reconciled.find((item) => item.operation.clientEventId === operation.clientEventId)
|
|
1387
|
+
?.operation ?? operation,
|
|
1388
|
+
)
|
|
1389
|
+
.filter((operation) => !settledIds.has(operation.clientEventId)),
|
|
1390
|
+
);
|
|
1391
|
+
}
|
|
1392
|
+
if (durableDrafts && reconciled.some((item) => item.admitted)) void loadDraft(false);
|
|
1393
|
+
}, [
|
|
1394
|
+
durableDrafts,
|
|
1395
|
+
loadDraft,
|
|
1396
|
+
markOptimisticAccepted,
|
|
1397
|
+
onSent,
|
|
1398
|
+
options.events,
|
|
1399
|
+
replaceOptimisticSends,
|
|
1400
|
+
]);
|
|
1072
1401
|
|
|
1073
1402
|
const processOptimisticSends = useCallback((): void => {
|
|
1074
1403
|
if (!sessionId || optimisticProcessorBusyRef.current) return;
|
|
@@ -1082,32 +1411,92 @@ export function useComposer(
|
|
|
1082
1411
|
const ownedGeneration = targetGeneration.current;
|
|
1083
1412
|
optimisticProcessorBusyRef.current = true;
|
|
1084
1413
|
void (async () => {
|
|
1414
|
+
let mutationFailureObserved = false;
|
|
1415
|
+
const markFailed = (cause: unknown, outcomeUnknown: boolean): void => {
|
|
1416
|
+
if (
|
|
1417
|
+
targetKeyRef.current !== ownedTargetKey ||
|
|
1418
|
+
targetGeneration.current !== ownedGeneration
|
|
1419
|
+
) {
|
|
1420
|
+
return;
|
|
1421
|
+
}
|
|
1422
|
+
const problem = asError(cause);
|
|
1423
|
+
replaceOptimisticSends((current) =>
|
|
1424
|
+
current.map((candidate) =>
|
|
1425
|
+
candidate.clientEventId === operation.clientEventId
|
|
1426
|
+
? {
|
|
1427
|
+
...candidate,
|
|
1428
|
+
state: "failed",
|
|
1429
|
+
error: formatError(
|
|
1430
|
+
cause,
|
|
1431
|
+
outcomeUnknown &&
|
|
1432
|
+
!isOutcomeUnknownError(problem) &&
|
|
1433
|
+
!(problem instanceof ComposerReconciliationRequiredError)
|
|
1434
|
+
? `The request could not be confirmed. Check its status before retrying.${problem instanceof OpenGeniApiError && problem.correlationId ? ` Reference: ${problem.correlationId}.` : ""}`
|
|
1435
|
+
: composerSubmissionErrorMessage(problem),
|
|
1436
|
+
),
|
|
1437
|
+
retryable: outcomeUnknown || composerSubmissionCanRetry(problem),
|
|
1438
|
+
outcomeUnknown,
|
|
1439
|
+
correlationId:
|
|
1440
|
+
problem instanceof OpenGeniApiError
|
|
1441
|
+
? problem.correlationId
|
|
1442
|
+
: candidate.correlationId,
|
|
1443
|
+
}
|
|
1444
|
+
: candidate,
|
|
1445
|
+
),
|
|
1446
|
+
);
|
|
1447
|
+
};
|
|
1085
1448
|
try {
|
|
1086
1449
|
if (operation.outcomeUnknown) {
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1450
|
+
let events: SessionEvent[];
|
|
1451
|
+
try {
|
|
1452
|
+
events = await client.listEvents(workspaceId, sessionId, {
|
|
1453
|
+
includeTypes: [
|
|
1454
|
+
"user.message",
|
|
1455
|
+
"turn.queued",
|
|
1456
|
+
"turn.started",
|
|
1457
|
+
"turn.completed",
|
|
1458
|
+
"turn.failed",
|
|
1459
|
+
"turn.cancelled",
|
|
1460
|
+
"turn.superseded",
|
|
1461
|
+
],
|
|
1462
|
+
limit: 250,
|
|
1463
|
+
payloadMode: "full",
|
|
1464
|
+
});
|
|
1465
|
+
} catch (cause) {
|
|
1466
|
+
// This read cannot settle the prior mutation in either direction.
|
|
1467
|
+
// Preserve its exact key and uncertain status until reconciliation
|
|
1468
|
+
// succeeds or that same mutation is actually replayed.
|
|
1469
|
+
markFailed(cause, true);
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1472
|
+
const reconciled = reconcileOptimisticSendFromEvents(operation, events);
|
|
1473
|
+
if (reconciled.admitted) {
|
|
1474
|
+
if (durableDrafts) await loadDraft(false);
|
|
1475
|
+
if (reconciled.settled) {
|
|
1476
|
+
markOptimisticAccepted(reconciled.operation);
|
|
1477
|
+
} else {
|
|
1478
|
+
replaceOptimisticSends((current) =>
|
|
1479
|
+
current.map((candidate) =>
|
|
1480
|
+
candidate.clientEventId === operation.clientEventId
|
|
1481
|
+
? reconciled.operation
|
|
1482
|
+
: candidate,
|
|
1483
|
+
),
|
|
1484
|
+
);
|
|
1485
|
+
if (!optimisticCallbackIdsRef.current.has(operation.clientEventId)) {
|
|
1486
|
+
optimisticCallbackIdsRef.current.add(operation.clientEventId);
|
|
1487
|
+
onSent?.(reconciled.operation.input.text, reconciled.operation.input);
|
|
1488
|
+
}
|
|
1489
|
+
}
|
|
1099
1490
|
return;
|
|
1100
1491
|
}
|
|
1101
1492
|
if (!operation.canRetry) {
|
|
1102
|
-
throw new
|
|
1103
|
-
"OpenGeni cannot safely retry this uncertain request after remount; reconcile the session before sending again.",
|
|
1104
|
-
);
|
|
1493
|
+
throw new ComposerReconciliationRequiredError();
|
|
1105
1494
|
}
|
|
1106
1495
|
}
|
|
1107
1496
|
let expectedDraftRevision: number | undefined;
|
|
1108
1497
|
if (durableDrafts && operation.draftPayload) {
|
|
1109
1498
|
if (!(await persistPayload(operation.draftPayload))) {
|
|
1110
|
-
throw new
|
|
1499
|
+
throw new ComposerStateError("The message draft could not be saved before delivery.");
|
|
1111
1500
|
}
|
|
1112
1501
|
expectedDraftRevision = draftRef.current?.revision;
|
|
1113
1502
|
}
|
|
@@ -1121,20 +1510,83 @@ export function useComposer(
|
|
|
1121
1510
|
...operation.input,
|
|
1122
1511
|
...(expectedDraftRevision !== undefined ? { expectedDraftRevision } : {}),
|
|
1123
1512
|
};
|
|
1124
|
-
|
|
1513
|
+
let acceptedResult: SubmitComposerDraftResponse | null = null;
|
|
1514
|
+
let acceptedEvent: SessionEvent | null = null;
|
|
1515
|
+
if (durableDrafts) {
|
|
1516
|
+
if (!operation.draftPayload || expectedDraftRevision === undefined) {
|
|
1517
|
+
throw new ComposerStateError("The durable composer draft is not ready for delivery.");
|
|
1518
|
+
}
|
|
1519
|
+
const result = await client
|
|
1520
|
+
.submitComposerDraft(workspaceId, sessionId, {
|
|
1521
|
+
text: operation.draftPayload.text,
|
|
1522
|
+
annotations: operation.draftPayload.annotations,
|
|
1523
|
+
resources: operation.draftPayload.resources,
|
|
1524
|
+
model: operation.draftPayload.model,
|
|
1525
|
+
reasoningEffort: operation.draftPayload.reasoningEffort,
|
|
1526
|
+
latencyMode: operation.draftPayload.latencyMode,
|
|
1527
|
+
expectedDraftRevision,
|
|
1528
|
+
clientEventId: operation.clientEventId,
|
|
1529
|
+
delivery: "send",
|
|
1530
|
+
...(wireInput.controlEtag ? { controlEtag: wireInput.controlEtag } : {}),
|
|
1531
|
+
...(wireInput.modelContext ? { modelContext: wireInput.modelContext } : {}),
|
|
1532
|
+
...(wireInput.mcpCredentialUpdates
|
|
1533
|
+
? { mcpCredentialUpdates: wireInput.mcpCredentialUpdates }
|
|
1534
|
+
: {}),
|
|
1535
|
+
...(wireInput.connectionAccounts
|
|
1536
|
+
? { connectionAccounts: wireInput.connectionAccounts }
|
|
1537
|
+
: {}),
|
|
1538
|
+
|
|
1539
|
+
...(wireInput.personalResourceAttachment
|
|
1540
|
+
? { personalResourceAttachment: wireInput.personalResourceAttachment }
|
|
1541
|
+
: {}),
|
|
1542
|
+
})
|
|
1543
|
+
.catch((cause: unknown) => {
|
|
1544
|
+
mutationFailureObserved = true;
|
|
1545
|
+
throw cause;
|
|
1546
|
+
});
|
|
1547
|
+
acceptedResult = result;
|
|
1548
|
+
adoptDraftBase(result.draft);
|
|
1549
|
+
} else {
|
|
1550
|
+
acceptedEvent = await client
|
|
1551
|
+
.sendMessage(workspaceId, sessionId, wireInput)
|
|
1552
|
+
.catch((cause: unknown) => {
|
|
1553
|
+
mutationFailureObserved = true;
|
|
1554
|
+
throw cause;
|
|
1555
|
+
});
|
|
1556
|
+
}
|
|
1125
1557
|
if (
|
|
1126
1558
|
targetKeyRef.current !== ownedTargetKey ||
|
|
1127
1559
|
targetGeneration.current !== ownedGeneration
|
|
1128
1560
|
) {
|
|
1129
1561
|
return;
|
|
1130
1562
|
}
|
|
1131
|
-
|
|
1563
|
+
const acceptedRouting =
|
|
1564
|
+
acceptedResult?.routing ?? promptRoutingFromAcceptedEvent(acceptedEvent);
|
|
1565
|
+
const acceptedDestination =
|
|
1566
|
+
acceptedRouting === "queued_for_execution"
|
|
1567
|
+
? "queue"
|
|
1568
|
+
: acceptedRouting === "accepted_for_execution" ||
|
|
1569
|
+
acceptedRouting === "accepted_for_steering"
|
|
1570
|
+
? "chat"
|
|
1571
|
+
: operation.destination;
|
|
1572
|
+
if (options.events === undefined && acceptedDestination === "chat") {
|
|
1132
1573
|
markOptimisticAccepted({ ...operation, input: wireInput });
|
|
1133
1574
|
} else {
|
|
1134
1575
|
replaceOptimisticSends((current) =>
|
|
1135
1576
|
current.map((candidate) =>
|
|
1136
1577
|
candidate.clientEventId === operation.clientEventId
|
|
1137
|
-
? {
|
|
1578
|
+
? {
|
|
1579
|
+
...candidate,
|
|
1580
|
+
input: wireInput,
|
|
1581
|
+
destination: acceptedDestination,
|
|
1582
|
+
turnId: acceptedResult?.turn.id ?? candidate.turnId,
|
|
1583
|
+
triggerEventId:
|
|
1584
|
+
acceptedResult?.accepted.id ?? acceptedEvent?.id ?? candidate.triggerEventId,
|
|
1585
|
+
appliedQueueVersion:
|
|
1586
|
+
acceptedResult?.receipt?.appliedQueueVersion ?? candidate.appliedQueueVersion,
|
|
1587
|
+
state: "queued",
|
|
1588
|
+
error: undefined,
|
|
1589
|
+
}
|
|
1138
1590
|
: candidate,
|
|
1139
1591
|
),
|
|
1140
1592
|
);
|
|
@@ -1143,41 +1595,24 @@ export function useComposer(
|
|
|
1143
1595
|
optimisticCallbackIdsRef.current.add(operation.clientEventId);
|
|
1144
1596
|
onSent?.(wireInput.text, wireInput);
|
|
1145
1597
|
}
|
|
1146
|
-
if (durableDrafts && draftRef.current) {
|
|
1147
|
-
const cleared = {
|
|
1148
|
-
...draftRef.current,
|
|
1149
|
-
revision: 0,
|
|
1150
|
-
text: "",
|
|
1151
|
-
resources: [],
|
|
1152
|
-
annotations: [],
|
|
1153
|
-
sourceTurnId: null,
|
|
1154
|
-
sourceTurnVersion: null,
|
|
1155
|
-
updatedAt: null,
|
|
1156
|
-
};
|
|
1157
|
-
draftRef.current = cleared;
|
|
1158
|
-
setDraft(cleared);
|
|
1159
|
-
lastSavedSignature.current = draftSignature(draftPayload(cleared));
|
|
1160
|
-
}
|
|
1161
1598
|
} catch (cause) {
|
|
1162
|
-
const
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
) {
|
|
1168
|
-
|
|
1169
|
-
current.map((candidate) =>
|
|
1170
|
-
candidate.clientEventId === operation.clientEventId
|
|
1171
|
-
? {
|
|
1172
|
-
...candidate,
|
|
1173
|
-
state: "failed",
|
|
1174
|
-
error: problem.message,
|
|
1175
|
-
outcomeUnknown,
|
|
1176
|
-
}
|
|
1177
|
-
: candidate,
|
|
1178
|
-
),
|
|
1179
|
-
);
|
|
1599
|
+
const current = optimisticSendsRef.current.find(
|
|
1600
|
+
(candidate) => candidate.clientEventId === operation.clientEventId,
|
|
1601
|
+
);
|
|
1602
|
+
// A live durable event can prove admission before the original HTTP
|
|
1603
|
+
// response times out. Never regress that confirmed state to failure.
|
|
1604
|
+
if (!current || (current.state === "queued" && current.outcomeUnknown === false)) {
|
|
1605
|
+
return;
|
|
1180
1606
|
}
|
|
1607
|
+
const problem = asError(cause);
|
|
1608
|
+
onDeliveryErrorRef.current?.(problem, operation.input, "send");
|
|
1609
|
+
// Only an actual mutation replay can replace prior uncertainty with a
|
|
1610
|
+
// definitive response. Local preparation failures remain retry-only.
|
|
1611
|
+
const outcomeUnknown =
|
|
1612
|
+
operation.outcomeUnknown && !mutationFailureObserved
|
|
1613
|
+
? true
|
|
1614
|
+
: isOutcomeUnknownError(cause);
|
|
1615
|
+
markFailed(cause, outcomeUnknown);
|
|
1181
1616
|
} finally {
|
|
1182
1617
|
optimisticProcessorBusyRef.current = false;
|
|
1183
1618
|
if (targetKeyRef.current === ownedTargetKey) {
|
|
@@ -1186,9 +1621,12 @@ export function useComposer(
|
|
|
1186
1621
|
}
|
|
1187
1622
|
})();
|
|
1188
1623
|
}, [
|
|
1624
|
+
adoptDraftBase,
|
|
1189
1625
|
client,
|
|
1190
1626
|
durableDrafts,
|
|
1627
|
+
formatError,
|
|
1191
1628
|
markOptimisticAccepted,
|
|
1629
|
+
loadDraft,
|
|
1192
1630
|
onSent,
|
|
1193
1631
|
options.events,
|
|
1194
1632
|
persistPayload,
|
|
@@ -1214,11 +1652,13 @@ export function useComposer(
|
|
|
1214
1652
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
1215
1653
|
),
|
|
1216
1654
|
annotations: annotationsRef.current,
|
|
1655
|
+
...(policyRef.current ? { policy: policyRef.current } : {}),
|
|
1217
1656
|
};
|
|
1218
1657
|
if (
|
|
1219
1658
|
pending.newerShadow.text !== shadow.text ||
|
|
1220
1659
|
JSON.stringify(pending.newerShadow.resources) !== JSON.stringify(shadow.resources) ||
|
|
1221
|
-
JSON.stringify(pending.newerShadow.annotations) !== JSON.stringify(shadow.annotations)
|
|
1660
|
+
JSON.stringify(pending.newerShadow.annotations) !== JSON.stringify(shadow.annotations) ||
|
|
1661
|
+
JSON.stringify(pending.newerShadow.policy) !== JSON.stringify(shadow.policy)
|
|
1222
1662
|
) {
|
|
1223
1663
|
pendingOperationRef.current = updatePendingComposerShadow(
|
|
1224
1664
|
pendingOperationKey,
|
|
@@ -1237,11 +1677,13 @@ export function useComposer(
|
|
|
1237
1677
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
1238
1678
|
),
|
|
1239
1679
|
annotations: annotationsRef.current,
|
|
1680
|
+
...(policyRef.current ? { policy: policyRef.current } : {}),
|
|
1240
1681
|
};
|
|
1241
1682
|
if (
|
|
1242
1683
|
optimistic.newerShadow.text !== shadow.text ||
|
|
1243
1684
|
JSON.stringify(optimistic.newerShadow.resources) !== JSON.stringify(shadow.resources) ||
|
|
1244
|
-
JSON.stringify(optimistic.newerShadow.annotations) !== JSON.stringify(shadow.annotations)
|
|
1685
|
+
JSON.stringify(optimistic.newerShadow.annotations) !== JSON.stringify(shadow.annotations) ||
|
|
1686
|
+
JSON.stringify(optimistic.newerShadow.policy) !== JSON.stringify(shadow.policy)
|
|
1245
1687
|
) {
|
|
1246
1688
|
setOptimisticDraftShadow(shadow);
|
|
1247
1689
|
}
|
|
@@ -1290,6 +1732,7 @@ export function useComposer(
|
|
|
1290
1732
|
const rawText = explicit ?? draftAtSend;
|
|
1291
1733
|
const hasText = rawText.trim().length > 0;
|
|
1292
1734
|
const hasAnnotations = annotationsAtSend.length > 0;
|
|
1735
|
+
const currentPolicy = policyRef.current;
|
|
1293
1736
|
const annotationsComplete = annotationsAtSend.every(
|
|
1294
1737
|
(annotation) => annotation.note.trim().length > 0,
|
|
1295
1738
|
);
|
|
@@ -1300,6 +1743,7 @@ export function useComposer(
|
|
|
1300
1743
|
if (
|
|
1301
1744
|
(!pending && (!annotationsComplete || (!hasText && !hasResources && !hasAnnotations))) ||
|
|
1302
1745
|
!sessionId ||
|
|
1746
|
+
!currentPolicy ||
|
|
1303
1747
|
sending ||
|
|
1304
1748
|
sendBlockedRef.current?.() === true ||
|
|
1305
1749
|
targetKeyRef.current !== ownedTargetKey
|
|
@@ -1323,22 +1767,6 @@ export function useComposer(
|
|
|
1323
1767
|
JSON.stringify(operation.resourcesAtSend);
|
|
1324
1768
|
const annotationsWereUnchanged =
|
|
1325
1769
|
JSON.stringify(annotationsRef.current) === JSON.stringify(operation.annotationsAtSend);
|
|
1326
|
-
const previousDraft = draftRef.current;
|
|
1327
|
-
if (previousDraft) {
|
|
1328
|
-
const cleared = {
|
|
1329
|
-
...previousDraft,
|
|
1330
|
-
revision: 0,
|
|
1331
|
-
text: "",
|
|
1332
|
-
resources: [],
|
|
1333
|
-
annotations: [],
|
|
1334
|
-
sourceTurnId: null,
|
|
1335
|
-
sourceTurnVersion: null,
|
|
1336
|
-
updatedAt: null,
|
|
1337
|
-
};
|
|
1338
|
-
draftRef.current = cleared;
|
|
1339
|
-
setDraft(cleared);
|
|
1340
|
-
lastSavedSignature.current = draftSignature(draftPayload(cleared));
|
|
1341
|
-
}
|
|
1342
1770
|
if (resourcesWereUnchanged) {
|
|
1343
1771
|
restoredResourcesRef.current = [];
|
|
1344
1772
|
setRestoredResources([]);
|
|
@@ -1356,6 +1784,40 @@ export function useComposer(
|
|
|
1356
1784
|
};
|
|
1357
1785
|
|
|
1358
1786
|
const deliver = async (operation: PendingComposerOperation) => {
|
|
1787
|
+
if (durableDrafts) {
|
|
1788
|
+
const input = operation.input;
|
|
1789
|
+
if (
|
|
1790
|
+
input.expectedDraftRevision === undefined ||
|
|
1791
|
+
input.clientEventId === undefined ||
|
|
1792
|
+
input.model === undefined ||
|
|
1793
|
+
input.reasoningEffort === undefined ||
|
|
1794
|
+
input.latencyMode === undefined
|
|
1795
|
+
) {
|
|
1796
|
+
throw new ComposerStateError("The durable composer draft is not ready for delivery.");
|
|
1797
|
+
}
|
|
1798
|
+
const result = await client.submitComposerDraft(workspaceId, sessionId, {
|
|
1799
|
+
text: input.text,
|
|
1800
|
+
annotations: input.annotations ?? [],
|
|
1801
|
+
resources: input.resources ?? [],
|
|
1802
|
+
model: input.model,
|
|
1803
|
+
reasoningEffort: input.reasoningEffort,
|
|
1804
|
+
latencyMode: input.latencyMode,
|
|
1805
|
+
expectedDraftRevision: input.expectedDraftRevision,
|
|
1806
|
+
clientEventId: input.clientEventId,
|
|
1807
|
+
delivery: operation.delivery,
|
|
1808
|
+
...(input.controlEtag ? { controlEtag: input.controlEtag } : {}),
|
|
1809
|
+
...(input.modelContext ? { modelContext: input.modelContext } : {}),
|
|
1810
|
+
...(input.mcpCredentialUpdates
|
|
1811
|
+
? { mcpCredentialUpdates: input.mcpCredentialUpdates }
|
|
1812
|
+
: {}),
|
|
1813
|
+
...(input.connectionAccounts ? { connectionAccounts: input.connectionAccounts } : {}),
|
|
1814
|
+
...(input.personalResourceAttachment
|
|
1815
|
+
? { personalResourceAttachment: input.personalResourceAttachment }
|
|
1816
|
+
: {}),
|
|
1817
|
+
});
|
|
1818
|
+
adoptDraftBase(result.draft);
|
|
1819
|
+
return result;
|
|
1820
|
+
}
|
|
1359
1821
|
if (operation.delivery === "steer") {
|
|
1360
1822
|
return await client.steerMessage(workspaceId, sessionId, operation.input);
|
|
1361
1823
|
}
|
|
@@ -1364,6 +1826,9 @@ export function useComposer(
|
|
|
1364
1826
|
};
|
|
1365
1827
|
|
|
1366
1828
|
if (delivery === "steer") {
|
|
1829
|
+
if (steeringRef.current?.clientEventId !== pending?.input.clientEventId) {
|
|
1830
|
+
steeringOccurredAtRef.current = new Date().toISOString();
|
|
1831
|
+
}
|
|
1367
1832
|
setSteering({
|
|
1368
1833
|
phase: "submitting",
|
|
1369
1834
|
text: rawText,
|
|
@@ -1394,7 +1859,20 @@ export function useComposer(
|
|
|
1394
1859
|
targetKeyRef.current === ownedTargetKey &&
|
|
1395
1860
|
targetGeneration.current === ownedGeneration
|
|
1396
1861
|
) {
|
|
1397
|
-
|
|
1862
|
+
const problem = asError(cause);
|
|
1863
|
+
setError(problem);
|
|
1864
|
+
if (pending.delivery === "steer") {
|
|
1865
|
+
keepSteering = true;
|
|
1866
|
+
setSteering({
|
|
1867
|
+
phase: "failed",
|
|
1868
|
+
text: pending.input.text,
|
|
1869
|
+
clientEventId: pending.input.clientEventId ?? null,
|
|
1870
|
+
triggerEventId: null,
|
|
1871
|
+
turnId: null,
|
|
1872
|
+
error: "Not confirmed. Retry.",
|
|
1873
|
+
outcomeUnknown: true,
|
|
1874
|
+
});
|
|
1875
|
+
}
|
|
1398
1876
|
}
|
|
1399
1877
|
return false;
|
|
1400
1878
|
}
|
|
@@ -1405,6 +1883,7 @@ export function useComposer(
|
|
|
1405
1883
|
return false;
|
|
1406
1884
|
}
|
|
1407
1885
|
if (acceptedEvent) {
|
|
1886
|
+
if (durableDrafts) await loadDraft(false);
|
|
1408
1887
|
if (pending.delivery === "steer") {
|
|
1409
1888
|
keepSteering = true;
|
|
1410
1889
|
setSteering({
|
|
@@ -1419,11 +1898,7 @@ export function useComposer(
|
|
|
1419
1898
|
return true;
|
|
1420
1899
|
}
|
|
1421
1900
|
if (!pending.canRetry) {
|
|
1422
|
-
setError(
|
|
1423
|
-
new Error(
|
|
1424
|
-
"OpenGeni cannot safely retry this uncertain request after remount; reconcile the session before sending again.",
|
|
1425
|
-
),
|
|
1426
|
-
);
|
|
1901
|
+
setError(new ComposerReconciliationRequiredError());
|
|
1427
1902
|
return false;
|
|
1428
1903
|
}
|
|
1429
1904
|
try {
|
|
@@ -1441,12 +1916,29 @@ export function useComposer(
|
|
|
1441
1916
|
});
|
|
1442
1917
|
}
|
|
1443
1918
|
} catch (cause) {
|
|
1444
|
-
|
|
1919
|
+
const problem = asError(cause);
|
|
1920
|
+
const outcomeUnknown = isOutcomeUnknownError(cause);
|
|
1921
|
+
onDeliveryErrorRef.current?.(problem, pending.input, pending.delivery);
|
|
1922
|
+
if (!outcomeUnknown) {
|
|
1923
|
+
clearPending();
|
|
1924
|
+
keepSteering = false;
|
|
1925
|
+
} else if (pending.delivery === "steer") {
|
|
1926
|
+
keepSteering = true;
|
|
1927
|
+
setSteering({
|
|
1928
|
+
phase: "failed",
|
|
1929
|
+
text: pending.input.text,
|
|
1930
|
+
clientEventId: pending.input.clientEventId ?? null,
|
|
1931
|
+
triggerEventId: null,
|
|
1932
|
+
turnId: null,
|
|
1933
|
+
error: "Not confirmed. Retry.",
|
|
1934
|
+
outcomeUnknown: true,
|
|
1935
|
+
});
|
|
1936
|
+
}
|
|
1445
1937
|
if (
|
|
1446
1938
|
targetKeyRef.current === ownedTargetKey &&
|
|
1447
1939
|
targetGeneration.current === ownedGeneration
|
|
1448
1940
|
) {
|
|
1449
|
-
setError(
|
|
1941
|
+
setError(problem);
|
|
1450
1942
|
}
|
|
1451
1943
|
return false;
|
|
1452
1944
|
}
|
|
@@ -1478,10 +1970,10 @@ export function useComposer(
|
|
|
1478
1970
|
// worker rejects whitespace-only text; a file-only message therefore
|
|
1479
1971
|
// carries a minimal default so the attachments still get delivered.
|
|
1480
1972
|
pendingClientEventId.current ??= generateClientEventId();
|
|
1973
|
+
const effectiveControl = effectiveControlRef.current;
|
|
1481
1974
|
const input = composeSendInput(sendText, pendingClientEventId.current, extras, {
|
|
1482
|
-
...
|
|
1483
|
-
|
|
1484
|
-
: {}),
|
|
1975
|
+
...currentPolicy,
|
|
1976
|
+
...(effectiveControl?.controlEtag ? { controlEtag: effectiveControl.controlEtag } : {}),
|
|
1485
1977
|
...(durableDrafts && draftRef.current
|
|
1486
1978
|
? { expectedDraftRevision: draftRef.current.revision }
|
|
1487
1979
|
: {}),
|
|
@@ -1498,6 +1990,7 @@ export function useComposer(
|
|
|
1498
1990
|
text: draftAtSend,
|
|
1499
1991
|
resources: mergeResources(restoredResources, extras.resources ?? []),
|
|
1500
1992
|
annotations: cloneAnnotations(annotationsAtSend),
|
|
1993
|
+
policy: currentPolicy,
|
|
1501
1994
|
},
|
|
1502
1995
|
clearDraftOnAccept: explicit === undefined,
|
|
1503
1996
|
canRetry: true,
|
|
@@ -1528,10 +2021,20 @@ export function useComposer(
|
|
|
1528
2021
|
});
|
|
1529
2022
|
}
|
|
1530
2023
|
} catch (cause) {
|
|
2024
|
+
onDeliveryErrorRef.current?.(asError(cause), operation.input, operation.delivery);
|
|
1531
2025
|
if (!isOutcomeUnknownError(cause)) {
|
|
1532
2026
|
clearPending();
|
|
1533
2027
|
} else if (delivery === "steer") {
|
|
1534
2028
|
keepSteering = true;
|
|
2029
|
+
setSteering({
|
|
2030
|
+
phase: "failed",
|
|
2031
|
+
text: operation.input.text,
|
|
2032
|
+
clientEventId: operation.input.clientEventId ?? null,
|
|
2033
|
+
triggerEventId: null,
|
|
2034
|
+
turnId: null,
|
|
2035
|
+
error: "Not confirmed. Retry.",
|
|
2036
|
+
outcomeUnknown: true,
|
|
2037
|
+
});
|
|
1535
2038
|
}
|
|
1536
2039
|
if (
|
|
1537
2040
|
targetKeyRef.current === ownedTargetKey &&
|
|
@@ -1564,15 +2067,16 @@ export function useComposer(
|
|
|
1564
2067
|
currentDraftPayload,
|
|
1565
2068
|
durableDrafts,
|
|
1566
2069
|
onSent,
|
|
1567
|
-
options.effectiveControl?.controlEtag,
|
|
1568
2070
|
persistPayload,
|
|
1569
2071
|
restoredResources,
|
|
1570
2072
|
annotations,
|
|
2073
|
+
adoptDraftBase,
|
|
1571
2074
|
sending,
|
|
1572
2075
|
sessionId,
|
|
1573
2076
|
targetKey,
|
|
1574
2077
|
value,
|
|
1575
2078
|
workspaceId,
|
|
2079
|
+
loadDraft,
|
|
1576
2080
|
],
|
|
1577
2081
|
);
|
|
1578
2082
|
|
|
@@ -1583,6 +2087,7 @@ export function useComposer(
|
|
|
1583
2087
|
}
|
|
1584
2088
|
const rawText = explicit ?? valueRef.current;
|
|
1585
2089
|
const annotationsAtSend = cloneAnnotations(annotationsRef.current);
|
|
2090
|
+
const currentPolicy = policyRef.current;
|
|
1586
2091
|
const hasText = rawText.trim().length > 0;
|
|
1587
2092
|
const hasAnnotations = annotationsAtSend.length > 0;
|
|
1588
2093
|
const annotationsComplete = annotationsAtSend.every(
|
|
@@ -1592,6 +2097,7 @@ export function useComposer(
|
|
|
1592
2097
|
const resources = mergeResources(restoredResourcesRef.current, extras.resources ?? []);
|
|
1593
2098
|
if (
|
|
1594
2099
|
!sessionId ||
|
|
2100
|
+
!currentPolicy ||
|
|
1595
2101
|
sending ||
|
|
1596
2102
|
!annotationsComplete ||
|
|
1597
2103
|
(!hasText && !hasAnnotations && resources.length === 0) ||
|
|
@@ -1602,16 +2108,24 @@ export function useComposer(
|
|
|
1602
2108
|
}
|
|
1603
2109
|
const sendText = hasText ? rawText : hasAnnotations ? "" : FILE_ONLY_MESSAGE_TEXT;
|
|
1604
2110
|
const clientEventId = generateClientEventId();
|
|
2111
|
+
const effectiveControl = effectiveControlRef.current;
|
|
1605
2112
|
const input = composeSendInput(sendText, clientEventId, extras, {
|
|
1606
|
-
...
|
|
1607
|
-
|
|
1608
|
-
: {}),
|
|
2113
|
+
...currentPolicy,
|
|
2114
|
+
...(effectiveControl?.controlEtag ? { controlEtag: effectiveControl.controlEtag } : {}),
|
|
1609
2115
|
resources,
|
|
1610
2116
|
annotations: annotationsAtSend,
|
|
1611
2117
|
});
|
|
1612
2118
|
const currentPayload = currentDraftPayload();
|
|
2119
|
+
const hasEarlierUnsettledSend = optimisticSendsRef.current.some(
|
|
2120
|
+
(candidate) => candidate.state !== "failed" || candidate.outcomeUnknown === true,
|
|
2121
|
+
);
|
|
1613
2122
|
const operation: OptimisticSendOperation = {
|
|
1614
2123
|
clientEventId,
|
|
2124
|
+
delivery: "send",
|
|
2125
|
+
destination:
|
|
2126
|
+
effectiveControl?.state === "paused" || hasEarlierUnsettledSend
|
|
2127
|
+
? "queue"
|
|
2128
|
+
: (sendDestinationRef.current?.() ?? "chat"),
|
|
1615
2129
|
text: sendText,
|
|
1616
2130
|
annotations: annotationsAtSend,
|
|
1617
2131
|
resources,
|
|
@@ -1623,13 +2137,16 @@ export function useComposer(
|
|
|
1623
2137
|
text: explicit === undefined ? "" : valueRef.current,
|
|
1624
2138
|
resources: explicit === undefined ? [] : [...restoredResourcesRef.current],
|
|
1625
2139
|
annotations: explicit === undefined ? [] : cloneAnnotations(annotationsRef.current),
|
|
2140
|
+
policy: currentPolicy,
|
|
1626
2141
|
},
|
|
1627
2142
|
canRetry: true,
|
|
1628
2143
|
};
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
2144
|
+
// The local submission acknowledgement owns the composer before any
|
|
2145
|
+
// host callback or queued processor can observe it. Hosts commonly use
|
|
2146
|
+
// onSubmitted to remove attachment/repository state, and those updates
|
|
2147
|
+
// may synchronously re-render the controlled composer. Clear the refs
|
|
2148
|
+
// first so that handoff cannot re-project the submitted text as the next
|
|
2149
|
+
// draft. The immutable operation above still owns the exact retry input.
|
|
1633
2150
|
if (explicit === undefined) {
|
|
1634
2151
|
valueRef.current = "";
|
|
1635
2152
|
annotationsRef.current = [];
|
|
@@ -1640,13 +2157,16 @@ export function useComposer(
|
|
|
1640
2157
|
setAnnotationReviewTargetId(null);
|
|
1641
2158
|
setRestoredResources([]);
|
|
1642
2159
|
}
|
|
2160
|
+
replaceOptimisticSends((current) => [...current, operation]);
|
|
2161
|
+
queueMicrotask(processOptimisticSends);
|
|
2162
|
+
setError(null);
|
|
2163
|
+
onSubmitted?.(sendText, input);
|
|
1643
2164
|
return true;
|
|
1644
2165
|
},
|
|
1645
2166
|
[
|
|
1646
2167
|
currentDraftPayload,
|
|
1647
2168
|
dispatch,
|
|
1648
2169
|
onSubmitted,
|
|
1649
|
-
options.effectiveControl?.controlEtag,
|
|
1650
2170
|
processOptimisticSends,
|
|
1651
2171
|
replaceOptimisticSends,
|
|
1652
2172
|
sending,
|
|
@@ -1658,6 +2178,12 @@ export function useComposer(
|
|
|
1658
2178
|
|
|
1659
2179
|
const retryOptimisticMessage = useCallback(
|
|
1660
2180
|
(clientEventId: string): void => {
|
|
2181
|
+
if (sendBlockedRef.current?.() === true) return;
|
|
2182
|
+
const currentSteering = steeringRef.current;
|
|
2183
|
+
if (currentSteering?.phase === "failed" && currentSteering.clientEventId === clientEventId) {
|
|
2184
|
+
void dispatch("steer");
|
|
2185
|
+
return;
|
|
2186
|
+
}
|
|
1661
2187
|
replaceOptimisticSends((current) =>
|
|
1662
2188
|
current.map((operation) => {
|
|
1663
2189
|
if (operation.clientEventId !== clientEventId || operation.state !== "failed") {
|
|
@@ -1666,31 +2192,37 @@ export function useComposer(
|
|
|
1666
2192
|
if (operation.outcomeUnknown) {
|
|
1667
2193
|
return { ...operation, state: "sending", error: undefined };
|
|
1668
2194
|
}
|
|
2195
|
+
if (operation.retryable === false) return operation;
|
|
1669
2196
|
const nextClientEventId = generateClientEventId();
|
|
1670
|
-
const
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
2197
|
+
const currentPersonalResourceAttachment = resolveSendExtras(
|
|
2198
|
+
sendExtrasRef.current,
|
|
2199
|
+
).personalResourceAttachment;
|
|
2200
|
+
const retryInput = composeSendInput(
|
|
2201
|
+
operation.text,
|
|
2202
|
+
nextClientEventId,
|
|
2203
|
+
{
|
|
2204
|
+
...operation.input,
|
|
2205
|
+
...(currentPersonalResourceAttachment
|
|
2206
|
+
? { personalResourceAttachment: currentPersonalResourceAttachment }
|
|
2207
|
+
: {}),
|
|
2208
|
+
},
|
|
2209
|
+
{
|
|
2210
|
+
...(effectiveControlRef.current?.controlEtag
|
|
2211
|
+
? { controlEtag: effectiveControlRef.current.controlEtag }
|
|
2212
|
+
: {}),
|
|
2213
|
+
resources: operation.resources,
|
|
2214
|
+
annotations: operation.annotations,
|
|
2215
|
+
},
|
|
2216
|
+
);
|
|
1678
2217
|
return {
|
|
1679
2218
|
...operation,
|
|
1680
2219
|
clientEventId: nextClientEventId,
|
|
1681
2220
|
input: retryInput,
|
|
1682
|
-
draftPayload: operation.draftPayload
|
|
1683
|
-
? {
|
|
1684
|
-
...operation.draftPayload,
|
|
1685
|
-
model: retryExtras.model ?? operation.draftPayload.model,
|
|
1686
|
-
reasoningEffort:
|
|
1687
|
-
retryExtras.reasoningEffort ?? operation.draftPayload.reasoningEffort,
|
|
1688
|
-
latencyMode: retryExtras.latencyMode ?? operation.draftPayload.latencyMode,
|
|
1689
|
-
}
|
|
1690
|
-
: null,
|
|
2221
|
+
draftPayload: operation.draftPayload,
|
|
1691
2222
|
occurredAt: new Date().toISOString(),
|
|
1692
2223
|
state: "sending",
|
|
1693
2224
|
error: undefined,
|
|
2225
|
+
retryable: undefined,
|
|
1694
2226
|
outcomeUnknown: false,
|
|
1695
2227
|
canRetry: true,
|
|
1696
2228
|
};
|
|
@@ -1698,16 +2230,24 @@ export function useComposer(
|
|
|
1698
2230
|
);
|
|
1699
2231
|
queueMicrotask(processOptimisticSends);
|
|
1700
2232
|
},
|
|
1701
|
-
[
|
|
2233
|
+
[dispatch, processOptimisticSends, replaceOptimisticSends],
|
|
1702
2234
|
);
|
|
1703
2235
|
|
|
1704
2236
|
const removeOptimisticMessage = useCallback(
|
|
1705
2237
|
(clientEventId: string): void => {
|
|
2238
|
+
if (steeringRef.current?.clientEventId === clientEventId) {
|
|
2239
|
+
pendingOperationRef.current = null;
|
|
2240
|
+
pendingClientEventId.current = null;
|
|
2241
|
+
forgetPendingComposerOperation(pendingComposerOperationKey(workspaceId, sessionId));
|
|
2242
|
+
setSteering(null);
|
|
2243
|
+
setError(null);
|
|
2244
|
+
return;
|
|
2245
|
+
}
|
|
1706
2246
|
replaceOptimisticSends((current) =>
|
|
1707
2247
|
current.filter((operation) => operation.clientEventId !== clientEventId),
|
|
1708
2248
|
);
|
|
1709
2249
|
},
|
|
1710
|
-
[replaceOptimisticSends],
|
|
2250
|
+
[replaceOptimisticSends, sessionId, workspaceId],
|
|
1711
2251
|
);
|
|
1712
2252
|
|
|
1713
2253
|
// A send is possible with non-empty text OR with ≥1 attached resource (a
|
|
@@ -1731,33 +2271,29 @@ export function useComposer(
|
|
|
1731
2271
|
setPausing(true);
|
|
1732
2272
|
setError(null);
|
|
1733
2273
|
try {
|
|
1734
|
-
const
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
|
|
1739
|
-
|
|
2274
|
+
const clientEventId = generateClientEventId();
|
|
2275
|
+
const effectiveControl = effectiveControlRef.current;
|
|
2276
|
+
const result = await replayOutcomeUnknown(() =>
|
|
2277
|
+
client.pauseSession(workspaceId, sessionId, {
|
|
2278
|
+
clientEventId,
|
|
2279
|
+
...(reason !== undefined ? { reason } : {}),
|
|
2280
|
+
...(effectiveControl?.controlEtag
|
|
2281
|
+
? { expectedControlEtag: effectiveControl.controlEtag }
|
|
2282
|
+
: {}),
|
|
2283
|
+
}),
|
|
2284
|
+
);
|
|
1740
2285
|
if (
|
|
1741
2286
|
targetKeyRef.current === ownedTargetKey &&
|
|
1742
2287
|
targetGeneration.current === ownedGeneration
|
|
1743
2288
|
) {
|
|
1744
|
-
|
|
1745
|
-
result.interruptionCount > 0 &&
|
|
1746
|
-
result.effectiveControl.state === "paused" &&
|
|
1747
|
-
result.effectiveControl.settlement !== null
|
|
1748
|
-
? {
|
|
1749
|
-
controlVersion: result.effectiveControl.controlVersion,
|
|
1750
|
-
controlEtag: result.effectiveControl.controlEtag,
|
|
1751
|
-
}
|
|
1752
|
-
: null,
|
|
1753
|
-
);
|
|
2289
|
+
setAcceptedControl(result.effectiveControl);
|
|
1754
2290
|
}
|
|
1755
2291
|
} catch (cause) {
|
|
1756
2292
|
if (
|
|
1757
2293
|
targetKeyRef.current === ownedTargetKey &&
|
|
1758
2294
|
targetGeneration.current === ownedGeneration
|
|
1759
2295
|
) {
|
|
1760
|
-
setError(
|
|
2296
|
+
setError(normalizeError(cause));
|
|
1761
2297
|
}
|
|
1762
2298
|
} finally {
|
|
1763
2299
|
if (
|
|
@@ -1768,7 +2304,7 @@ export function useComposer(
|
|
|
1768
2304
|
}
|
|
1769
2305
|
}
|
|
1770
2306
|
},
|
|
1771
|
-
[client, workspaceId, sessionId, pausing,
|
|
2307
|
+
[client, workspaceId, sessionId, pausing, targetKey],
|
|
1772
2308
|
);
|
|
1773
2309
|
|
|
1774
2310
|
const resume = useCallback(
|
|
@@ -1779,18 +2315,29 @@ export function useComposer(
|
|
|
1779
2315
|
setResuming(true);
|
|
1780
2316
|
setError(null);
|
|
1781
2317
|
try {
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
2318
|
+
const clientEventId = generateClientEventId();
|
|
2319
|
+
const effectiveControl = effectiveControlRef.current;
|
|
2320
|
+
const result = await replayOutcomeUnknown(() =>
|
|
2321
|
+
client.resumeSession(workspaceId, sessionId, {
|
|
2322
|
+
clientEventId,
|
|
2323
|
+
...(reason !== undefined ? { reason } : {}),
|
|
2324
|
+
...(effectiveControl?.controlEtag
|
|
2325
|
+
? { expectedControlEtag: effectiveControl.controlEtag }
|
|
2326
|
+
: {}),
|
|
2327
|
+
}),
|
|
2328
|
+
);
|
|
2329
|
+
if (
|
|
2330
|
+
targetKeyRef.current === ownedTargetKey &&
|
|
2331
|
+
targetGeneration.current === ownedGeneration
|
|
2332
|
+
) {
|
|
2333
|
+
setAcceptedControl(result.effectiveControl);
|
|
2334
|
+
}
|
|
1788
2335
|
} catch (cause) {
|
|
1789
2336
|
if (
|
|
1790
2337
|
targetKeyRef.current === ownedTargetKey &&
|
|
1791
2338
|
targetGeneration.current === ownedGeneration
|
|
1792
2339
|
) {
|
|
1793
|
-
setError(
|
|
2340
|
+
setError(normalizeError(cause));
|
|
1794
2341
|
}
|
|
1795
2342
|
} finally {
|
|
1796
2343
|
if (
|
|
@@ -1801,7 +2348,7 @@ export function useComposer(
|
|
|
1801
2348
|
}
|
|
1802
2349
|
}
|
|
1803
2350
|
},
|
|
1804
|
-
[client, workspaceId, sessionId, resuming,
|
|
2351
|
+
[client, workspaceId, sessionId, resuming, targetKey],
|
|
1805
2352
|
);
|
|
1806
2353
|
|
|
1807
2354
|
const resumeScope = useCallback(
|
|
@@ -1813,19 +2360,20 @@ export function useComposer(
|
|
|
1813
2360
|
setError(null);
|
|
1814
2361
|
try {
|
|
1815
2362
|
if (option.scope === "workspace") {
|
|
1816
|
-
const workspaceBlocker =
|
|
2363
|
+
const workspaceBlocker = effectiveControlRef.current?.blockers.find(
|
|
1817
2364
|
(blocker) => blocker.kind === "workspace",
|
|
1818
2365
|
);
|
|
1819
2366
|
if (!client.setWorkspaceInferenceState) {
|
|
1820
|
-
throw new
|
|
1821
|
-
"@opengeni/react: workspace-scoped resume requires setWorkspaceInferenceState.",
|
|
1822
|
-
);
|
|
2367
|
+
throw new ComposerWorkspaceControlUnavailableError();
|
|
1823
2368
|
}
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
2369
|
+
const clientEventId = generateClientEventId();
|
|
2370
|
+
await replayOutcomeUnknown(() =>
|
|
2371
|
+
client.setWorkspaceInferenceState!(workspaceId, {
|
|
2372
|
+
action: "resume",
|
|
2373
|
+
clientEventId,
|
|
2374
|
+
...(workspaceBlocker ? { expectedRevision: workspaceBlocker.revision } : {}),
|
|
2375
|
+
}),
|
|
2376
|
+
);
|
|
1829
2377
|
} else if (option.scope === "session" && option.targetId) {
|
|
1830
2378
|
const target = await client.getQueue(workspaceId, option.targetId);
|
|
1831
2379
|
if (
|
|
@@ -1834,15 +2382,30 @@ export function useComposer(
|
|
|
1834
2382
|
) {
|
|
1835
2383
|
return;
|
|
1836
2384
|
}
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
2385
|
+
const clientEventId = generateClientEventId();
|
|
2386
|
+
await replayOutcomeUnknown(() =>
|
|
2387
|
+
client.resumeSession(workspaceId, option.targetId!, {
|
|
2388
|
+
clientEventId,
|
|
2389
|
+
expectedControlEtag: target.effectiveControl.controlEtag,
|
|
2390
|
+
}),
|
|
2391
|
+
);
|
|
1840
2392
|
} else {
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
2393
|
+
const clientEventId = generateClientEventId();
|
|
2394
|
+
const effectiveControl = effectiveControlRef.current;
|
|
2395
|
+
const result = await replayOutcomeUnknown(() =>
|
|
2396
|
+
client.resumeSession(workspaceId, sessionId, {
|
|
2397
|
+
clientEventId,
|
|
2398
|
+
...(effectiveControl?.controlEtag
|
|
2399
|
+
? { expectedControlEtag: effectiveControl.controlEtag }
|
|
2400
|
+
: {}),
|
|
2401
|
+
}),
|
|
2402
|
+
);
|
|
2403
|
+
if (
|
|
2404
|
+
targetKeyRef.current === ownedTargetKey &&
|
|
2405
|
+
targetGeneration.current === ownedGeneration
|
|
2406
|
+
) {
|
|
2407
|
+
setAcceptedControl(result.effectiveControl);
|
|
2408
|
+
}
|
|
1846
2409
|
}
|
|
1847
2410
|
} catch (cause) {
|
|
1848
2411
|
if (
|
|
@@ -1860,7 +2423,7 @@ export function useComposer(
|
|
|
1860
2423
|
}
|
|
1861
2424
|
}
|
|
1862
2425
|
},
|
|
1863
|
-
[client,
|
|
2426
|
+
[client, resuming, sessionId, targetKey, workspaceId],
|
|
1864
2427
|
);
|
|
1865
2428
|
|
|
1866
2429
|
const updateValue = useCallback(
|
|
@@ -1878,6 +2441,7 @@ export function useComposer(
|
|
|
1878
2441
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
1879
2442
|
),
|
|
1880
2443
|
annotations: annotationsRef.current,
|
|
2444
|
+
policy: policyRef.current ?? undefined,
|
|
1881
2445
|
},
|
|
1882
2446
|
);
|
|
1883
2447
|
setOptimisticDraftShadow({
|
|
@@ -1887,6 +2451,7 @@ export function useComposer(
|
|
|
1887
2451
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
1888
2452
|
),
|
|
1889
2453
|
annotations: annotationsRef.current,
|
|
2454
|
+
policy: policyRef.current ?? undefined,
|
|
1890
2455
|
});
|
|
1891
2456
|
setValue(next);
|
|
1892
2457
|
},
|
|
@@ -1908,6 +2473,7 @@ export function useComposer(
|
|
|
1908
2473
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
1909
2474
|
),
|
|
1910
2475
|
annotations: next,
|
|
2476
|
+
policy: policyRef.current ?? undefined,
|
|
1911
2477
|
},
|
|
1912
2478
|
);
|
|
1913
2479
|
setOptimisticDraftShadow({
|
|
@@ -1917,6 +2483,7 @@ export function useComposer(
|
|
|
1917
2483
|
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
1918
2484
|
),
|
|
1919
2485
|
annotations: next,
|
|
2486
|
+
policy: policyRef.current ?? undefined,
|
|
1920
2487
|
});
|
|
1921
2488
|
setAnnotations(next);
|
|
1922
2489
|
setAnnotationReviewTargetId(reviewTargetId);
|
|
@@ -1927,7 +2494,7 @@ export function useComposer(
|
|
|
1927
2494
|
const addAnnotation = useCallback(
|
|
1928
2495
|
(annotation: DraftTimelineAnnotation) => {
|
|
1929
2496
|
if (annotationsRef.current.length >= 12) {
|
|
1930
|
-
setError(new
|
|
2497
|
+
setError(new ComposerStateError("A message can include at most 12 timeline annotations."));
|
|
1931
2498
|
return;
|
|
1932
2499
|
}
|
|
1933
2500
|
const next = [...annotationsRef.current, annotation];
|
|
@@ -1958,6 +2525,69 @@ export function useComposer(
|
|
|
1958
2525
|
setAnnotationReviewTargetId(null);
|
|
1959
2526
|
}, []);
|
|
1960
2527
|
|
|
2528
|
+
const requestAnnotationReview = useCallback((id?: string) => {
|
|
2529
|
+
const current = annotationsRef.current;
|
|
2530
|
+
if (current.length === 0) {
|
|
2531
|
+
setAnnotationReviewTargetId(null);
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
if (id && current.some((annotation) => annotation.id === id)) {
|
|
2535
|
+
setAnnotationReviewTargetId(id);
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
const incomplete = current.find((annotation) => annotation.note.trim().length === 0);
|
|
2539
|
+
setAnnotationReviewTargetId(incomplete?.id ?? current[0]!.id);
|
|
2540
|
+
}, []);
|
|
2541
|
+
|
|
2542
|
+
const updatePolicy = useCallback(
|
|
2543
|
+
(next: ComposerPolicy): void => {
|
|
2544
|
+
if (targetKeyRef.current !== targetKey) return;
|
|
2545
|
+
localEditRevision.current += 1;
|
|
2546
|
+
policyRef.current = next;
|
|
2547
|
+
const shadow = {
|
|
2548
|
+
text: valueRef.current,
|
|
2549
|
+
resources: mergeResources(
|
|
2550
|
+
restoredResourcesRef.current,
|
|
2551
|
+
resolveSendExtras(sendExtrasRef.current).resources ?? [],
|
|
2552
|
+
),
|
|
2553
|
+
annotations: annotationsRef.current,
|
|
2554
|
+
policy: next,
|
|
2555
|
+
};
|
|
2556
|
+
pendingOperationRef.current = updatePendingComposerShadow(
|
|
2557
|
+
pendingOperationKey,
|
|
2558
|
+
pendingOperationRef.current,
|
|
2559
|
+
shadow,
|
|
2560
|
+
);
|
|
2561
|
+
setOptimisticDraftShadow(shadow);
|
|
2562
|
+
setPolicy(next);
|
|
2563
|
+
},
|
|
2564
|
+
[pendingOperationKey, setOptimisticDraftShadow, targetKey],
|
|
2565
|
+
);
|
|
2566
|
+
|
|
2567
|
+
const updateModel = useCallback(
|
|
2568
|
+
(model: string): void => {
|
|
2569
|
+
const current = policyRef.current;
|
|
2570
|
+
if (current) updatePolicy({ ...current, model });
|
|
2571
|
+
},
|
|
2572
|
+
[updatePolicy],
|
|
2573
|
+
);
|
|
2574
|
+
|
|
2575
|
+
const updateReasoningEffort = useCallback(
|
|
2576
|
+
(reasoningEffort: ReasoningEffort): void => {
|
|
2577
|
+
const current = policyRef.current;
|
|
2578
|
+
if (current) updatePolicy({ ...current, reasoningEffort });
|
|
2579
|
+
},
|
|
2580
|
+
[updatePolicy],
|
|
2581
|
+
);
|
|
2582
|
+
|
|
2583
|
+
const updateLatencyMode = useCallback(
|
|
2584
|
+
(latencyMode: LatencyMode): void => {
|
|
2585
|
+
const current = policyRef.current;
|
|
2586
|
+
if (current) updatePolicy({ ...current, latencyMode });
|
|
2587
|
+
},
|
|
2588
|
+
[updatePolicy],
|
|
2589
|
+
);
|
|
2590
|
+
|
|
1961
2591
|
const removeRestoredResource = useCallback(
|
|
1962
2592
|
(index: number) => {
|
|
1963
2593
|
if (targetKeyRef.current !== targetKey) return;
|
|
@@ -1971,12 +2601,14 @@ export function useComposer(
|
|
|
1971
2601
|
text: valueRef.current,
|
|
1972
2602
|
resources: mergeResources(next, resolveSendExtras(sendExtrasRef.current).resources ?? []),
|
|
1973
2603
|
annotations: annotationsRef.current,
|
|
2604
|
+
policy: policyRef.current ?? undefined,
|
|
1974
2605
|
},
|
|
1975
2606
|
);
|
|
1976
2607
|
setOptimisticDraftShadow({
|
|
1977
2608
|
text: valueRef.current,
|
|
1978
2609
|
resources: mergeResources(next, resolveSendExtras(sendExtrasRef.current).resources ?? []),
|
|
1979
2610
|
annotations: annotationsRef.current,
|
|
2611
|
+
policy: policyRef.current ?? undefined,
|
|
1980
2612
|
});
|
|
1981
2613
|
setRestoredResources(next);
|
|
1982
2614
|
},
|
|
@@ -1995,6 +2627,42 @@ export function useComposer(
|
|
|
1995
2627
|
);
|
|
1996
2628
|
}, []);
|
|
1997
2629
|
|
|
2630
|
+
const restoreOptimisticMessage = useCallback(
|
|
2631
|
+
(clientEventId: string): void => {
|
|
2632
|
+
if (
|
|
2633
|
+
targetKeyRef.current !== targetKey ||
|
|
2634
|
+
draftLoading ||
|
|
2635
|
+
pendingOperationRef.current ||
|
|
2636
|
+
hasDraftContent()
|
|
2637
|
+
)
|
|
2638
|
+
return;
|
|
2639
|
+
const operation = optimisticSendsRef.current.find(
|
|
2640
|
+
(candidate) => candidate.clientEventId === clientEventId,
|
|
2641
|
+
);
|
|
2642
|
+
if (!operation || operation.state !== "failed" || operation.outcomeUnknown) return;
|
|
2643
|
+
// Restoring is a new draft edit, not a retry. Keep the user's current model
|
|
2644
|
+
// selection and never copy credentials from the old rejected request.
|
|
2645
|
+
localEditRevision.current += 1;
|
|
2646
|
+
valueRef.current = operation.text;
|
|
2647
|
+
restoredResourcesRef.current = [...operation.resources];
|
|
2648
|
+
annotationsRef.current = cloneAnnotations(operation.annotations);
|
|
2649
|
+
setValue(operation.text);
|
|
2650
|
+
setRestoredResources(restoredResourcesRef.current);
|
|
2651
|
+
setAnnotations(annotationsRef.current);
|
|
2652
|
+
setError(null);
|
|
2653
|
+
setOptimisticDraftShadow({
|
|
2654
|
+
text: operation.text,
|
|
2655
|
+
resources: restoredResourcesRef.current,
|
|
2656
|
+
annotations: annotationsRef.current,
|
|
2657
|
+
policy: policyRef.current ?? undefined,
|
|
2658
|
+
});
|
|
2659
|
+
replaceOptimisticSends((current) =>
|
|
2660
|
+
current.filter((candidate) => candidate.clientEventId !== clientEventId),
|
|
2661
|
+
);
|
|
2662
|
+
},
|
|
2663
|
+
[draftLoading, hasDraftContent, replaceOptimisticSends, setOptimisticDraftShadow, targetKey],
|
|
2664
|
+
);
|
|
2665
|
+
|
|
1998
2666
|
const resolveDraftConflict = useCallback(
|
|
1999
2667
|
async (choice: "keep_mine" | "use_remote"): Promise<void> => {
|
|
2000
2668
|
const ownedTargetKey = targetKey;
|
|
@@ -2030,15 +2698,62 @@ export function useComposer(
|
|
|
2030
2698
|
|
|
2031
2699
|
const identityMatches = stateTargetKey === targetKey;
|
|
2032
2700
|
const visibleSteering = identityMatches ? steering : null;
|
|
2701
|
+
const visibleOptimisticMessages: ComposerOptimisticMessage[] = identityMatches
|
|
2702
|
+
? [
|
|
2703
|
+
...optimisticSends.map(
|
|
2704
|
+
({ input: _input, draftPayload: _payload, canRetry: _retry, ...item }) => item,
|
|
2705
|
+
),
|
|
2706
|
+
...(visibleSteering?.clientEventId
|
|
2707
|
+
? [
|
|
2708
|
+
{
|
|
2709
|
+
clientEventId: visibleSteering.clientEventId,
|
|
2710
|
+
delivery: "steer" as const,
|
|
2711
|
+
destination: "chat" as const,
|
|
2712
|
+
text: visibleSteering.text,
|
|
2713
|
+
annotations:
|
|
2714
|
+
pendingOperationRef.current?.delivery === "steer"
|
|
2715
|
+
? pendingOperationRef.current.annotationsAtSend
|
|
2716
|
+
: [],
|
|
2717
|
+
resources:
|
|
2718
|
+
pendingOperationRef.current?.delivery === "steer"
|
|
2719
|
+
? pendingOperationRef.current.resourcesAtSend
|
|
2720
|
+
: [],
|
|
2721
|
+
occurredAt: steeringOccurredAtRef.current,
|
|
2722
|
+
state:
|
|
2723
|
+
visibleSteering.phase === "submitting"
|
|
2724
|
+
? ("sending" as const)
|
|
2725
|
+
: visibleSteering.phase === "failed"
|
|
2726
|
+
? ("failed" as const)
|
|
2727
|
+
: ("queued" as const),
|
|
2728
|
+
turnId: visibleSteering.turnId,
|
|
2729
|
+
triggerEventId: visibleSteering.triggerEventId,
|
|
2730
|
+
...(visibleSteering.error ? { error: visibleSteering.error } : {}),
|
|
2731
|
+
...(visibleSteering.outcomeUnknown !== undefined
|
|
2732
|
+
? { outcomeUnknown: visibleSteering.outcomeUnknown }
|
|
2733
|
+
: {}),
|
|
2734
|
+
},
|
|
2735
|
+
]
|
|
2736
|
+
: []),
|
|
2737
|
+
]
|
|
2738
|
+
: [];
|
|
2033
2739
|
const reloadDraft = useCallback(async () => await loadDraft(true), [loadDraft]);
|
|
2034
2740
|
const clearError = useCallback(() => {
|
|
2035
2741
|
if (targetKeyRef.current !== targetKey) return;
|
|
2036
2742
|
setError(null);
|
|
2743
|
+
setDraftReadError(null);
|
|
2037
2744
|
setDraftConflict(null);
|
|
2038
2745
|
}, [targetKey]);
|
|
2746
|
+
// `valueRef` is the synchronous composer authority. React state exists to
|
|
2747
|
+
// schedule renders, but a concurrent autosave settlement can render the
|
|
2748
|
+
// previous state lane after a newer input event has already updated the ref.
|
|
2749
|
+
// Projecting that stale state into a controlled textarea rewrites the old
|
|
2750
|
+
// draft for one commit, which moves the caret and can briefly resurrect a
|
|
2751
|
+
// submitted message. Never let an incidental render outrank the latest
|
|
2752
|
+
// local lifecycle decision.
|
|
2753
|
+
const visibleValue = identityMatches ? valueRef.current : "";
|
|
2039
2754
|
|
|
2040
2755
|
return {
|
|
2041
|
-
value:
|
|
2756
|
+
value: visibleValue,
|
|
2042
2757
|
setValue: updateValue,
|
|
2043
2758
|
annotations: identityMatches ? annotations : [],
|
|
2044
2759
|
addAnnotation,
|
|
@@ -2046,33 +2761,34 @@ export function useComposer(
|
|
|
2046
2761
|
removeAnnotation,
|
|
2047
2762
|
annotationReviewTargetId: identityMatches ? annotationReviewTargetId : null,
|
|
2048
2763
|
clearAnnotationReviewTarget,
|
|
2764
|
+
requestAnnotationReview,
|
|
2049
2765
|
hasDraftContent,
|
|
2050
2766
|
send,
|
|
2051
|
-
optimisticMessages:
|
|
2052
|
-
? optimisticSends.map(
|
|
2053
|
-
({ input: _input, draftPayload: _payload, canRetry: _retry, ...item }) => item,
|
|
2054
|
-
)
|
|
2055
|
-
: [],
|
|
2767
|
+
optimisticMessages: visibleOptimisticMessages,
|
|
2056
2768
|
retryOptimisticMessage,
|
|
2769
|
+
restoreOptimisticMessage:
|
|
2770
|
+
!draftLoading && !pendingOperationRef.current && !hasDraftContent()
|
|
2771
|
+
? restoreOptimisticMessage
|
|
2772
|
+
: undefined,
|
|
2057
2773
|
removeOptimisticMessage,
|
|
2058
2774
|
steer,
|
|
2059
2775
|
steering: visibleSteering,
|
|
2060
2776
|
stoppingAttempt: identityMatches
|
|
2061
|
-
?
|
|
2062
|
-
? "
|
|
2063
|
-
:
|
|
2064
|
-
? "previous"
|
|
2065
|
-
: null
|
|
2777
|
+
? visibleSteering?.phase === "accepted" && visibleSteering.stoppingPreviousAttempt === true
|
|
2778
|
+
? "previous"
|
|
2779
|
+
: null
|
|
2066
2780
|
: null,
|
|
2067
2781
|
sending: identityMatches ? sending : false,
|
|
2068
2782
|
canSend:
|
|
2069
2783
|
identityMatches &&
|
|
2070
2784
|
Boolean(sessionId) &&
|
|
2071
2785
|
!sending &&
|
|
2786
|
+
!draftLoading &&
|
|
2787
|
+
policy !== null &&
|
|
2072
2788
|
sendBlockedRef.current?.() !== true &&
|
|
2073
2789
|
annotationsComplete &&
|
|
2074
2790
|
(hasPendingOperation ||
|
|
2075
|
-
|
|
2791
|
+
visibleValue.trim().length > 0 ||
|
|
2076
2792
|
hasReadyResources ||
|
|
2077
2793
|
annotations.length > 0),
|
|
2078
2794
|
pause,
|
|
@@ -2080,18 +2796,23 @@ export function useComposer(
|
|
|
2080
2796
|
resume,
|
|
2081
2797
|
resumeScope,
|
|
2082
2798
|
resuming: identityMatches ? resuming : false,
|
|
2799
|
+
effectiveControl: identityMatches ? effectiveControlRef.current : null,
|
|
2083
2800
|
draft: identityMatches ? draft : null,
|
|
2084
2801
|
draftRevision: identityMatches ? (draft?.revision ?? 0) : 0,
|
|
2085
2802
|
draftLoading: identityMatches ? draftLoading : Boolean(sessionId) && durableDrafts,
|
|
2086
2803
|
draftSaving: identityMatches ? draftSaving : false,
|
|
2087
2804
|
draftConflict: identityMatches ? draftConflict : null,
|
|
2805
|
+
policy: identityMatches ? policy : null,
|
|
2806
|
+
setModel: updateModel,
|
|
2807
|
+
setReasoningEffort: updateReasoningEffort,
|
|
2808
|
+
setLatencyMode: updateLatencyMode,
|
|
2088
2809
|
draftPersistence: durableDrafts ? "durable" : "disabled",
|
|
2089
2810
|
applyDraft,
|
|
2090
2811
|
reloadDraft,
|
|
2091
2812
|
resolveDraftConflict,
|
|
2092
2813
|
restoredResources: identityMatches ? restoredResources : [],
|
|
2093
2814
|
removeRestoredResource,
|
|
2094
|
-
error: identityMatches ? error : null,
|
|
2815
|
+
error: identityMatches ? (error ?? draftReadError) : null,
|
|
2095
2816
|
clearError,
|
|
2096
2817
|
};
|
|
2097
2818
|
}
|
|
@@ -2160,58 +2881,42 @@ function isOutcomeUnknownError(cause: unknown): boolean {
|
|
|
2160
2881
|
);
|
|
2161
2882
|
}
|
|
2162
2883
|
|
|
2884
|
+
async function replayOutcomeUnknown<T>(command: () => Promise<T>): Promise<T> {
|
|
2885
|
+
try {
|
|
2886
|
+
return await command();
|
|
2887
|
+
} catch (cause) {
|
|
2888
|
+
if (!isOutcomeUnknownError(cause)) throw cause;
|
|
2889
|
+
return await command();
|
|
2890
|
+
}
|
|
2891
|
+
}
|
|
2892
|
+
|
|
2163
2893
|
function asError(cause: unknown): Error {
|
|
2164
|
-
return
|
|
2894
|
+
return normalizeError(cause);
|
|
2165
2895
|
}
|
|
2166
2896
|
|
|
2167
|
-
function
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
// Production queue OCC returns `DRAFT_CHANGED`. Older/SDK-shaped 409s may
|
|
2171
|
-
// omit code or use the generic conflict labels — all are recoverable OCC.
|
|
2172
|
-
const code = apiError.code;
|
|
2897
|
+
function promptRoutingFromAcceptedEvent(
|
|
2898
|
+
event: SessionEvent | null,
|
|
2899
|
+
): "accepted_for_execution" | "queued_for_execution" | "accepted_for_steering" | null {
|
|
2173
2900
|
if (
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
code === "idempotency_conflict"
|
|
2901
|
+
typeof event?.payload !== "object" ||
|
|
2902
|
+
event.payload === null ||
|
|
2903
|
+
Array.isArray(event.payload)
|
|
2178
2904
|
) {
|
|
2179
|
-
return
|
|
2905
|
+
return null;
|
|
2180
2906
|
}
|
|
2181
|
-
|
|
2907
|
+
const routing = (event.payload as Record<string, unknown>).routing;
|
|
2908
|
+
return routing === "accepted_for_execution" ||
|
|
2909
|
+
routing === "queued_for_execution" ||
|
|
2910
|
+
routing === "accepted_for_steering"
|
|
2911
|
+
? routing
|
|
2912
|
+
: null;
|
|
2182
2913
|
}
|
|
2183
2914
|
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
async function saveComposerDraftWithStaleRetry(input: {
|
|
2190
|
-
client: {
|
|
2191
|
-
getComposerDraft: (workspaceId: string, sessionId: string) => Promise<ComposerDraft>;
|
|
2192
|
-
saveComposerDraft: (
|
|
2193
|
-
workspaceId: string,
|
|
2194
|
-
sessionId: string,
|
|
2195
|
-
request: SaveComposerDraftRequest,
|
|
2196
|
-
) => Promise<ComposerDraft>;
|
|
2197
|
-
};
|
|
2198
|
-
workspaceId: string;
|
|
2199
|
-
sessionId: string;
|
|
2200
|
-
request: SaveComposerDraftRequest;
|
|
2201
|
-
onAdoptRemote: (remote: ComposerDraft) => void;
|
|
2202
|
-
}): Promise<ComposerDraft> {
|
|
2203
|
-
try {
|
|
2204
|
-
return await input.client.saveComposerDraft(input.workspaceId, input.sessionId, input.request);
|
|
2205
|
-
} catch (cause) {
|
|
2206
|
-
const problem = asError(cause);
|
|
2207
|
-
if (!isDraftConflictError(problem)) throw problem;
|
|
2208
|
-
const remote = await input.client.getComposerDraft(input.workspaceId, input.sessionId);
|
|
2209
|
-
input.onAdoptRemote(remote);
|
|
2210
|
-
return await input.client.saveComposerDraft(input.workspaceId, input.sessionId, {
|
|
2211
|
-
...input.request,
|
|
2212
|
-
expectedRevision: remote.revision,
|
|
2213
|
-
});
|
|
2214
|
-
}
|
|
2915
|
+
function isDraftConflictError(error: Error): boolean {
|
|
2916
|
+
const apiError = error as Partial<OpenGeniApiError>;
|
|
2917
|
+
return (
|
|
2918
|
+
apiError.status === 409 && apiError.outcomeUnknown !== true && apiError.code === "DRAFT_CHANGED"
|
|
2919
|
+
);
|
|
2215
2920
|
}
|
|
2216
2921
|
|
|
2217
2922
|
function draftPayload(draft: ComposerDraft): SaveComposerDraftRequest {
|
|
@@ -2222,7 +2927,15 @@ function draftPayload(draft: ComposerDraft): SaveComposerDraftRequest {
|
|
|
2222
2927
|
annotations: draft.annotations ?? [],
|
|
2223
2928
|
model: draft.model,
|
|
2224
2929
|
reasoningEffort: draft.reasoningEffort,
|
|
2225
|
-
latencyMode: draft.latencyMode
|
|
2930
|
+
latencyMode: draft.latencyMode,
|
|
2931
|
+
};
|
|
2932
|
+
}
|
|
2933
|
+
|
|
2934
|
+
function policyFromDraft(draft: ComposerDraft): ComposerPolicy {
|
|
2935
|
+
return {
|
|
2936
|
+
model: draft.model,
|
|
2937
|
+
reasoningEffort: draft.reasoningEffort,
|
|
2938
|
+
latencyMode: draft.latencyMode,
|
|
2226
2939
|
};
|
|
2227
2940
|
}
|
|
2228
2941
|
|
|
@@ -2231,16 +2944,17 @@ function composerDraftPayload(
|
|
|
2231
2944
|
text: string,
|
|
2232
2945
|
restoredResources: ResourceRef[],
|
|
2233
2946
|
annotations: DraftTimelineAnnotation[],
|
|
2234
|
-
|
|
2947
|
+
policy: ComposerPolicy,
|
|
2948
|
+
additionalResources: ResourceRef[],
|
|
2235
2949
|
): SaveComposerDraftRequest {
|
|
2236
2950
|
return {
|
|
2237
2951
|
expectedRevision: base.revision,
|
|
2238
2952
|
text,
|
|
2239
|
-
resources: mergeResources(restoredResources,
|
|
2953
|
+
resources: mergeResources(restoredResources, additionalResources),
|
|
2240
2954
|
annotations,
|
|
2241
|
-
model:
|
|
2242
|
-
reasoningEffort:
|
|
2243
|
-
latencyMode:
|
|
2955
|
+
model: policy.model,
|
|
2956
|
+
reasoningEffort: policy.reasoningEffort,
|
|
2957
|
+
latencyMode: policy.latencyMode,
|
|
2244
2958
|
};
|
|
2245
2959
|
}
|
|
2246
2960
|
|