@athenaintel/react 0.48.0 → 0.52.1
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 +68 -10
- package/dist/agent-runtime/src/call-recap.cjs +319 -0
- package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
- package/dist/agent-runtime/src/call-recap.d.ts +142 -0
- package/dist/agent-runtime/src/call-recap.js +319 -0
- package/dist/agent-runtime/src/call-recap.js.map +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
- package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
- package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
- package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
- package/dist/agent-runtime/src/chat-outbox.js +46 -5
- package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
- package/dist/agent-runtime/src/conversation-index.js.map +1 -1
- package/dist/agent-runtime/src/conversation-list.cjs +2 -1
- package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
- package/dist/agent-runtime/src/conversation-list.js +2 -1
- package/dist/agent-runtime/src/conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/desktop-handoff.d.ts +10 -0
- package/dist/agent-runtime/src/edit-command.cjs +21 -0
- package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
- package/dist/agent-runtime/src/edit-command.d.ts +8 -0
- package/dist/agent-runtime/src/edit-command.js +21 -0
- package/dist/agent-runtime/src/edit-command.js.map +1 -1
- package/dist/agent-runtime/src/handoff-arrival.cjs +19 -0
- package/dist/agent-runtime/src/handoff-arrival.cjs.map +1 -1
- package/dist/agent-runtime/src/handoff-arrival.d.ts +18 -3
- package/dist/agent-runtime/src/handoff-arrival.js +20 -1
- package/dist/agent-runtime/src/handoff-arrival.js.map +1 -1
- package/dist/agent-runtime/src/handoff-cursor-id.cjs +6 -0
- package/dist/agent-runtime/src/handoff-cursor-id.cjs.map +1 -0
- package/dist/agent-runtime/src/handoff-cursor-id.d.ts +12 -0
- package/dist/agent-runtime/src/handoff-cursor-id.js +6 -0
- package/dist/agent-runtime/src/handoff-cursor-id.js.map +1 -0
- package/dist/agent-runtime/src/handoff-cursor.cjs +88 -0
- package/dist/agent-runtime/src/handoff-cursor.cjs.map +1 -0
- package/dist/agent-runtime/src/handoff-cursor.d.ts +120 -0
- package/dist/agent-runtime/src/handoff-cursor.js +88 -0
- package/dist/agent-runtime/src/handoff-cursor.js.map +1 -0
- package/dist/agent-runtime/src/index.d.ts +9 -7
- package/dist/agent-runtime/src/interrupts.cjs +28 -1
- package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
- package/dist/agent-runtime/src/interrupts.d.ts +21 -1
- package/dist/agent-runtime/src/interrupts.js +28 -1
- package/dist/agent-runtime/src/interrupts.js.map +1 -1
- package/dist/agent-runtime/src/model-context.cjs +3 -2
- package/dist/agent-runtime/src/model-context.cjs.map +1 -1
- package/dist/agent-runtime/src/model-context.d.ts +6 -1
- package/dist/agent-runtime/src/model-context.js +3 -2
- package/dist/agent-runtime/src/model-context.js.map +1 -1
- package/dist/agent-runtime/src/runtime.d.ts +3 -2
- package/dist/agent-runtime/src/send-recovery.cjs +37 -3
- package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
- package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
- package/dist/agent-runtime/src/send-recovery.js +37 -3
- package/dist/agent-runtime/src/send-recovery.js.map +1 -1
- package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +14 -8
- package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +227 -5
- package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
- package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
- package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
- package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
- package/dist/agent-runtime/src/use-conversation-list.js +3 -2
- package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
- package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
- package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
- package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
- package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
- package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +8 -3
- package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
- package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +7 -0
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js +9 -4
- package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
- package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +13 -2
- package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
- package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
- package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
- package/dist/agent-runtime/src/voice-earcon.js +139 -0
- package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
- package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-media.d.ts +14 -0
- package/dist/agent-runtime/src/voice-media.js.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
- package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
- package/dist/agent-runtime/src/voice-presentation.js +2 -0
- package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
- package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
- package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
- package/dist/assets/AthenaAssetEmbed.js.map +1 -1
- package/dist/assets/use-asset-embed.cjs.map +1 -1
- package/dist/assets/use-asset-embed.d.ts +12 -5
- package/dist/assets/use-asset-embed.js.map +1 -1
- package/dist/chat/AthenaChat.cjs +27 -12
- package/dist/chat/AthenaChat.cjs.map +1 -1
- package/dist/chat/AthenaChat.d.ts +13 -0
- package/dist/chat/AthenaChat.js +27 -12
- package/dist/chat/AthenaChat.js.map +1 -1
- package/dist/chat/AthenaPlanStrip.cjs +97 -0
- package/dist/chat/AthenaPlanStrip.cjs.map +1 -0
- package/dist/chat/AthenaPlanStrip.d.ts +15 -0
- package/dist/chat/AthenaPlanStrip.js +97 -0
- package/dist/chat/AthenaPlanStrip.js.map +1 -0
- package/dist/chat/StatewireApprovalCard.cjs +41 -6
- package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
- package/dist/chat/StatewireApprovalCard.d.ts +18 -1
- package/dist/chat/StatewireApprovalCard.js +42 -7
- package/dist/chat/StatewireApprovalCard.js.map +1 -1
- package/dist/chat/StatewireQueuedMessages.cjs +56 -6
- package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
- package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
- package/dist/chat/StatewireQueuedMessages.js +58 -8
- package/dist/chat/StatewireQueuedMessages.js.map +1 -1
- package/dist/chat/WaitingOnYouBanner.cjs +6 -2
- package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
- package/dist/chat/WaitingOnYouBanner.js +7 -3
- package/dist/chat/WaitingOnYouBanner.js.map +1 -1
- package/dist/chat/chat-voice-controls.cjs +3 -1
- package/dist/chat/chat-voice-controls.cjs.map +1 -1
- package/dist/chat/chat-voice-controls.d.ts +6 -1
- package/dist/chat/chat-voice-controls.js +3 -1
- package/dist/chat/chat-voice-controls.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.cjs +4 -2
- package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingCard.js +4 -2
- package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +4 -2
- package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
- package/dist/chat/super-grouping/SuperGroupingTranscript.js +4 -2
- package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.cjs +6 -4
- package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
- package/dist/chat/super-grouping/activity-timeline.d.ts +9 -1
- package/dist/chat/super-grouping/activity-timeline.js +6 -4
- package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
- package/dist/chat/super-grouping/index.d.ts +1 -1
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs +17 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs.map +1 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.d.ts +8 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.js +17 -0
- package/dist/chat/super-grouping/use-earlier-steps-unloaded.js.map +1 -0
- package/dist/chat-ui/index.cjs +11 -0
- package/dist/chat-ui/index.cjs.map +1 -1
- package/dist/chat-ui/index.d.ts +3 -1
- package/dist/chat-ui/index.js +12 -1
- package/dist/chat-ui/index.js.map +1 -1
- package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
- package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
- package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
- package/dist/chat-ui/messages/from-the-call-card.js +124 -0
- package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
- package/dist/chat-ui/rich-text/chat-link.js +4 -3
- package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
- package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
- package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
- package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
- package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.cjs +4 -0
- package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
- package/dist/chat-ui/thread/chat-thread-shell.d.ts +4 -0
- package/dist/chat-ui/thread/chat-thread-shell.js +4 -0
- package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
- package/dist/chat-ui/thread/handoff-cursor.cjs +216 -0
- package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -0
- package/dist/chat-ui/thread/handoff-cursor.d.ts +57 -0
- package/dist/chat-ui/thread/handoff-cursor.js +216 -0
- package/dist/chat-ui/thread/handoff-cursor.js.map +1 -0
- package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -0
- package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
- package/dist/chat-ui/thread/statewire-history-edge.d.ts +2 -0
- package/dist/chat-ui/thread/statewire-history-edge.js +3 -1
- package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
- package/dist/collab/client.cjs +15 -3
- package/dist/collab/client.cjs.map +1 -1
- package/dist/collab/client.d.ts +4 -2
- package/dist/collab/client.js +16 -4
- package/dist/collab/client.js.map +1 -1
- package/dist/composer/index.d.ts +1 -1
- package/dist/composer/useReadComposerDraft.cjs +26 -7
- package/dist/composer/useReadComposerDraft.cjs.map +1 -1
- package/dist/composer/useReadComposerDraft.d.ts +23 -1
- package/dist/composer/useReadComposerDraft.js +27 -8
- package/dist/composer/useReadComposerDraft.js.map +1 -1
- package/dist/index.cjs +5 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +5 -2
- package/dist/index.js +6 -1
- package/dist/index.js.map +1 -1
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +207 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +141 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js +207 -0
- package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
- package/dist/keryx-http/src/update-attributions.cjs +34 -4
- package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
- package/dist/keryx-http/src/update-attributions.d.ts +36 -4
- package/dist/keryx-http/src/update-attributions.js +35 -5
- package/dist/keryx-http/src/update-attributions.js.map +1 -1
- package/dist/provider/AthenaProvider.cjs +4 -0
- package/dist/provider/AthenaProvider.cjs.map +1 -1
- package/dist/provider/AthenaProvider.d.ts +11 -1
- package/dist/provider/AthenaProvider.js +4 -0
- package/dist/provider/AthenaProvider.js.map +1 -1
- package/dist/react/package.json.cjs +1 -1
- package/dist/react/package.json.js +1 -1
- package/dist/styles.css +1 -1
- package/dist/threads/api.cjs.map +1 -1
- package/dist/threads/api.d.ts +15 -0
- package/dist/threads/api.js.map +1 -1
- package/dist/threads/conversation-list-fetcher.cjs +13 -5
- package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
- package/dist/threads/conversation-list-fetcher.d.ts +4 -1
- package/dist/threads/conversation-list-fetcher.js +13 -5
- package/dist/threads/conversation-list-fetcher.js.map +1 -1
- package/dist/threads/statewire-thread-list.cjs +28 -6
- package/dist/threads/statewire-thread-list.cjs.map +1 -1
- package/dist/threads/statewire-thread-list.d.ts +20 -8
- package/dist/threads/statewire-thread-list.js +28 -6
- package/dist/threads/statewire-thread-list.js.map +1 -1
- package/dist/threads/use-athena-sessions.cjs +27 -5
- package/dist/threads/use-athena-sessions.cjs.map +1 -1
- package/dist/threads/use-athena-sessions.d.ts +10 -5
- package/dist/threads/use-athena-sessions.js +28 -6
- package/dist/threads/use-athena-sessions.js.map +1 -1
- package/dist/threads/use-rest-conversation-history.d.ts +1 -0
- package/dist/tools/write-todos-tool-ui.cjs +8 -0
- package/dist/tools/write-todos-tool-ui.cjs.map +1 -1
- package/dist/tools/write-todos-tool-ui.d.ts +2 -0
- package/dist/tools/write-todos-tool-ui.js +8 -0
- package/dist/tools/write-todos-tool-ui.js.map +1 -1
- package/dist/workbench/session-workbench-panes.cjs +1 -8
- package/dist/workbench/session-workbench-panes.cjs.map +1 -1
- package/dist/workbench/session-workbench-panes.js +2 -9
- package/dist/workbench/session-workbench-panes.js.map +1 -1
- package/package.json +2 -2
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"send-recovery.js","sources":["../../../../agent-runtime/src/send-recovery.ts"],"sourcesContent":["import type {\n StatewireDiagnosticsCommandUpdate,\n StatewireDiagnosticsRecorder,\n} from './statewire-diagnostics';\nimport { isDeepAgentStateHydrated } from './statewire-state';\nimport type { StatewireFailedSendEntry, StatewireFailedSendLane } from './thread-extras';\n\n/**\n * Transport-level send durability for the deep-agent thread (the successor to\n * Olympus' localStorage outbox, #29368 → #31504).\n *\n * The durable journal itself is the statewire session record: every command is\n * persisted through the mounted session storage from `send()` until the server\n * durably admits or judges it, and a reload resends survivors on the SAME lane\n * seq (exactly-once — the server absorbs replayed seqs at or below its\n * watermark). What the record cannot recover on its own is a send the client\n * had to settle without a server verdict — a proven session loss or a record\n * reset settles it `lost`/`aborted` and RETAINS it in the record's failed lane\n * (message-carrying sends only), where the vendor drops it as soon as a\n * canonical snapshot shows the message landed (`failureCovered`).\n *\n * This controller turns that persisted failed lane back into live sends: once\n * the thread snapshot has hydrated (so covered entries are already gone), every\n * failed send the server NEVER judged is re-dispatched through the thread's own\n * `retryFailed` — same client-minted message id, re-anchored against the\n * current thread — which the host dedupes by id (a still-queued id re-places,\n * a durable one rejects `duplicate-id`). Server-judged fates (`rejected`,\n * `failed`, `crashed`, `expired`) are the user's decision and stay in the\n * manual retry/discard affordance.\n *\n * Ordering: an `aborted` send stamped `predecessorLost` was never attempted\n * because an earlier send of the same batch WAS attempted and settled `lost`\n * — it may or may not have run. Re-dispatching the successor first could land\n * it ahead of a predecessor that later turns out to have landed too, so the\n * successor is held until every preceding `lost` predecessor has settled: it\n * was covered by a snapshot, its retry was judged by the server, or the user\n * discarded it.\n */\n\n/** Fates the server never saw: the send died client-side (unmount, stream\n * gone, session lost) and is safe to re-dispatch under its original message\n * id. The complement — rejected/failed/crashed/expired — is server-judged. */\nconst AUTO_RETRY_FATES: ReadonlySet<string> = new Set(['lost', 'aborted']);\n\n/** A failed send is re-dispatched at most this many times per attach; a\n * persistent failure (repeated session losses) stays in the manual lane. */\nconst MAX_AUTO_RETRIES_PER_MESSAGE = 3;\n\n/** Persisted failures older than this stay manual: auto-sending a message the\n * user abandoned a day ago is worse than surfacing it for a decision. Mirrors\n * the retired outbox's retention window. */\nconst MAX_AUTO_RETRY_AGE_MS = 24 * 60 * 60 * 1000;\n\nexport interface DeepAgentSendRecoveryOptions {\n /**\n * Reads the thread's failed-send lane. `null` while it is unavailable (the\n * client has not mounted yet, or its extras are not a statewire thread) —\n * the controller stays armed and retries on the next state change.\n */\n lane: () => StatewireFailedSendLane | null;\n /**\n * Persisted failure stamps (message id → epoch ms of the original send) for\n * the freshness gate. `null`/absent means no persistence is readable — every\n * lane entry then counts as fresh (an in-memory failed lane can only hold\n * failures from this page session).\n */\n failureStamps?: (() => ReadonlyMap<string, number> | null) | undefined;\n /** Transport lifecycle recorder; read per event so a late-mounted recorder still sees retries. */\n diagnostics?: (() => StatewireDiagnosticsRecorder | undefined) | undefined;\n /** Injectable clock (tests). */\n now?: () => number;\n /** Sweep scheduler; defaults to `queueMicrotask` so a sweep never dispatches\n * from inside a statewire flush or a React render. */\n schedule?: (task: () => void) => void;\n}\n\nexport interface DeepAgentSendRecovery {\n /** Feed every (normalized) statewire state change; hydration arms the mount sweep. */\n observeStateChange(state: unknown): void;\n /** Feed every command-lifecycle update; a never-server-judged settle arms a sweep. */\n observeCommand(update: StatewireDiagnosticsCommandUpdate): void;\n /** Feed client notifications too: failed history can publish after the\n * command settles, without any change to the canonical thread snapshot. */\n observeFailedLaneChange(): void;\n dispose(): void;\n}\n\n/** Whether a settle failure is a client-side death the recovery re-dispatches\n * (the server never judged the command). Exported for host-side telemetry. */\nexport function shouldAutoRetryFailedSend(failure: { fate: string } | undefined): boolean {\n return failure !== undefined && AUTO_RETRY_FATES.has(failure.fate);\n}\n\n/** Whether a failed entry must wait for a lost predecessor to settle first. */\nfunction awaitsLostPredecessor(entry: StatewireFailedSendEntry): boolean {\n return entry.sendFailure.fate === 'aborted' && entry.sendFailure.predecessorLost === true;\n}\n\n/** The client-minted message id a run send carries, off a command update's params. */\nfunction commandMessageId(update: StatewireDiagnosticsCommandUpdate): string | null {\n const first = update.params?.[0] as { message?: { id?: unknown } } | undefined;\n const id = first?.message?.id;\n return typeof id === 'string' ? id : null;\n}\n\nexport function createDeepAgentSendRecovery(\n options: DeepAgentSendRecoveryOptions\n): DeepAgentSendRecovery {\n const now = options.now ?? Date.now;\n const schedule = options.schedule ?? ((task: () => void) => queueMicrotask(task));\n\n let disposed = false;\n let hydrated = false;\n /** Work may exist: set at hydration and on retryable settles, cleared by a\n * sweep that could actually read the lane. */\n let armed = false;\n let sweepScheduled = false;\n /** Retry budget per message id, for this controller's attach lifetime. */\n const attempts = new Map<string, number>();\n /** Message ids already diagnosed as stale/exhausted, so the lane re-scan\n * stays quiet about them. */\n const surfacedSkips = new Set<string>();\n /** Message ids whose deferral behind a lost predecessor was diagnosed once. */\n const surfacedDeferrals = new Set<string>();\n /** Re-dispatched sends still awaiting the server's verdict: a successor\n * stamped `predecessorLost` waits while any of these is pending. */\n const inFlight = new Set<string>();\n /** Successors held back by the last sweep; a non-empty set keeps the\n * controller armed so the next settle or state change re-sweeps. */\n const deferred = new Set<string>();\n\n const record = (event: string, data?: Record<string, unknown>): void => {\n options.diagnostics?.()?.record(event, data);\n };\n\n const sweep = (): void => {\n sweepScheduled = false;\n if (disposed || !hydrated || !armed) return;\n const lane = options.lane();\n // No readable lane yet: stay armed so the next state change re-sweeps.\n if (lane === null) return;\n armed = false;\n deferred.clear();\n const stamps = options.failureStamps?.() ?? null;\n // A `lost` entry earlier in lane order that this sweep leaves unsettled\n // (skipped as stale/exhausted, or re-dispatched and now in flight).\n let lostPredecessorPending = false;\n for (const entry of lane.failed) {\n if (!shouldAutoRetryFailedSend(entry.sendFailure)) continue;\n // Shared retry/discard may still be waiting for the storage lock. The\n // visible lane can keep this entry until then; do not spend another\n // attempt on a send whose previous retry is still awaiting a verdict.\n if (inFlight.has(entry.id)) {\n if (entry.sendFailure.fate === 'lost') lostPredecessorPending = true;\n continue;\n }\n if (awaitsLostPredecessor(entry) && (lostPredecessorPending || inFlight.size > 0)) {\n deferred.add(entry.id);\n if (!surfacedDeferrals.has(entry.id)) {\n surfacedDeferrals.add(entry.id);\n record('send-recovery/deferred', { messageId: entry.id });\n }\n continue;\n }\n const isLost = entry.sendFailure.fate === 'lost';\n const stamp = stamps?.get(entry.id);\n if (stamp !== undefined && now() - stamp > MAX_AUTO_RETRY_AGE_MS) {\n if (!surfacedSkips.has(entry.id)) {\n surfacedSkips.add(entry.id);\n record('send-recovery/stale', { messageId: entry.id, ageMs: Math.round(now() - stamp) });\n }\n if (isLost) lostPredecessorPending = true;\n continue;\n }\n const attempt = (attempts.get(entry.id) ?? 0) + 1;\n if (attempt > MAX_AUTO_RETRIES_PER_MESSAGE) {\n if (!surfacedSkips.has(entry.id)) {\n surfacedSkips.add(entry.id);\n record('send-recovery/exhausted', { messageId: entry.id });\n }\n if (isLost) lostPredecessorPending = true;\n continue;\n }\n try {\n lane.retryFailed(entry.id);\n attempts.set(entry.id, attempt);\n inFlight.add(entry.id);\n if (isLost) lostPredecessorPending = true;\n record('send-recovery/retry', {\n messageId: entry.id,\n fate: entry.sendFailure.fate,\n attempt,\n });\n } catch {\n // The entry settled or was covered between the lane read and the\n // retry (the vendor drops covered failures against every snapshot);\n // nothing was dispatched.\n record('send-recovery/raced', { messageId: entry.id });\n }\n }\n // Held successors must be revisited once their predecessors settle.\n if (deferred.size > 0) armed = true;\n };\n\n const scheduleSweep = (): void => {\n if (disposed || sweepScheduled || !hydrated || !armed) return;\n sweepScheduled = true;\n schedule(sweep);\n };\n\n return {\n observeStateChange(state) {\n if (disposed) return;\n if (!hydrated && isDeepAgentStateHydrated(state)) {\n // First real snapshot: the vendor's failureCovered pass has had its\n // chance to drop entries whose message actually landed, so whatever\n // remains provably never reached the thread.\n hydrated = true;\n armed = true;\n }\n scheduleSweep();\n },\n observeCommand(update) {\n if (disposed || update.status !== 'settled') return;\n const messageId = commandMessageId(update);\n if (messageId !== null) inFlight.delete(messageId);\n // A never-server-judged settle means new work; any settle while\n // successors are held may be the predecessor they wait on.\n if (!shouldAutoRetryFailedSend(update.failure) && deferred.size === 0) return;\n armed = true;\n scheduleSweep();\n },\n observeFailedLaneChange() {\n if (disposed) return;\n armed = true;\n scheduleSweep();\n },\n dispose() {\n disposed = true;\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"send-recovery.js","sources":["../../../../agent-runtime/src/send-recovery.ts"],"sourcesContent":["import type {\n StatewireDiagnosticsCommandUpdate,\n StatewireDiagnosticsRecorder,\n} from './statewire-diagnostics';\nimport { isDeepAgentStateHydrated } from './statewire-state';\nimport type { StatewireFailedSendEntry, StatewireFailedSendLane } from './thread-extras';\n\n/**\n * Transport-level send durability for the deep-agent thread (the successor to\n * Olympus' localStorage outbox, #29368 → #31504).\n *\n * The durable journal itself is the statewire session record: every command is\n * persisted through the mounted session storage from `send()` until the server\n * durably admits or judges it, and a reload resends survivors on the SAME lane\n * seq (exactly-once — the server absorbs replayed seqs at or below its\n * watermark). What the record cannot recover on its own is a send the client\n * had to settle without a server verdict — a proven session loss or a record\n * reset settles it `lost`/`aborted` and RETAINS it in the record's failed lane\n * (message-carrying sends only), where the vendor drops it as soon as a\n * canonical snapshot shows the message landed (`failureCovered`).\n *\n * This controller turns that persisted failed lane back into live sends: once\n * the thread snapshot has hydrated (so covered entries are already gone), every\n * failed send the server NEVER judged is re-dispatched through the thread's own\n * `retryFailed` — same client-minted message id, re-anchored against the\n * current thread — which the host dedupes by id (a still-queued id re-places,\n * a durable one rejects `duplicate-id`). Server-judged fates (`rejected`,\n * `failed`, `crashed`, `expired`) are the user's decision and stay in the\n * manual retry/discard affordance.\n *\n * Ordering: an `aborted` send stamped `predecessorLost` was never attempted\n * because an earlier send of the same batch WAS attempted and settled `lost`\n * — it may or may not have run. Re-dispatching the successor first could land\n * it ahead of a predecessor that later turns out to have landed too, so the\n * successor is held until every preceding `lost` predecessor has settled: it\n * was covered by a snapshot, its retry was judged by the server, or the user\n * discarded it.\n */\n\n/** Fates the server never saw: the send died client-side (unmount, stream\n * gone, session lost) and is safe to re-dispatch under its original message\n * id. The complement — rejected/failed/crashed/expired — is server-judged. */\nconst AUTO_RETRY_FATES: ReadonlySet<string> = new Set(['lost', 'aborted']);\n\n/** A failed send is re-dispatched at most this many times per attach; a\n * persistent failure (repeated session losses) stays in the manual lane. */\nconst MAX_AUTO_RETRIES_PER_MESSAGE = 3;\n\n/** Persisted failures older than this stay manual: auto-sending a message the\n * user abandoned a day ago is worse than surfacing it for a decision. Mirrors\n * the retired outbox's retention window. */\nconst MAX_AUTO_RETRY_AGE_MS = 24 * 60 * 60 * 1000;\n\n/** How many times a host may hold one message's retry (`beforeRetry`\n * returning a promise) before the sweep sends it regardless: a hold is the\n * host's own bounded wait, never a reason to keep a message back for good. */\nexport const MAX_BEFORE_RETRY_HOLDS = 10;\n\nexport interface DeepAgentSendRecoveryOptions {\n /**\n * Reads the thread's failed-send lane. `null` while it is unavailable (the\n * client has not mounted yet, or its extras are not a statewire thread) —\n * the controller stays armed and retries on the next state change.\n */\n lane: () => StatewireFailedSendLane | null;\n /**\n * Persisted failure stamps (message id → epoch ms of the original send) for\n * the freshness gate. `null`/absent means no persistence is readable — every\n * lane entry then counts as fresh (an in-memory failed lane can only hold\n * failures from this page session).\n */\n failureStamps?: (() => ReadonlyMap<string, number> | null) | undefined;\n /** Transport lifecycle recorder; read per event so a late-mounted recorder still sees retries. */\n diagnostics?: (() => StatewireDiagnosticsRecorder | undefined) | undefined;\n /** Injectable clock (tests). */\n now?: () => number;\n /** Sweep scheduler; defaults to `queueMicrotask` so a sweep never dispatches\n * from inside a statewire flush or a React render. */\n schedule?: (task: () => void) => void;\n /**\n * Called right before a failed send is dispatched again, with its message\n * id, so a host can put back the per-send context that message was sent\n * with (Olympus stages the agents it named) before its command leaves.\n *\n * Returning a promise holds the retry: the host can't put that context back\n * yet (every send of the conversation rides one ambient context, and a send\n * with another context is still on its way). The sweep then stops at this\n * message, so nothing behind it overtakes it, and asks again once the\n * promise settles; past {@link MAX_BEFORE_RETRY_HOLDS} holds it sends the\n * message regardless. A hook that throws never holds a retry back. A\n * function returned is called when the message does not go after all (the\n * retry raced its leaving the lane), so the host lets go of what it put back.\n */\n beforeRetry?: ((messageId: string) => void | PromiseLike<void> | (() => void)) | undefined;\n}\n\nexport interface DeepAgentSendRecovery {\n /** Feed every (normalized) statewire state change; hydration arms the mount sweep. */\n observeStateChange(state: unknown): void;\n /** Feed every command-lifecycle update; a never-server-judged settle arms a sweep. */\n observeCommand(update: StatewireDiagnosticsCommandUpdate): void;\n /** Feed client notifications too: failed history can publish after the\n * command settles, without any change to the canonical thread snapshot. */\n observeFailedLaneChange(): void;\n dispose(): void;\n}\n\n/** Whether a settle failure is a client-side death the recovery re-dispatches\n * (the server never judged the command). Exported for host-side telemetry. */\nexport function shouldAutoRetryFailedSend(failure: { fate: string } | undefined): boolean {\n return failure !== undefined && AUTO_RETRY_FATES.has(failure.fate);\n}\n\n/** Whether a failed entry must wait for a lost predecessor to settle first. */\nfunction awaitsLostPredecessor(entry: StatewireFailedSendEntry): boolean {\n return entry.sendFailure.fate === 'aborted' && entry.sendFailure.predecessorLost === true;\n}\n\nfunction isThenable(value: unknown): value is PromiseLike<unknown> {\n return (\n typeof value === 'object' &&\n value !== null &&\n typeof (value as { then?: unknown }).then === 'function'\n );\n}\n\n/** The client-minted message id a run send carries, off a command update's params. */\nfunction commandMessageId(update: StatewireDiagnosticsCommandUpdate): string | null {\n const first = update.params?.[0] as { message?: { id?: unknown } } | undefined;\n const id = first?.message?.id;\n return typeof id === 'string' ? id : null;\n}\n\nexport function createDeepAgentSendRecovery(\n options: DeepAgentSendRecoveryOptions\n): DeepAgentSendRecovery {\n const now = options.now ?? Date.now;\n const schedule = options.schedule ?? ((task: () => void) => queueMicrotask(task));\n\n let disposed = false;\n let hydrated = false;\n /** Work may exist: set at hydration and on retryable settles, cleared by a\n * sweep that could actually read the lane. */\n let armed = false;\n let sweepScheduled = false;\n /** Retry budget per message id, for this controller's attach lifetime. */\n const attempts = new Map<string, number>();\n /** Message ids already diagnosed as stale/exhausted, so the lane re-scan\n * stays quiet about them. */\n const surfacedSkips = new Set<string>();\n /** Message ids whose deferral behind a lost predecessor was diagnosed once. */\n const surfacedDeferrals = new Set<string>();\n /** Re-dispatched sends still awaiting the server's verdict: a successor\n * stamped `predecessorLost` waits while any of these is pending. */\n const inFlight = new Set<string>();\n /** Successors held back by the last sweep; a non-empty set keeps the\n * controller armed so the next settle or state change re-sweeps. */\n const deferred = new Set<string>();\n /** How many times the host held each message's retry (`beforeRetry`'s promise). */\n const holds = new Map<string, number>();\n /** A host's hold in progress: no sweep runs until it settles. */\n let holding = false;\n\n const record = (event: string, data?: Record<string, unknown>): void => {\n options.diagnostics?.()?.record(event, data);\n };\n\n const sweep = (): void => {\n sweepScheduled = false;\n // A host's hold re-sweeps once it settles; until then nothing is retried.\n if (disposed || holding || !hydrated || !armed) return;\n const lane = options.lane();\n // No readable lane yet: stay armed so the next state change re-sweeps.\n if (lane === null) return;\n armed = false;\n deferred.clear();\n const stamps = options.failureStamps?.() ?? null;\n // A `lost` entry earlier in lane order that this sweep leaves unsettled\n // (skipped as stale/exhausted, or re-dispatched and now in flight).\n let lostPredecessorPending = false;\n for (const entry of lane.failed) {\n if (!shouldAutoRetryFailedSend(entry.sendFailure)) continue;\n // Shared retry/discard may still be waiting for the storage lock. The\n // visible lane can keep this entry until then; do not spend another\n // attempt on a send whose previous retry is still awaiting a verdict.\n if (inFlight.has(entry.id)) {\n if (entry.sendFailure.fate === 'lost') lostPredecessorPending = true;\n continue;\n }\n if (awaitsLostPredecessor(entry) && (lostPredecessorPending || inFlight.size > 0)) {\n deferred.add(entry.id);\n if (!surfacedDeferrals.has(entry.id)) {\n surfacedDeferrals.add(entry.id);\n record('send-recovery/deferred', { messageId: entry.id });\n }\n continue;\n }\n const isLost = entry.sendFailure.fate === 'lost';\n const stamp = stamps?.get(entry.id);\n if (stamp !== undefined && now() - stamp > MAX_AUTO_RETRY_AGE_MS) {\n if (!surfacedSkips.has(entry.id)) {\n surfacedSkips.add(entry.id);\n record('send-recovery/stale', { messageId: entry.id, ageMs: Math.round(now() - stamp) });\n }\n if (isLost) lostPredecessorPending = true;\n continue;\n }\n const attempt = (attempts.get(entry.id) ?? 0) + 1;\n if (attempt > MAX_AUTO_RETRIES_PER_MESSAGE) {\n if (!surfacedSkips.has(entry.id)) {\n surfacedSkips.add(entry.id);\n record('send-recovery/exhausted', { messageId: entry.id });\n }\n if (isLost) lostPredecessorPending = true;\n continue;\n }\n let before: ReturnType<NonNullable<DeepAgentSendRecoveryOptions['beforeRetry']>> | undefined;\n try {\n before = options.beforeRetry?.(entry.id);\n } catch {\n // A host's own context step failing never holds the retry back.\n record('send-recovery/before-retry-failed', { messageId: entry.id });\n }\n if (isThenable(before)) {\n const held = (holds.get(entry.id) ?? 0) + 1;\n if (held <= MAX_BEFORE_RETRY_HOLDS) {\n // The host can't put this message's context back yet: it waits, and\n // so does everything behind it (nothing overtakes it); the sweep asks\n // again once the hold settles.\n holds.set(entry.id, held);\n holding = true;\n armed = true;\n record('send-recovery/held', { messageId: entry.id, holds: held });\n const resume = (): void => {\n holding = false;\n armed = true;\n scheduleSweep();\n };\n before.then(resume, resume);\n return;\n }\n record('send-recovery/held-too-long', { messageId: entry.id });\n }\n try {\n lane.retryFailed(entry.id);\n attempts.set(entry.id, attempt);\n inFlight.add(entry.id);\n if (isLost) lostPredecessorPending = true;\n record('send-recovery/retry', {\n messageId: entry.id,\n fate: entry.sendFailure.fate,\n attempt,\n });\n } catch {\n // The entry settled or was covered between the lane read and the\n // retry (the vendor drops covered failures against every snapshot);\n // nothing was dispatched, so the host lets go of what it put back.\n record('send-recovery/raced', { messageId: entry.id });\n try {\n if (typeof before === 'function') before();\n } catch {\n // The host's own step failing never stops the sweep.\n }\n }\n }\n // Held successors must be revisited once their predecessors settle.\n if (deferred.size > 0) armed = true;\n };\n\n const scheduleSweep = (): void => {\n if (disposed || sweepScheduled || holding || !hydrated || !armed) return;\n sweepScheduled = true;\n schedule(sweep);\n };\n\n return {\n observeStateChange(state) {\n if (disposed) return;\n if (!hydrated && isDeepAgentStateHydrated(state)) {\n // First real snapshot: the vendor's failureCovered pass has had its\n // chance to drop entries whose message actually landed, so whatever\n // remains provably never reached the thread.\n hydrated = true;\n armed = true;\n }\n scheduleSweep();\n },\n observeCommand(update) {\n if (disposed || update.status !== 'settled') return;\n const messageId = commandMessageId(update);\n if (messageId !== null) inFlight.delete(messageId);\n // A never-server-judged settle means new work; any settle while\n // successors are held may be the predecessor they wait on.\n if (!shouldAutoRetryFailedSend(update.failure) && deferred.size === 0) return;\n armed = true;\n scheduleSweep();\n },\n observeFailedLaneChange() {\n if (disposed) return;\n armed = true;\n scheduleSweep();\n },\n dispose() {\n disposed = true;\n },\n };\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -54,6 +54,9 @@ export declare const threadHandoffFlowMachine: import("xstate").StateMachine<Han
|
|
|
54
54
|
claimed_surface?: "chrome" | "desktop" | "mobile" | "spaces" | null | undefined;
|
|
55
55
|
mode?: "text" | "voice" | undefined;
|
|
56
56
|
draft?: string | null | undefined;
|
|
57
|
+
cursor?: string | null | undefined;
|
|
58
|
+
revision?: number | undefined;
|
|
59
|
+
implicit?: boolean | undefined;
|
|
57
60
|
};
|
|
58
61
|
launch: {
|
|
59
62
|
kind: "mobile";
|
|
@@ -72,19 +75,19 @@ export declare const threadHandoffFlowMachine: import("xstate").StateMachine<Han
|
|
|
72
75
|
} | {
|
|
73
76
|
kind: "office_invitation";
|
|
74
77
|
host: "excel" | "powerpoint" | "word";
|
|
78
|
+
} | {
|
|
79
|
+
kind: "chrome";
|
|
75
80
|
} | {
|
|
76
81
|
kind: "spaces";
|
|
77
82
|
url: string;
|
|
83
|
+
} | {
|
|
84
|
+
kind: "next";
|
|
78
85
|
} | {
|
|
79
86
|
kind: "desktop";
|
|
80
87
|
scheme_url: string;
|
|
81
88
|
download_url: string;
|
|
82
89
|
deployment_url: string;
|
|
83
90
|
protocol_version: 1;
|
|
84
|
-
} | {
|
|
85
|
-
kind: "chrome";
|
|
86
|
-
} | {
|
|
87
|
-
kind: "next";
|
|
88
91
|
};
|
|
89
92
|
}, {
|
|
90
93
|
run: ThreadHandoffRun;
|
|
@@ -114,6 +117,9 @@ export declare const threadHandoffFlowMachine: import("xstate").StateMachine<Han
|
|
|
114
117
|
claimed_surface?: "chrome" | "desktop" | "mobile" | "spaces" | null | undefined;
|
|
115
118
|
mode?: "text" | "voice" | undefined;
|
|
116
119
|
draft?: string | null | undefined;
|
|
120
|
+
cursor?: string | null | undefined;
|
|
121
|
+
revision?: number | undefined;
|
|
122
|
+
implicit?: boolean | undefined;
|
|
117
123
|
};
|
|
118
124
|
launch: {
|
|
119
125
|
kind: "mobile";
|
|
@@ -132,19 +138,19 @@ export declare const threadHandoffFlowMachine: import("xstate").StateMachine<Han
|
|
|
132
138
|
} | {
|
|
133
139
|
kind: "office_invitation";
|
|
134
140
|
host: "excel" | "powerpoint" | "word";
|
|
141
|
+
} | {
|
|
142
|
+
kind: "chrome";
|
|
135
143
|
} | {
|
|
136
144
|
kind: "spaces";
|
|
137
145
|
url: string;
|
|
146
|
+
} | {
|
|
147
|
+
kind: "next";
|
|
138
148
|
} | {
|
|
139
149
|
kind: "desktop";
|
|
140
150
|
scheme_url: string;
|
|
141
151
|
download_url: string;
|
|
142
152
|
deployment_url: string;
|
|
143
153
|
protocol_version: 1;
|
|
144
|
-
} | {
|
|
145
|
-
kind: "chrome";
|
|
146
|
-
} | {
|
|
147
|
-
kind: "next";
|
|
148
154
|
};
|
|
149
155
|
}, {
|
|
150
156
|
run: ThreadHandoffRun;
|
|
@@ -29,6 +29,7 @@ export declare const SURFACE_HANDOFF_DESTINATIONS: readonly ['spaces', 'mobile',
|
|
|
29
29
|
export type SurfaceHandoffDestination = (typeof SURFACE_HANDOFF_DESTINATIONS)[number];
|
|
30
30
|
/** The source composer's unsent text that travels prefilled, never sent. */
|
|
31
31
|
export declare const MAX_HANDOFF_DRAFT_LENGTH = 4000;
|
|
32
|
+
export { MAX_HANDOFF_CURSOR_LENGTH, parseHandoffCursor } from './handoff-cursor-id';
|
|
32
33
|
/**
|
|
33
34
|
* How the person chose to continue. `voice` is an offer the destination shows
|
|
34
35
|
* ("Continue by voice"); it never starts a microphone by itself. Older servers
|
|
@@ -94,6 +95,54 @@ export declare const destinationReadinessSchema: z.ZodObject<{
|
|
|
94
95
|
marketplace_url: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
95
96
|
}, z.core.$strip>;
|
|
96
97
|
export type DestinationReadiness = z.infer<typeof destinationReadinessSchema>;
|
|
98
|
+
/**
|
|
99
|
+
* How recently one of the person's four apps was seen, from a real signal: the desktop app and
|
|
100
|
+
* Chrome by their device tunnel, the phone by its foreground ping (or its push token), Spaces by
|
|
101
|
+
* workspace awareness. `live` is open right now; `recent` within minutes; `seen` longer ago;
|
|
102
|
+
* `not_seen` no evidence; `unknown` no signal to judge by; `unsupported` not in this environment.
|
|
103
|
+
*/
|
|
104
|
+
export declare const surfaceLivenessStateSchema: z.ZodEnum<{
|
|
105
|
+
live: "live";
|
|
106
|
+
not_seen: "not_seen";
|
|
107
|
+
recent: "recent";
|
|
108
|
+
seen: "seen";
|
|
109
|
+
unknown: "unknown";
|
|
110
|
+
unsupported: "unsupported";
|
|
111
|
+
}>;
|
|
112
|
+
export type SurfaceLivenessState = z.infer<typeof surfaceLivenessStateSchema>;
|
|
113
|
+
/** The signal a liveness row rests on. */
|
|
114
|
+
export declare const surfaceLivenessBasisSchema: z.ZodEnum<{
|
|
115
|
+
awareness: "awareness";
|
|
116
|
+
device_tunnel: "device_tunnel";
|
|
117
|
+
foreground: "foreground";
|
|
118
|
+
push_token: "push_token";
|
|
119
|
+
}>;
|
|
120
|
+
export type SurfaceLivenessBasis = z.infer<typeof surfaceLivenessBasisSchema>;
|
|
121
|
+
export declare const surfaceLivenessSchema: z.ZodObject<{
|
|
122
|
+
surface: z.ZodEnum<{
|
|
123
|
+
chrome: "chrome";
|
|
124
|
+
desktop: "desktop";
|
|
125
|
+
mobile: "mobile";
|
|
126
|
+
spaces: "spaces";
|
|
127
|
+
}>;
|
|
128
|
+
state: z.ZodEnum<{
|
|
129
|
+
live: "live";
|
|
130
|
+
not_seen: "not_seen";
|
|
131
|
+
recent: "recent";
|
|
132
|
+
seen: "seen";
|
|
133
|
+
unknown: "unknown";
|
|
134
|
+
unsupported: "unsupported";
|
|
135
|
+
}>;
|
|
136
|
+
basis: z.ZodOptional<z.ZodNullable<z.ZodEnum<{
|
|
137
|
+
awareness: "awareness";
|
|
138
|
+
device_tunnel: "device_tunnel";
|
|
139
|
+
foreground: "foreground";
|
|
140
|
+
push_token: "push_token";
|
|
141
|
+
}>>>;
|
|
142
|
+
last_seen_at: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
143
|
+
label: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
144
|
+
}, z.core.$strip>;
|
|
145
|
+
export type SurfaceLiveness = z.infer<typeof surfaceLivenessSchema>;
|
|
97
146
|
export declare const handoffReadinessResponseSchema: z.ZodObject<{
|
|
98
147
|
environment: z.ZodEnum<{
|
|
99
148
|
localhost: "localhost";
|
|
@@ -122,11 +171,47 @@ export declare const handoffReadinessResponseSchema: z.ZodObject<{
|
|
|
122
171
|
supports_office_invitations: z.ZodOptional<z.ZodBoolean>;
|
|
123
172
|
supports_desktop_source: z.ZodOptional<z.ZodBoolean>;
|
|
124
173
|
supports_surface_destinations: z.ZodOptional<z.ZodBoolean>;
|
|
174
|
+
supports_handoff_cursor: z.ZodOptional<z.ZodBoolean>;
|
|
175
|
+
supports_surface_liveness: z.ZodOptional<z.ZodBoolean>;
|
|
176
|
+
surfaces: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<{
|
|
177
|
+
surface: "chrome" | "desktop" | "mobile" | "spaces";
|
|
178
|
+
state: "live" | "not_seen" | "recent" | "seen" | "unknown" | "unsupported";
|
|
179
|
+
basis?: "awareness" | "device_tunnel" | "foreground" | "push_token" | null | undefined;
|
|
180
|
+
last_seen_at?: string | null | undefined;
|
|
181
|
+
label?: string | null | undefined;
|
|
182
|
+
}[], unknown[]>>>>;
|
|
125
183
|
}, z.core.$strip>;
|
|
126
184
|
export type HandoffReadinessResponse = z.infer<typeof handoffReadinessResponseSchema>;
|
|
127
185
|
export declare const supportsDesktopHandoffSource: (readiness: HandoffReadinessResponse | null | undefined) => boolean;
|
|
128
186
|
/** Whether the server can broker the four-surface destinations; absent means an older agora. */
|
|
129
187
|
export declare const supportsSurfaceHandoffDestinations: (readiness: HandoffReadinessResponse | null | undefined) => boolean;
|
|
188
|
+
/** Whether the server takes a reading position (`cursor`); absent means an older agora. */
|
|
189
|
+
export declare const supportsHandoffCursor: (readiness: HandoffReadinessResponse | null | undefined) => boolean;
|
|
190
|
+
/** Whether the server reports where the person's apps were last seen; absent means an older agora. */
|
|
191
|
+
export declare const supportsSurfaceLiveness: (readiness: HandoffReadinessResponse | null | undefined) => boolean;
|
|
192
|
+
/** Index liveness rows by surface; `null` where the server sent none (or can't report liveness). */
|
|
193
|
+
export declare const surfaceLivenessBySurface: (readiness: HandoffReadinessResponse | null | undefined) => Record<PickupSurface, SurfaceLiveness | null>;
|
|
194
|
+
/** "just now", "4 min ago", "3 hr ago", "yesterday", "5 days ago". */
|
|
195
|
+
export declare const livenessAgo: ({ at, now }: {
|
|
196
|
+
at: number;
|
|
197
|
+
now: number;
|
|
198
|
+
}) => string;
|
|
199
|
+
export type SurfaceLivenessTone = 'live' | 'recent' | 'idle';
|
|
200
|
+
/** What a "Continue on…" row says about where that app was last seen, and how warm it is. */
|
|
201
|
+
export interface SurfaceLivenessPresentation {
|
|
202
|
+
text: string;
|
|
203
|
+
tone: SurfaceLivenessTone;
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* The one wording every picker shows for a surface's liveness ("Open now on your Mac", "Chrome is
|
|
207
|
+
* running", "Your phone was active 2 min ago"). `null` when there is nothing real to say (no row,
|
|
208
|
+
* an older server, `unknown`, `unsupported`): the row then keeps the text it shows today.
|
|
209
|
+
*/
|
|
210
|
+
export declare const presentSurfaceLiveness: ({ surface, liveness, now, }: {
|
|
211
|
+
surface: PickupSurface;
|
|
212
|
+
liveness: SurfaceLiveness | null | undefined;
|
|
213
|
+
now?: number;
|
|
214
|
+
}) => SurfaceLivenessPresentation | null;
|
|
130
215
|
export declare const threadHandoffViewSchema: z.ZodObject<{
|
|
131
216
|
handoff_id: z.ZodString;
|
|
132
217
|
thread_id: z.ZodString;
|
|
@@ -175,8 +260,17 @@ export declare const threadHandoffViewSchema: z.ZodObject<{
|
|
|
175
260
|
voice: "voice";
|
|
176
261
|
}>>;
|
|
177
262
|
draft: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
263
|
+
cursor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
264
|
+
revision: z.ZodOptional<z.ZodNumber>;
|
|
265
|
+
implicit: z.ZodOptional<z.ZodBoolean>;
|
|
178
266
|
}, z.core.$strip>;
|
|
179
267
|
export type ThreadHandoffView = z.infer<typeof threadHandoffViewSchema>;
|
|
268
|
+
/**
|
|
269
|
+
* What a "Not now" remembers: the ticket at the revision it was offered at (`<id>@<revision>`).
|
|
270
|
+
* Sent back as `exclude`, it skips that ticket only until the same conversation is sent here
|
|
271
|
+
* again, which offers it again.
|
|
272
|
+
*/
|
|
273
|
+
export declare function handoffDeclineKey(view: Pick<ThreadHandoffView, 'handoff_id' | 'revision'>): string;
|
|
180
274
|
export declare const mobileHandoffLaunchSchema: z.ZodObject<{
|
|
181
275
|
kind: z.ZodLiteral<"mobile">;
|
|
182
276
|
push_attempted: z.ZodBoolean;
|
|
@@ -319,6 +413,9 @@ export declare const createThreadHandoffResponseSchema: z.ZodObject<{
|
|
|
319
413
|
voice: "voice";
|
|
320
414
|
}>>;
|
|
321
415
|
draft: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
416
|
+
cursor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
417
|
+
revision: z.ZodOptional<z.ZodNumber>;
|
|
418
|
+
implicit: z.ZodOptional<z.ZodBoolean>;
|
|
322
419
|
}, z.core.$strip>;
|
|
323
420
|
launch: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
324
421
|
kind: z.ZodLiteral<"mobile">;
|
|
@@ -368,15 +465,40 @@ export interface CreateThreadHandoffInput {
|
|
|
368
465
|
officeLaunchMode?: OfficeLaunchMode;
|
|
369
466
|
/** An observed workspace precondition, never an authorization grant. */
|
|
370
467
|
expectedWorkspaceId?: string;
|
|
371
|
-
/**
|
|
468
|
+
/**
|
|
469
|
+
* The composer's unsent text; the destination prefills it and never sends
|
|
470
|
+
* it. Blank (the composer is empty) clears an open ticket's draft; omitted
|
|
471
|
+
* leaves it. See {@link handoffDraftToCarry}.
|
|
472
|
+
*/
|
|
372
473
|
draft?: string;
|
|
373
474
|
/** `voice` offers "Continue by voice" on the destination. Omitted means text. */
|
|
374
475
|
mode?: HandoffMode;
|
|
476
|
+
/**
|
|
477
|
+
* Where the source was reading: the id of the last message it showed, or `''`
|
|
478
|
+
* when it was at the bottom (which also clears a reused ticket's). Send it only
|
|
479
|
+
* to a host whose readiness says `supports_handoff_cursor`. A value that is
|
|
480
|
+
* not a message id goes as `''`.
|
|
481
|
+
*/
|
|
482
|
+
cursor?: string;
|
|
375
483
|
}
|
|
484
|
+
/**
|
|
485
|
+
* The draft a create carries, the one rule every source follows. An Office
|
|
486
|
+
* pane never gets one: it neither prefills nor continues by voice. Anywhere
|
|
487
|
+
* else a surface that read its composer sends what it holds, within the
|
|
488
|
+
* server's bound, and a blank composer as `''`: agora reuses an open ticket
|
|
489
|
+
* for the same thread, destination and mode, and an empty draft clears the
|
|
490
|
+
* one that ticket stored, so text the person deleted never comes back at the
|
|
491
|
+
* destination. `undefined` or `null` (the surface could not read a composer)
|
|
492
|
+
* sends none and leaves an open ticket's draft as it was.
|
|
493
|
+
*/
|
|
494
|
+
export declare function handoffDraftToCarry({ destination, draft, }: {
|
|
495
|
+
destination: ThreadHandoffDestination;
|
|
496
|
+
draft: string | null | undefined;
|
|
497
|
+
}): string | undefined;
|
|
376
498
|
/** A phone invites the computer's pane; other sources keep their existing launch. */
|
|
377
499
|
export declare function resolveOfficeHandoffLaunchMode({ source, destination, officeLaunchMode, }: CreateThreadHandoffInput): OfficeLaunchMode | undefined;
|
|
378
500
|
/** The same request body for every host's authenticated HTTP adapter. */
|
|
379
|
-
export declare function threadHandoffCreateBody({ threadId, destination, source, officeLaunchMode, expectedWorkspaceId, draft, mode: handoffMode, }: CreateThreadHandoffInput): {
|
|
501
|
+
export declare function threadHandoffCreateBody({ threadId, destination, source, officeLaunchMode, expectedWorkspaceId, draft, mode: handoffMode, cursor, }: CreateThreadHandoffInput): {
|
|
380
502
|
thread_id: string;
|
|
381
503
|
destination: "chrome" | "desktop" | "excel" | "mobile" | "next" | "powerpoint" | "spaces" | "word";
|
|
382
504
|
source: HandoffSource;
|
|
@@ -384,6 +506,7 @@ export declare function threadHandoffCreateBody({ threadId, destination, source,
|
|
|
384
506
|
office_launch_mode?: "invitation" | "new_document" | undefined;
|
|
385
507
|
draft?: string | undefined;
|
|
386
508
|
mode?: "voice" | undefined;
|
|
509
|
+
cursor?: string | undefined;
|
|
387
510
|
};
|
|
388
511
|
/** Keep a draft within the server's limit at a character boundary; never send more. */
|
|
389
512
|
export declare const clampHandoffDraft: (draft: string) => string;
|
|
@@ -429,10 +552,15 @@ export declare function pickupExcludeIds({ issuedHere, declined, }: {
|
|
|
429
552
|
issuedHere: Iterable<string>;
|
|
430
553
|
declined: Iterable<string>;
|
|
431
554
|
}): string[];
|
|
432
|
-
/**
|
|
433
|
-
|
|
555
|
+
/**
|
|
556
|
+
* `/pending?destination=…&exclude=…` relative to `/api/handoffs`. `includeImplicit` asks for an
|
|
557
|
+
* implicit ticket too ("pick up without sending"): only on a `next` peek, the one destination an
|
|
558
|
+
* implicit ticket names. A server that predates it ignores the flag.
|
|
559
|
+
*/
|
|
560
|
+
export declare function pendingHandoffPath({ destination, exclude, includeImplicit, }: {
|
|
434
561
|
destination: Exclude<ThreadHandoffDestination, 'desktop'> | 'desktop';
|
|
435
562
|
exclude?: Iterable<string>;
|
|
563
|
+
includeImplicit?: boolean;
|
|
436
564
|
}): string;
|
|
437
565
|
export declare const claimThreadHandoffResponseSchema: z.ZodObject<{
|
|
438
566
|
handoff: z.ZodObject<{
|
|
@@ -483,6 +611,9 @@ export declare const claimThreadHandoffResponseSchema: z.ZodObject<{
|
|
|
483
611
|
voice: "voice";
|
|
484
612
|
}>>;
|
|
485
613
|
draft: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
614
|
+
cursor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
615
|
+
revision: z.ZodOptional<z.ZodNumber>;
|
|
616
|
+
implicit: z.ZodOptional<z.ZodBoolean>;
|
|
486
617
|
}, z.core.$strip>;
|
|
487
618
|
}, z.core.$strip>;
|
|
488
619
|
export type ClaimThreadHandoffResponse = z.infer<typeof claimThreadHandoffResponseSchema>;
|
|
@@ -536,6 +667,9 @@ export declare const pendingThreadHandoffResponseSchema: z.ZodNullable<z.ZodObje
|
|
|
536
667
|
voice: "voice";
|
|
537
668
|
}>>;
|
|
538
669
|
draft: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
670
|
+
cursor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
671
|
+
revision: z.ZodOptional<z.ZodNumber>;
|
|
672
|
+
implicit: z.ZodOptional<z.ZodBoolean>;
|
|
539
673
|
}, z.core.$strip>;
|
|
540
674
|
}, z.core.$strip>>;
|
|
541
675
|
/**
|
|
@@ -551,6 +685,8 @@ export interface ThreadHandoffReceiverTransport {
|
|
|
551
685
|
destination: Exclude<ThreadHandoffDestination, 'desktop'>;
|
|
552
686
|
/** Tickets this receiver declined or issued itself; the newest other one is returned. */
|
|
553
687
|
exclude?: readonly string[];
|
|
688
|
+
/** Also an implicit ticket, on a `next` peek ({@link pendingHandoffPath}). */
|
|
689
|
+
includeImplicit?: boolean;
|
|
554
690
|
signal?: AbortSignal;
|
|
555
691
|
}) => Promise<ClaimThreadHandoffResponse | null>;
|
|
556
692
|
claim: (options: {
|
|
@@ -568,16 +704,84 @@ export interface ThreadHandoffReceiverTransport {
|
|
|
568
704
|
signal?: AbortSignal;
|
|
569
705
|
}) => Promise<ThreadHandoffView>;
|
|
570
706
|
}
|
|
707
|
+
/**
|
|
708
|
+
* "Pick up without sending": an app offers a conversation the person is driving on another of
|
|
709
|
+
* their apps only when it just became active (the Spaces tab became visible, the phone came to the
|
|
710
|
+
* foreground, Chrome's side panel opened, the desktop window gained focus), within this long.
|
|
711
|
+
*/
|
|
712
|
+
export declare const IMPLICIT_PICKUP_ACTIVATION_WINDOW_MS = 10000;
|
|
713
|
+
/**
|
|
714
|
+
* How long a "Not now" to an implicit offer is kept where declines expire (Chrome, the desktop
|
|
715
|
+
* app). The ticket keeps its id for as long as the same app drives, past its 10 minutes, so a
|
|
716
|
+
* decline kept only as long as the ticket's first expiry would let the same offer come back.
|
|
717
|
+
*/
|
|
718
|
+
export declare const IMPLICIT_PICKUP_DECLINE_HOLD_MS: number;
|
|
719
|
+
/** Whether a view is an implicit ticket: left by the app the person drives from, never sent. */
|
|
720
|
+
export declare const isImplicitHandoff: (view: Pick<ThreadHandoffView, 'implicit'> | null | undefined) => boolean;
|
|
721
|
+
/**
|
|
722
|
+
* What a receiving app knows about itself to judge an implicit ticket. `seenAt` comes from
|
|
723
|
+
* {@link nextImplicitPickupAnswer}: when this app first saw the ticket after `becameActiveAt`.
|
|
724
|
+
*/
|
|
725
|
+
export interface ImplicitPickupContext {
|
|
726
|
+
/** This app. */
|
|
727
|
+
here: PickupSurface;
|
|
728
|
+
/** The conversation this app shows now; `null` when none. */
|
|
729
|
+
showingThreadId: string | null;
|
|
730
|
+
/** When this app last became active; `null` until it has. */
|
|
731
|
+
becameActiveAt: number | null;
|
|
732
|
+
/** When this app first saw the ticket after becoming active; `null` when it has not. */
|
|
733
|
+
seenAt: number | null;
|
|
734
|
+
}
|
|
735
|
+
/**
|
|
736
|
+
* Whether an implicit ticket may be offered here: it is another app's, this app isn't already
|
|
737
|
+
* showing that conversation, and this app saw it within {@link IMPLICIT_PICKUP_ACTIVATION_WINDOW_MS}
|
|
738
|
+
* of becoming active. An idle poll's answer never qualifies on its own, and nothing here ever
|
|
739
|
+
* raises a notification: only the app's own banner offers one.
|
|
740
|
+
*/
|
|
741
|
+
export declare function implicitPickupAllowed({ pending, context, }: {
|
|
742
|
+
pending: Pick<ThreadHandoffView, 'source' | 'thread_id'>;
|
|
743
|
+
context: ImplicitPickupContext;
|
|
744
|
+
}): boolean;
|
|
745
|
+
/** An implicit-pickup peek's answer, with what judging it needs. */
|
|
746
|
+
export interface ImplicitPickupAnswer<View extends {
|
|
747
|
+
handoff_id: string;
|
|
748
|
+
} = ThreadHandoffView> {
|
|
749
|
+
view: View | null;
|
|
750
|
+
/** The activation this answer belongs to. */
|
|
751
|
+
becameActiveAt: number;
|
|
752
|
+
/** When this app first saw `view`'s ticket since that activation. */
|
|
753
|
+
seenAt: number;
|
|
754
|
+
}
|
|
755
|
+
/**
|
|
756
|
+
* Fold a new peek into the earlier ones: a ticket seen again since the same activation keeps the
|
|
757
|
+
* time it was first seen, so an offer made on becoming active stays up until it is answered or
|
|
758
|
+
* goes away, while a ticket that first shows up on a later poll is never offered. `previous` may
|
|
759
|
+
* be every answer of this activation (one per set of exclusions a host asked with), so declining
|
|
760
|
+
* another ticket never makes this one look new.
|
|
761
|
+
*/
|
|
762
|
+
export declare function nextImplicitPickupAnswer<View extends {
|
|
763
|
+
handoff_id: string;
|
|
764
|
+
} = ThreadHandoffView>({ previous, view, becameActiveAt, now, }: {
|
|
765
|
+
previous: ImplicitPickupAnswer<View> | readonly (ImplicitPickupAnswer<View> | null | undefined)[] | null | undefined;
|
|
766
|
+
view: View | null;
|
|
767
|
+
becameActiveAt: number;
|
|
768
|
+
now?: number;
|
|
769
|
+
}): ImplicitPickupAnswer<View>;
|
|
571
770
|
/**
|
|
572
771
|
* Whether a pending handoff should be offered on this surface: still open,
|
|
573
772
|
* not one this device issued itself, and not one it already declined.
|
|
574
773
|
* Offering never claims; the person's tap does.
|
|
774
|
+
*
|
|
775
|
+
* A ticket the person sent is judged exactly as it always was. An implicit
|
|
776
|
+
* one ("pick up without sending") also needs `implicit`, the receiver's own
|
|
777
|
+
* context ({@link implicitPickupAllowed}); without it, it is never offered.
|
|
575
778
|
*/
|
|
576
|
-
export declare function shouldOfferPendingHandoff({ pending, issuedHere, declined, now, }: {
|
|
779
|
+
export declare function shouldOfferPendingHandoff({ pending, issuedHere, declined, now, implicit, }: {
|
|
577
780
|
pending: ThreadHandoffView | null | undefined;
|
|
578
781
|
issuedHere: ReadonlySet<string>;
|
|
579
782
|
declined: ReadonlySet<string>;
|
|
580
783
|
now?: number;
|
|
784
|
+
implicit?: ImplicitPickupContext;
|
|
581
785
|
}): boolean;
|
|
582
786
|
/**
|
|
583
787
|
* Surface-specific HTTP adapter. Olympus binds it to `apiClient`; the
|
|
@@ -599,6 +803,19 @@ export interface ThreadHandoffTransport {
|
|
|
599
803
|
export declare const HANDOFF_DESTINATION_LABELS: Record<HandoffDestination, string>;
|
|
600
804
|
/** Picker labels for every destination a "Continue on…" row can name. */
|
|
601
805
|
export declare const THREAD_HANDOFF_DESTINATION_LABELS: Record<ThreadHandoffDestination, string>;
|
|
806
|
+
/**
|
|
807
|
+
* How a landing names the surface a conversation came from ("Continued from …"), and every other
|
|
808
|
+
* "from …" an app says about another (a device's "Athena is using Mail, from your phone").
|
|
809
|
+
*/
|
|
810
|
+
export declare const HANDOFF_ORIGIN_LABELS: {
|
|
811
|
+
readonly spaces: 'Spaces';
|
|
812
|
+
readonly mobile: 'your phone';
|
|
813
|
+
readonly chrome: 'Chrome';
|
|
814
|
+
readonly desktop: 'Athena Desktop';
|
|
815
|
+
readonly word: 'Word';
|
|
816
|
+
readonly powerpoint: 'PowerPoint';
|
|
817
|
+
readonly excel: 'Excel';
|
|
818
|
+
};
|
|
602
819
|
/** "Pick up your conversation from your phone?" — a destination's offer, before any claim. */
|
|
603
820
|
export declare const pickUpOfferLabel: (source: string) => string;
|
|
604
821
|
/** "Continued from your phone" — the divider a destination shows where it picked up. */
|
|
@@ -629,6 +846,11 @@ export interface HandoffLanding {
|
|
|
629
846
|
* "Continue by voice"; nothing starts the microphone on its own.
|
|
630
847
|
*/
|
|
631
848
|
continueByVoice: boolean;
|
|
849
|
+
/**
|
|
850
|
+
* The message the source was reading; the transcript opens there instead of
|
|
851
|
+
* at the bottom. `null` when the source was at the bottom (or sent none).
|
|
852
|
+
*/
|
|
853
|
+
cursor: string | null;
|
|
632
854
|
}
|
|
633
855
|
export declare const handoffLandingFromView: (view: ThreadHandoffView) => HandoffLanding;
|
|
634
856
|
export declare const isOfficeHandoffDestination: (destination: ThreadHandoffDestination) => destination is OfficeHandoffDestination;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
/** Backward-compatible entrypoint. Use the protocol entrypoint without QR rendering. */
|
|
2
|
-
export { type CreateThreadHandoffInput, type CreateThreadHandoffResponse, type DestinationReadiness, HANDOFF_DESTINATIONS, HANDOFF_DESTINATION_LABELS, HANDOFF_URL_PARAM, type HandoffDestination, type HandoffDestinationPresentation, type HandoffEnvironment, HandoffGoneError, type HandoffLaunch, type HandoffOutcome, HandoffPermanentError, type HandoffReadinessResponse, type HandoffReadinessState, type HandoffSource, type HandoffState, type HandoffTransportErrorKind, MOBILE_APP_SCHEME, type MobileHandoffLaunch, type MobileLaunchMode, OFFICE_HANDOFF_DESTINATIONS, type OfficeHandoffDestination, type OfficeHandoffLaunch, type OfficeInvitationLaunch, type OfficeLaunchMode, SERVER_HANDOFF_DESTINATIONS, type ServerHandoffDestination, type ThreadHandoffTransport, type ThreadHandoffView, type WaitForHandoffOutcomeOptions, buildMobileHandoffSchemeUrl, chromeHandoffLaunchSchema, classifyHandoffTransportError, createThreadHandoffResponseSchema, describeHandoffTransportError, destinationReadinessSchema, handoffEnvironmentSchema, handoffHttpError, handoffLaunchSchema, handoffReadinessResponseSchema, handoffReadinessStateSchema, handoffStateSchema, isHandoffGoneError, isHandoffPermanentError, isOfficeHandoffDestination, isRetryableHandoffHttpStatus, isServerHandoffDestination, isTerminalHandoffState, mobileHandoffLaunchSchema, officeHandoffLaunchSchema, officeInvitationLaunchSchema, officeLaunchModeSchema, parseHandoffIdParam, presentHandoffDestination, readinessByDestination, resolveOfficeHandoffLaunchMode, selectMobileLaunchMode, supportsDesktopHandoffSource, threadHandoffCreateBody, threadHandoffViewSchema, waitForHandoffOutcome, type ClaimThreadHandoffResponse, claimThreadHandoffResponseSchema, clampHandoffDraft, continuedFromLabel, pickUpFromLabel, pickUpOfferLabel, type DesktopThreadHandoffLaunch, desktopThreadHandoffLaunchSchema, driverSurfaceForChannel, drivingOnLabel, type HandoffLanding, handoffLandingFromView, isPickupSurface, MAX_HANDOFF_DRAFT_LENGTH, nextHandoffLaunchSchema, parseThreadHandoffCreateResponse, pendingThreadHandoffResponseSchema, pickedUpOnLabel, PICKUP_SURFACES, type PickupSurface, shouldOfferPendingHandoff, SPACES_HANDOFF_URL_PARAM, type SpacesHandoffLaunch, spacesHandoffLaunchSchema, SURFACE_HANDOFF_DESTINATIONS, type SurfaceHandoffDestination, supportsSurfaceHandoffDestinations, THREAD_HANDOFF_DESTINATION_LABELS, THREAD_HANDOFF_DESTINATIONS, threadHandoffClaimBody, threadHandoffCreateRequest, type ThreadHandoffDestination, type ThreadHandoffReceiverTransport, MAX_PENDING_EXCLUDE, pendingExcludeIds, pickupExcludeIds, pendingHandoffPath, type HandoffMode, handoffModeSchema, isVoiceHandoffDestination, } from './thread-handoff-protocol';
|
|
2
|
+
export { type CreateThreadHandoffInput, type CreateThreadHandoffResponse, type DestinationReadiness, HANDOFF_DESTINATIONS, HANDOFF_DESTINATION_LABELS, HANDOFF_URL_PARAM, type HandoffDestination, type HandoffDestinationPresentation, type HandoffEnvironment, HandoffGoneError, type HandoffLaunch, type HandoffOutcome, HandoffPermanentError, type HandoffReadinessResponse, type HandoffReadinessState, type HandoffSource, type HandoffState, type HandoffTransportErrorKind, MOBILE_APP_SCHEME, type MobileHandoffLaunch, type MobileLaunchMode, OFFICE_HANDOFF_DESTINATIONS, type OfficeHandoffDestination, type OfficeHandoffLaunch, type OfficeInvitationLaunch, type OfficeLaunchMode, SERVER_HANDOFF_DESTINATIONS, type ServerHandoffDestination, type ThreadHandoffTransport, type ThreadHandoffView, type WaitForHandoffOutcomeOptions, buildMobileHandoffSchemeUrl, chromeHandoffLaunchSchema, classifyHandoffTransportError, createThreadHandoffResponseSchema, describeHandoffTransportError, destinationReadinessSchema, handoffEnvironmentSchema, handoffHttpError, handoffLaunchSchema, handoffReadinessResponseSchema, handoffReadinessStateSchema, handoffStateSchema, isHandoffGoneError, isHandoffPermanentError, isOfficeHandoffDestination, isRetryableHandoffHttpStatus, isServerHandoffDestination, isTerminalHandoffState, mobileHandoffLaunchSchema, officeHandoffLaunchSchema, officeInvitationLaunchSchema, officeLaunchModeSchema, parseHandoffIdParam, presentHandoffDestination, readinessByDestination, resolveOfficeHandoffLaunchMode, selectMobileLaunchMode, supportsDesktopHandoffSource, threadHandoffCreateBody, threadHandoffViewSchema, waitForHandoffOutcome, type ClaimThreadHandoffResponse, claimThreadHandoffResponseSchema, clampHandoffDraft, handoffDraftToCarry, continuedFromLabel, pickUpFromLabel, pickUpOfferLabel, type DesktopThreadHandoffLaunch, desktopThreadHandoffLaunchSchema, driverSurfaceForChannel, drivingOnLabel, type HandoffLanding, handoffLandingFromView, isPickupSurface, MAX_HANDOFF_DRAFT_LENGTH, nextHandoffLaunchSchema, parseThreadHandoffCreateResponse, pendingThreadHandoffResponseSchema, pickedUpOnLabel, PICKUP_SURFACES, type PickupSurface, shouldOfferPendingHandoff, IMPLICIT_PICKUP_ACTIVATION_WINDOW_MS, IMPLICIT_PICKUP_DECLINE_HOLD_MS, type ImplicitPickupAnswer, type ImplicitPickupContext, implicitPickupAllowed, isImplicitHandoff, nextImplicitPickupAnswer, SPACES_HANDOFF_URL_PARAM, type SpacesHandoffLaunch, spacesHandoffLaunchSchema, SURFACE_HANDOFF_DESTINATIONS, type SurfaceHandoffDestination, supportsSurfaceHandoffDestinations, THREAD_HANDOFF_DESTINATION_LABELS, THREAD_HANDOFF_DESTINATIONS, threadHandoffClaimBody, threadHandoffCreateRequest, type ThreadHandoffDestination, type ThreadHandoffReceiverTransport, MAX_PENDING_EXCLUDE, handoffDeclineKey, pendingExcludeIds, pickupExcludeIds, pendingHandoffPath, type HandoffMode, handoffModeSchema, isVoiceHandoffDestination, MAX_HANDOFF_CURSOR_LENGTH, parseHandoffCursor, supportsHandoffCursor, livenessAgo, presentSurfaceLiveness, type SurfaceLiveness, type SurfaceLivenessBasis, surfaceLivenessBasisSchema, surfaceLivenessBySurface, type SurfaceLivenessPresentation, surfaceLivenessSchema, type SurfaceLivenessState, surfaceLivenessStateSchema, type SurfaceLivenessTone, supportsSurfaceLiveness, } from './thread-handoff-protocol';
|
|
3
3
|
export { buildHandoffQrSvg } from './thread-handoff-qr';
|
|
@@ -7,7 +7,7 @@ const DEFAULT_PAGE_SIZE = 20;
|
|
|
7
7
|
const DEFAULT_STALE_TIME_MS = 3e4;
|
|
8
8
|
const DEFAULT_GC_TIME_MS = 10 * 60 * 1e3;
|
|
9
9
|
function useConversationList(options) {
|
|
10
|
-
var _a, _b, _c;
|
|
10
|
+
var _a, _b, _c, _d;
|
|
11
11
|
const {
|
|
12
12
|
filter,
|
|
13
13
|
pageSize = DEFAULT_PAGE_SIZE,
|
|
@@ -133,10 +133,11 @@ function useConversationList(options) {
|
|
|
133
133
|
return {
|
|
134
134
|
conversations,
|
|
135
135
|
totalCount: ((_b = firstPage.data) == null ? void 0 : _b.totalCount) ?? null,
|
|
136
|
+
homeAgentApplied: ((_c = firstPage.data) == null ? void 0 : _c.homeAgentApplied) === true,
|
|
136
137
|
isLoading: firstPage.isLoading,
|
|
137
138
|
isFetching: firstPage.isFetching || history.isFetching,
|
|
138
139
|
hasResolvedFirstPage: firstPage.data !== void 0,
|
|
139
|
-
hasNextPage: hasLoadedHistory ? history.hasNextPage ?? false : ((
|
|
140
|
+
hasNextPage: hasLoadedHistory ? history.hasNextPage ?? false : ((_d = firstPage.data) == null ? void 0 : _d.hasMore) ?? false,
|
|
140
141
|
isFetchingNextPage: history.isFetchingNextPage || !hasLoadedHistory && history.isFetching,
|
|
141
142
|
isFetchNextPageError: history.isError,
|
|
142
143
|
fetchNextPage,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-conversation-list.cjs","sources":["../../../../agent-runtime/src/use-conversation-list.ts"],"sourcesContent":["import {\n type InfiniteData,\n type QueryClient,\n useInfiniteQuery,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query';\nimport { useCallback, useMemo, useRef } from 'react';\nimport {\n CONVERSATION_LIST_DEFAULT_SCOPE_KEY,\n type ConversationListFetcher,\n type ConversationListFilter,\n type ConversationListPage,\n type ConversationSummary,\n conversationListFilterKey,\n conversationListQueryKeys,\n invalidateConversationListCaches,\n mergeConversationPages,\n normalizeConversationListFilter,\n patchConversationsInPage,\n prependConversationToPage,\n removeConversationFromPage,\n} from './conversation-list';\n\nconst DEFAULT_PAGE_SIZE = 20;\nconst DEFAULT_STALE_TIME_MS = 30_000;\nconst DEFAULT_GC_TIME_MS = 10 * 60 * 1000;\n\nexport interface UseConversationListOptions {\n /**\n * Transport adapter for one page (GraphQL `listConversations`, the REST\n * thread list, …). Captured in a ref — identity changes do not re-key the\n * cache, so pair a changing fetcher with a changing `scopeKey`.\n */\n fetchPage: ConversationListFetcher;\n filter?: ConversationListFilter;\n pageSize?: number;\n /**\n * Cache identity beyond the filter. MUST include everything that changes\n * whose list this is — principal/user id, backend base URL — so a\n * credential swap can never serve another identity's rows from the cache.\n */\n scopeKey?: string;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnMount?: boolean | 'always';\n refetchOnWindowFocus?: boolean;\n /**\n * Newest-page freshness is push-based by default (the conversation-index\n * statewire invalidates these caches), so no timer runs unless asked.\n */\n refetchInterval?: number | false;\n retry?: boolean | number | ((failureCount: number, error: Error) => boolean);\n /**\n * The client to cache in. Defaults to the one in context; a hook that may\n * render outside any `QueryClientProvider` (and stays disabled there)\n * passes its own.\n */\n queryClient?: QueryClient;\n}\n\nexport interface ConversationListController {\n /** Merged newest-first rows: live first page + retained history pages. */\n conversations: ConversationSummary[];\n /** Advisory total from the live first page, when the transport counts. */\n totalCount: number | null;\n isLoading: boolean;\n isFetching: boolean;\n /** True once the live first page has resolved at least once. */\n hasResolvedFirstPage: boolean;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n /** True while the last next-page request failed (cleared by a successful one). */\n isFetchNextPageError: boolean;\n fetchNextPage: () => Promise<unknown>;\n /** Refetch the live first page (history pages stay frozen). */\n refresh: () => Promise<unknown>;\n /** Invalidate every conversation-list cache in this scope (all filters). */\n invalidate: () => Promise<void>;\n /** Optimistically show a new conversation at the top of THIS filter view. */\n prependConversation: (item: ConversationSummary) => void;\n /** Optimistically drop a conversation from THIS filter view. */\n removeConversation: (threadId: string) => void;\n /** Optimistically patch a conversation's row in THIS filter view. */\n patchConversation: (threadId: string, patch: Partial<ConversationSummary>) => void;\n}\n\n/**\n * The shared headless conversation-list pager (chat-stack unification —\n * thread-list-contract-convergence).\n *\n * Implements, once, the two-tier TanStack Query pattern Olympus and mobile\n * each hand-rolled:\n *\n * - a LIVE first-page query — the only observer that refetches on\n * invalidation/focus, so push-driven freshness costs one page, and\n * - a permanently-disabled infinite \"history\" query whose pages are fetched\n * only by explicit `fetchNextPage()` calls and retained without replay\n * (invalidation/polling never refetches every loaded page).\n *\n * Rows merge newest-first with the live page winning duplicate threads.\n */\nexport function useConversationList(options: UseConversationListOptions): ConversationListController {\n const {\n filter,\n pageSize = DEFAULT_PAGE_SIZE,\n scopeKey = CONVERSATION_LIST_DEFAULT_SCOPE_KEY,\n enabled,\n staleTime = DEFAULT_STALE_TIME_MS,\n gcTime = DEFAULT_GC_TIME_MS,\n refetchOnMount,\n refetchOnWindowFocus,\n refetchInterval = false,\n retry,\n } = options;\n const queryClient = useQueryClient(options.queryClient);\n\n const fetchPageRef = useRef(options.fetchPage);\n fetchPageRef.current = options.fetchPage;\n\n // The normalized filter is both the cache identity (via the key) and what\n // queryFn closures fetch, so a cache entry holds exactly what it claims —\n // fresh-but-equal filter objects from the caller never re-key or refetch.\n // (Plain per-render values: only the serialized key participates in\n // identity, so object freshness here is irrelevant.)\n const normalizedFilter = normalizeConversationListFilter(filter ?? {});\n const filterKey = conversationListFilterKey(normalizedFilter);\n\n const firstPageKey = useMemo(\n () => conversationListQueryKeys.firstPage(scopeKey, filterKey, pageSize),\n [scopeKey, filterKey, pageSize]\n );\n const historyKey = useMemo(\n () => conversationListQueryKeys.history(scopeKey, filterKey, pageSize),\n [scopeKey, filterKey, pageSize]\n );\n\n // React Query merges per-query options OVER the client's defaultOptions with\n // Object.assign, which copies explicitly-`undefined` keys — so passing\n // `refetchOnWindowFocus: undefined` CLOBBERS a consumer's\n // `refetchOnWindowFocus: false` default and silently re-enables focus\n // refetching. Options the caller did not set must therefore be omitted, not\n // forwarded as undefined, so they fall through to the consuming app's client.\n const passthrough = {\n ...(enabled !== undefined ? { enabled } : {}),\n ...(refetchOnMount !== undefined ? { refetchOnMount } : {}),\n ...(refetchOnWindowFocus !== undefined ? { refetchOnWindowFocus } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n\n const firstPage = useQuery(\n {\n queryKey: firstPageKey,\n queryFn: ({ signal }) =>\n fetchPageRef.current({ limit: pageSize, offset: 0, filter: normalizedFilter, signal }),\n staleTime,\n gcTime,\n refetchInterval,\n ...passthrough,\n },\n queryClient\n );\n\n const history = useInfiniteQuery(\n {\n queryKey: historyKey,\n initialPageParam: pageSize,\n gcTime,\n queryFn: ({ pageParam, signal }) =>\n fetchPageRef.current({\n limit: pageSize,\n offset: pageParam,\n filter: normalizedFilter,\n signal,\n }),\n getNextPageParam: (lastPage, _allPages, lastOffset) =>\n lastPage.hasMore ? lastOffset + pageSize : undefined,\n ...passthrough,\n // fetchNextPage/refetch still perform explicit fetches for disabled\n // queries. Keeping this observer disabled prevents focus, invalidation,\n // and polling from replaying every retained historical page. This must\n // win over `passthrough.enabled`, so it is spread last.\n enabled: false,\n },\n queryClient\n );\n\n const historyPages = history.data?.pages;\n const hasLoadedHistory = (historyPages?.length ?? 0) > 0;\n\n const conversations = useMemo(\n () =>\n mergeConversationPages([\n ...(firstPage.data ? [firstPage.data] : []),\n ...(historyPages ?? []),\n ]),\n [firstPage.data, historyPages]\n );\n\n const fetchNextHistoryPage = history.fetchNextPage;\n const refetchHistoryFirstPage = history.refetch;\n const fetchNextPage = useCallback(\n () => (hasLoadedHistory ? fetchNextHistoryPage() : refetchHistoryFirstPage()),\n [hasLoadedHistory, fetchNextHistoryPage, refetchHistoryFirstPage]\n );\n\n const refresh = useCallback(() => firstPage.refetch(), [firstPage.refetch]);\n\n const invalidate = useCallback(\n () => invalidateConversationListCaches(queryClient, { scopeKey }),\n [queryClient, scopeKey]\n );\n\n const prependConversation = useCallback(\n (item: ConversationSummary) => {\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? prependConversationToPage(page, item) : page\n );\n },\n [queryClient, firstPageKey]\n );\n\n const removeConversation = useCallback(\n (threadId: string) => {\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? removeConversationFromPage(page, threadId) : page\n );\n queryClient.setQueryData<InfiniteData<ConversationListPage, number>>(historyKey, (data) =>\n data\n ? { ...data, pages: data.pages.map((page) => removeConversationFromPage(page, threadId)) }\n : data\n );\n },\n [queryClient, firstPageKey, historyKey]\n );\n\n const patchConversation = useCallback(\n (threadId: string, patch: Partial<ConversationSummary>) => {\n const match = (item: ConversationSummary) => item.threadId === threadId;\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? patchConversationsInPage(page, match, patch) : page\n );\n queryClient.setQueryData<InfiniteData<ConversationListPage, number>>(historyKey, (data) =>\n data\n ? {\n ...data,\n pages: data.pages.map((page) => patchConversationsInPage(page, match, patch)),\n }\n : data\n );\n },\n [queryClient, firstPageKey, historyKey]\n );\n\n return {\n conversations,\n totalCount: firstPage.data?.totalCount ?? null,\n isLoading: firstPage.isLoading,\n isFetching: firstPage.isFetching || history.isFetching,\n hasResolvedFirstPage: firstPage.data !== undefined,\n hasNextPage: hasLoadedHistory\n ? (history.hasNextPage ?? false)\n : (firstPage.data?.hasMore ?? false),\n isFetchingNextPage: history.isFetchingNextPage || (!hasLoadedHistory && history.isFetching),\n isFetchNextPageError: history.isError,\n fetchNextPage,\n refresh,\n invalidate,\n prependConversation,\n removeConversation,\n patchConversation,\n };\n}\n"],"names":["CONVERSATION_LIST_DEFAULT_SCOPE_KEY","useQueryClient","useRef","normalizeConversationListFilter","conversationListFilterKey","useMemo","conversationListQueryKeys","useQuery","useInfiniteQuery","mergeConversationPages","useCallback","invalidateConversationListCaches","prependConversationToPage","removeConversationFromPage","patchConversationsInPage"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"use-conversation-list.cjs","sources":["../../../../agent-runtime/src/use-conversation-list.ts"],"sourcesContent":["import {\n type InfiniteData,\n type QueryClient,\n useInfiniteQuery,\n useQuery,\n useQueryClient,\n} from '@tanstack/react-query';\nimport { useCallback, useMemo, useRef } from 'react';\nimport {\n CONVERSATION_LIST_DEFAULT_SCOPE_KEY,\n type ConversationListFetcher,\n type ConversationListFilter,\n type ConversationListPage,\n type ConversationSummary,\n conversationListFilterKey,\n conversationListQueryKeys,\n invalidateConversationListCaches,\n mergeConversationPages,\n normalizeConversationListFilter,\n patchConversationsInPage,\n prependConversationToPage,\n removeConversationFromPage,\n} from './conversation-list';\n\nconst DEFAULT_PAGE_SIZE = 20;\nconst DEFAULT_STALE_TIME_MS = 30_000;\nconst DEFAULT_GC_TIME_MS = 10 * 60 * 1000;\n\nexport interface UseConversationListOptions {\n /**\n * Transport adapter for one page (GraphQL `listConversations`, the REST\n * thread list, …). Captured in a ref — identity changes do not re-key the\n * cache, so pair a changing fetcher with a changing `scopeKey`.\n */\n fetchPage: ConversationListFetcher;\n filter?: ConversationListFilter;\n pageSize?: number;\n /**\n * Cache identity beyond the filter. MUST include everything that changes\n * whose list this is — principal/user id, backend base URL — so a\n * credential swap can never serve another identity's rows from the cache.\n */\n scopeKey?: string;\n enabled?: boolean;\n staleTime?: number;\n gcTime?: number;\n refetchOnMount?: boolean | 'always';\n refetchOnWindowFocus?: boolean;\n /**\n * Newest-page freshness is push-based by default (the conversation-index\n * statewire invalidates these caches), so no timer runs unless asked.\n */\n refetchInterval?: number | false;\n retry?: boolean | number | ((failureCount: number, error: Error) => boolean);\n /**\n * The client to cache in. Defaults to the one in context; a hook that may\n * render outside any `QueryClientProvider` (and stays disabled there)\n * passes its own.\n */\n queryClient?: QueryClient;\n}\n\nexport interface ConversationListController {\n /** Merged newest-first rows: live first page + retained history pages. */\n conversations: ConversationSummary[];\n /** Advisory total from the live first page, when the transport counts. */\n totalCount: number | null;\n /**\n * Whether the live first page's latest answer held the conversations with\n * the filter's `homeAgentId` agent ({@link ConversationListPage.homeAgentApplied}).\n * `false` until it answers, and wherever the server did not widen the list.\n */\n homeAgentApplied: boolean;\n isLoading: boolean;\n isFetching: boolean;\n /** True once the live first page has resolved at least once. */\n hasResolvedFirstPage: boolean;\n hasNextPage: boolean;\n isFetchingNextPage: boolean;\n /** True while the last next-page request failed (cleared by a successful one). */\n isFetchNextPageError: boolean;\n fetchNextPage: () => Promise<unknown>;\n /** Refetch the live first page (history pages stay frozen). */\n refresh: () => Promise<unknown>;\n /** Invalidate every conversation-list cache in this scope (all filters). */\n invalidate: () => Promise<void>;\n /** Optimistically show a new conversation at the top of THIS filter view. */\n prependConversation: (item: ConversationSummary) => void;\n /** Optimistically drop a conversation from THIS filter view. */\n removeConversation: (threadId: string) => void;\n /** Optimistically patch a conversation's row in THIS filter view. */\n patchConversation: (threadId: string, patch: Partial<ConversationSummary>) => void;\n}\n\n/**\n * The shared headless conversation-list pager (chat-stack unification —\n * thread-list-contract-convergence).\n *\n * Implements, once, the two-tier TanStack Query pattern Olympus and mobile\n * each hand-rolled:\n *\n * - a LIVE first-page query — the only observer that refetches on\n * invalidation/focus, so push-driven freshness costs one page, and\n * - a permanently-disabled infinite \"history\" query whose pages are fetched\n * only by explicit `fetchNextPage()` calls and retained without replay\n * (invalidation/polling never refetches every loaded page).\n *\n * Rows merge newest-first with the live page winning duplicate threads.\n */\nexport function useConversationList(options: UseConversationListOptions): ConversationListController {\n const {\n filter,\n pageSize = DEFAULT_PAGE_SIZE,\n scopeKey = CONVERSATION_LIST_DEFAULT_SCOPE_KEY,\n enabled,\n staleTime = DEFAULT_STALE_TIME_MS,\n gcTime = DEFAULT_GC_TIME_MS,\n refetchOnMount,\n refetchOnWindowFocus,\n refetchInterval = false,\n retry,\n } = options;\n const queryClient = useQueryClient(options.queryClient);\n\n const fetchPageRef = useRef(options.fetchPage);\n fetchPageRef.current = options.fetchPage;\n\n // The normalized filter is both the cache identity (via the key) and what\n // queryFn closures fetch, so a cache entry holds exactly what it claims —\n // fresh-but-equal filter objects from the caller never re-key or refetch.\n // (Plain per-render values: only the serialized key participates in\n // identity, so object freshness here is irrelevant.)\n const normalizedFilter = normalizeConversationListFilter(filter ?? {});\n const filterKey = conversationListFilterKey(normalizedFilter);\n\n const firstPageKey = useMemo(\n () => conversationListQueryKeys.firstPage(scopeKey, filterKey, pageSize),\n [scopeKey, filterKey, pageSize]\n );\n const historyKey = useMemo(\n () => conversationListQueryKeys.history(scopeKey, filterKey, pageSize),\n [scopeKey, filterKey, pageSize]\n );\n\n // React Query merges per-query options OVER the client's defaultOptions with\n // Object.assign, which copies explicitly-`undefined` keys — so passing\n // `refetchOnWindowFocus: undefined` CLOBBERS a consumer's\n // `refetchOnWindowFocus: false` default and silently re-enables focus\n // refetching. Options the caller did not set must therefore be omitted, not\n // forwarded as undefined, so they fall through to the consuming app's client.\n const passthrough = {\n ...(enabled !== undefined ? { enabled } : {}),\n ...(refetchOnMount !== undefined ? { refetchOnMount } : {}),\n ...(refetchOnWindowFocus !== undefined ? { refetchOnWindowFocus } : {}),\n ...(retry !== undefined ? { retry } : {}),\n };\n\n const firstPage = useQuery(\n {\n queryKey: firstPageKey,\n queryFn: ({ signal }) =>\n fetchPageRef.current({ limit: pageSize, offset: 0, filter: normalizedFilter, signal }),\n staleTime,\n gcTime,\n refetchInterval,\n ...passthrough,\n },\n queryClient\n );\n\n const history = useInfiniteQuery(\n {\n queryKey: historyKey,\n initialPageParam: pageSize,\n gcTime,\n queryFn: ({ pageParam, signal }) =>\n fetchPageRef.current({\n limit: pageSize,\n offset: pageParam,\n filter: normalizedFilter,\n signal,\n }),\n getNextPageParam: (lastPage, _allPages, lastOffset) =>\n lastPage.hasMore ? lastOffset + pageSize : undefined,\n ...passthrough,\n // fetchNextPage/refetch still perform explicit fetches for disabled\n // queries. Keeping this observer disabled prevents focus, invalidation,\n // and polling from replaying every retained historical page. This must\n // win over `passthrough.enabled`, so it is spread last.\n enabled: false,\n },\n queryClient\n );\n\n const historyPages = history.data?.pages;\n const hasLoadedHistory = (historyPages?.length ?? 0) > 0;\n\n const conversations = useMemo(\n () =>\n mergeConversationPages([\n ...(firstPage.data ? [firstPage.data] : []),\n ...(historyPages ?? []),\n ]),\n [firstPage.data, historyPages]\n );\n\n const fetchNextHistoryPage = history.fetchNextPage;\n const refetchHistoryFirstPage = history.refetch;\n const fetchNextPage = useCallback(\n () => (hasLoadedHistory ? fetchNextHistoryPage() : refetchHistoryFirstPage()),\n [hasLoadedHistory, fetchNextHistoryPage, refetchHistoryFirstPage]\n );\n\n const refresh = useCallback(() => firstPage.refetch(), [firstPage.refetch]);\n\n const invalidate = useCallback(\n () => invalidateConversationListCaches(queryClient, { scopeKey }),\n [queryClient, scopeKey]\n );\n\n const prependConversation = useCallback(\n (item: ConversationSummary) => {\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? prependConversationToPage(page, item) : page\n );\n },\n [queryClient, firstPageKey]\n );\n\n const removeConversation = useCallback(\n (threadId: string) => {\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? removeConversationFromPage(page, threadId) : page\n );\n queryClient.setQueryData<InfiniteData<ConversationListPage, number>>(historyKey, (data) =>\n data\n ? { ...data, pages: data.pages.map((page) => removeConversationFromPage(page, threadId)) }\n : data\n );\n },\n [queryClient, firstPageKey, historyKey]\n );\n\n const patchConversation = useCallback(\n (threadId: string, patch: Partial<ConversationSummary>) => {\n const match = (item: ConversationSummary) => item.threadId === threadId;\n queryClient.setQueryData<ConversationListPage>(firstPageKey, (page) =>\n page ? patchConversationsInPage(page, match, patch) : page\n );\n queryClient.setQueryData<InfiniteData<ConversationListPage, number>>(historyKey, (data) =>\n data\n ? {\n ...data,\n pages: data.pages.map((page) => patchConversationsInPage(page, match, patch)),\n }\n : data\n );\n },\n [queryClient, firstPageKey, historyKey]\n );\n\n return {\n conversations,\n totalCount: firstPage.data?.totalCount ?? null,\n homeAgentApplied: firstPage.data?.homeAgentApplied === true,\n isLoading: firstPage.isLoading,\n isFetching: firstPage.isFetching || history.isFetching,\n hasResolvedFirstPage: firstPage.data !== undefined,\n hasNextPage: hasLoadedHistory\n ? (history.hasNextPage ?? false)\n : (firstPage.data?.hasMore ?? false),\n isFetchingNextPage: history.isFetchingNextPage || (!hasLoadedHistory && history.isFetching),\n isFetchNextPageError: history.isError,\n fetchNextPage,\n refresh,\n invalidate,\n prependConversation,\n removeConversation,\n patchConversation,\n };\n}\n"],"names":["CONVERSATION_LIST_DEFAULT_SCOPE_KEY","useQueryClient","useRef","normalizeConversationListFilter","conversationListFilterKey","useMemo","conversationListQueryKeys","useQuery","useInfiniteQuery","mergeConversationPages","useCallback","invalidateConversationListCaches","prependConversationToPage","removeConversationFromPage","patchConversationsInPage"],"mappings":"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"}
|
|
@@ -38,6 +38,12 @@ export interface ConversationListController {
|
|
|
38
38
|
conversations: ConversationSummary[];
|
|
39
39
|
/** Advisory total from the live first page, when the transport counts. */
|
|
40
40
|
totalCount: number | null;
|
|
41
|
+
/**
|
|
42
|
+
* Whether the live first page's latest answer held the conversations with
|
|
43
|
+
* the filter's `homeAgentId` agent ({@link ConversationListPage.homeAgentApplied}).
|
|
44
|
+
* `false` until it answers, and wherever the server did not widen the list.
|
|
45
|
+
*/
|
|
46
|
+
homeAgentApplied: boolean;
|
|
41
47
|
isLoading: boolean;
|
|
42
48
|
isFetching: boolean;
|
|
43
49
|
/** True once the live first page has resolved at least once. */
|