@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.
Files changed (255) hide show
  1. package/README.md +68 -10
  2. package/dist/agent-runtime/src/call-recap.cjs +319 -0
  3. package/dist/agent-runtime/src/call-recap.cjs.map +1 -0
  4. package/dist/agent-runtime/src/call-recap.d.ts +142 -0
  5. package/dist/agent-runtime/src/call-recap.js +319 -0
  6. package/dist/agent-runtime/src/call-recap.js.map +1 -0
  7. package/dist/agent-runtime/src/chat-outbox-store.cjs +1 -0
  8. package/dist/agent-runtime/src/chat-outbox-store.cjs.map +1 -1
  9. package/dist/agent-runtime/src/chat-outbox-store.d.ts +7 -0
  10. package/dist/agent-runtime/src/chat-outbox-store.js +1 -0
  11. package/dist/agent-runtime/src/chat-outbox-store.js.map +1 -1
  12. package/dist/agent-runtime/src/chat-outbox.cjs +46 -5
  13. package/dist/agent-runtime/src/chat-outbox.cjs.map +1 -1
  14. package/dist/agent-runtime/src/chat-outbox.d.ts +29 -0
  15. package/dist/agent-runtime/src/chat-outbox.js +46 -5
  16. package/dist/agent-runtime/src/chat-outbox.js.map +1 -1
  17. package/dist/agent-runtime/src/conversation-index.cjs.map +1 -1
  18. package/dist/agent-runtime/src/conversation-index.d.ts +7 -0
  19. package/dist/agent-runtime/src/conversation-index.js.map +1 -1
  20. package/dist/agent-runtime/src/conversation-list.cjs +2 -1
  21. package/dist/agent-runtime/src/conversation-list.cjs.map +1 -1
  22. package/dist/agent-runtime/src/conversation-list.d.ts +19 -3
  23. package/dist/agent-runtime/src/conversation-list.js +2 -1
  24. package/dist/agent-runtime/src/conversation-list.js.map +1 -1
  25. package/dist/agent-runtime/src/desktop-handoff.d.ts +10 -0
  26. package/dist/agent-runtime/src/edit-command.cjs +21 -0
  27. package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
  28. package/dist/agent-runtime/src/edit-command.d.ts +8 -0
  29. package/dist/agent-runtime/src/edit-command.js +21 -0
  30. package/dist/agent-runtime/src/edit-command.js.map +1 -1
  31. package/dist/agent-runtime/src/handoff-arrival.cjs +19 -0
  32. package/dist/agent-runtime/src/handoff-arrival.cjs.map +1 -1
  33. package/dist/agent-runtime/src/handoff-arrival.d.ts +18 -3
  34. package/dist/agent-runtime/src/handoff-arrival.js +20 -1
  35. package/dist/agent-runtime/src/handoff-arrival.js.map +1 -1
  36. package/dist/agent-runtime/src/handoff-cursor-id.cjs +6 -0
  37. package/dist/agent-runtime/src/handoff-cursor-id.cjs.map +1 -0
  38. package/dist/agent-runtime/src/handoff-cursor-id.d.ts +12 -0
  39. package/dist/agent-runtime/src/handoff-cursor-id.js +6 -0
  40. package/dist/agent-runtime/src/handoff-cursor-id.js.map +1 -0
  41. package/dist/agent-runtime/src/handoff-cursor.cjs +88 -0
  42. package/dist/agent-runtime/src/handoff-cursor.cjs.map +1 -0
  43. package/dist/agent-runtime/src/handoff-cursor.d.ts +120 -0
  44. package/dist/agent-runtime/src/handoff-cursor.js +88 -0
  45. package/dist/agent-runtime/src/handoff-cursor.js.map +1 -0
  46. package/dist/agent-runtime/src/index.d.ts +9 -7
  47. package/dist/agent-runtime/src/interrupts.cjs +28 -1
  48. package/dist/agent-runtime/src/interrupts.cjs.map +1 -1
  49. package/dist/agent-runtime/src/interrupts.d.ts +21 -1
  50. package/dist/agent-runtime/src/interrupts.js +28 -1
  51. package/dist/agent-runtime/src/interrupts.js.map +1 -1
  52. package/dist/agent-runtime/src/model-context.cjs +3 -2
  53. package/dist/agent-runtime/src/model-context.cjs.map +1 -1
  54. package/dist/agent-runtime/src/model-context.d.ts +6 -1
  55. package/dist/agent-runtime/src/model-context.js +3 -2
  56. package/dist/agent-runtime/src/model-context.js.map +1 -1
  57. package/dist/agent-runtime/src/runtime.d.ts +3 -2
  58. package/dist/agent-runtime/src/send-recovery.cjs +37 -3
  59. package/dist/agent-runtime/src/send-recovery.cjs.map +1 -1
  60. package/dist/agent-runtime/src/send-recovery.d.ts +19 -0
  61. package/dist/agent-runtime/src/send-recovery.js +37 -3
  62. package/dist/agent-runtime/src/send-recovery.js.map +1 -1
  63. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +14 -8
  64. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +227 -5
  65. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  66. package/dist/agent-runtime/src/use-conversation-list.cjs +3 -2
  67. package/dist/agent-runtime/src/use-conversation-list.cjs.map +1 -1
  68. package/dist/agent-runtime/src/use-conversation-list.d.ts +6 -0
  69. package/dist/agent-runtime/src/use-conversation-list.js +3 -2
  70. package/dist/agent-runtime/src/use-conversation-list.js.map +1 -1
  71. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs +21 -1
  72. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.cjs.map +1 -1
  73. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.d.ts +8 -2
  74. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js +21 -1
  75. package/dist/agent-runtime/src/use-deep-agent-chat-outbox.js.map +1 -1
  76. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs +24 -6
  77. package/dist/agent-runtime/src/use-deep-agent-runtime.cjs.map +1 -1
  78. package/dist/agent-runtime/src/use-deep-agent-runtime.d.ts +17 -0
  79. package/dist/agent-runtime/src/use-deep-agent-runtime.js +24 -6
  80. package/dist/agent-runtime/src/use-deep-agent-runtime.js.map +1 -1
  81. package/dist/agent-runtime/src/use-deep-agent-voice.cjs +44 -9
  82. package/dist/agent-runtime/src/use-deep-agent-voice.cjs.map +1 -1
  83. package/dist/agent-runtime/src/use-deep-agent-voice.d.ts +34 -1
  84. package/dist/agent-runtime/src/use-deep-agent-voice.js +44 -9
  85. package/dist/agent-runtime/src/use-deep-agent-voice.js.map +1 -1
  86. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +8 -3
  87. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
  88. package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +7 -0
  89. package/dist/agent-runtime/src/use-deep-agent-window-sync.js +9 -4
  90. package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
  91. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +13 -2
  92. package/dist/agent-runtime/src/voice-earcon.cjs +139 -0
  93. package/dist/agent-runtime/src/voice-earcon.cjs.map +1 -0
  94. package/dist/agent-runtime/src/voice-earcon.d.ts +38 -0
  95. package/dist/agent-runtime/src/voice-earcon.js +139 -0
  96. package/dist/agent-runtime/src/voice-earcon.js.map +1 -0
  97. package/dist/agent-runtime/src/voice-media.cjs.map +1 -1
  98. package/dist/agent-runtime/src/voice-media.d.ts +14 -0
  99. package/dist/agent-runtime/src/voice-media.js.map +1 -1
  100. package/dist/agent-runtime/src/voice-presentation.cjs +2 -0
  101. package/dist/agent-runtime/src/voice-presentation.cjs.map +1 -1
  102. package/dist/agent-runtime/src/voice-presentation.js +2 -0
  103. package/dist/agent-runtime/src/voice-presentation.js.map +1 -1
  104. package/dist/assets/AthenaAssetEmbed.cjs.map +1 -1
  105. package/dist/assets/AthenaAssetEmbed.d.ts +11 -5
  106. package/dist/assets/AthenaAssetEmbed.js.map +1 -1
  107. package/dist/assets/use-asset-embed.cjs.map +1 -1
  108. package/dist/assets/use-asset-embed.d.ts +12 -5
  109. package/dist/assets/use-asset-embed.js.map +1 -1
  110. package/dist/chat/AthenaChat.cjs +27 -12
  111. package/dist/chat/AthenaChat.cjs.map +1 -1
  112. package/dist/chat/AthenaChat.d.ts +13 -0
  113. package/dist/chat/AthenaChat.js +27 -12
  114. package/dist/chat/AthenaChat.js.map +1 -1
  115. package/dist/chat/AthenaPlanStrip.cjs +97 -0
  116. package/dist/chat/AthenaPlanStrip.cjs.map +1 -0
  117. package/dist/chat/AthenaPlanStrip.d.ts +15 -0
  118. package/dist/chat/AthenaPlanStrip.js +97 -0
  119. package/dist/chat/AthenaPlanStrip.js.map +1 -0
  120. package/dist/chat/StatewireApprovalCard.cjs +41 -6
  121. package/dist/chat/StatewireApprovalCard.cjs.map +1 -1
  122. package/dist/chat/StatewireApprovalCard.d.ts +18 -1
  123. package/dist/chat/StatewireApprovalCard.js +42 -7
  124. package/dist/chat/StatewireApprovalCard.js.map +1 -1
  125. package/dist/chat/StatewireQueuedMessages.cjs +56 -6
  126. package/dist/chat/StatewireQueuedMessages.cjs.map +1 -1
  127. package/dist/chat/StatewireQueuedMessages.d.ts +14 -0
  128. package/dist/chat/StatewireQueuedMessages.js +58 -8
  129. package/dist/chat/StatewireQueuedMessages.js.map +1 -1
  130. package/dist/chat/WaitingOnYouBanner.cjs +6 -2
  131. package/dist/chat/WaitingOnYouBanner.cjs.map +1 -1
  132. package/dist/chat/WaitingOnYouBanner.js +7 -3
  133. package/dist/chat/WaitingOnYouBanner.js.map +1 -1
  134. package/dist/chat/chat-voice-controls.cjs +3 -1
  135. package/dist/chat/chat-voice-controls.cjs.map +1 -1
  136. package/dist/chat/chat-voice-controls.d.ts +6 -1
  137. package/dist/chat/chat-voice-controls.js +3 -1
  138. package/dist/chat/chat-voice-controls.js.map +1 -1
  139. package/dist/chat/super-grouping/SuperGroupingCard.cjs +4 -2
  140. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  141. package/dist/chat/super-grouping/SuperGroupingCard.js +4 -2
  142. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  143. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +4 -2
  144. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  145. package/dist/chat/super-grouping/SuperGroupingTranscript.js +4 -2
  146. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  147. package/dist/chat/super-grouping/activity-timeline.cjs +6 -4
  148. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  149. package/dist/chat/super-grouping/activity-timeline.d.ts +9 -1
  150. package/dist/chat/super-grouping/activity-timeline.js +6 -4
  151. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  152. package/dist/chat/super-grouping/index.d.ts +1 -1
  153. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs +17 -0
  154. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs.map +1 -0
  155. package/dist/chat/super-grouping/use-earlier-steps-unloaded.d.ts +8 -0
  156. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js +17 -0
  157. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js.map +1 -0
  158. package/dist/chat-ui/index.cjs +11 -0
  159. package/dist/chat-ui/index.cjs.map +1 -1
  160. package/dist/chat-ui/index.d.ts +3 -1
  161. package/dist/chat-ui/index.js +12 -1
  162. package/dist/chat-ui/index.js.map +1 -1
  163. package/dist/chat-ui/messages/from-the-call-card.cjs +124 -0
  164. package/dist/chat-ui/messages/from-the-call-card.cjs.map +1 -0
  165. package/dist/chat-ui/messages/from-the-call-card.d.ts +17 -0
  166. package/dist/chat-ui/messages/from-the-call-card.js +124 -0
  167. package/dist/chat-ui/messages/from-the-call-card.js.map +1 -0
  168. package/dist/chat-ui/rich-text/chat-link.cjs +1 -0
  169. package/dist/chat-ui/rich-text/chat-link.cjs.map +1 -1
  170. package/dist/chat-ui/rich-text/chat-link.d.ts +4 -0
  171. package/dist/chat-ui/rich-text/chat-link.js +4 -3
  172. package/dist/chat-ui/rich-text/chat-link.js.map +1 -1
  173. package/dist/chat-ui/rich-text/chat-message-body.cjs +1 -0
  174. package/dist/chat-ui/rich-text/chat-message-body.cjs.map +1 -1
  175. package/dist/chat-ui/rich-text/chat-message-body.d.ts +12 -0
  176. package/dist/chat-ui/rich-text/chat-message-body.js +2 -1
  177. package/dist/chat-ui/rich-text/chat-message-body.js.map +1 -1
  178. package/dist/chat-ui/thread/chat-thread-shell.cjs +4 -0
  179. package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
  180. package/dist/chat-ui/thread/chat-thread-shell.d.ts +4 -0
  181. package/dist/chat-ui/thread/chat-thread-shell.js +4 -0
  182. package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
  183. package/dist/chat-ui/thread/handoff-cursor.cjs +216 -0
  184. package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -0
  185. package/dist/chat-ui/thread/handoff-cursor.d.ts +57 -0
  186. package/dist/chat-ui/thread/handoff-cursor.js +216 -0
  187. package/dist/chat-ui/thread/handoff-cursor.js.map +1 -0
  188. package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -0
  189. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  190. package/dist/chat-ui/thread/statewire-history-edge.d.ts +2 -0
  191. package/dist/chat-ui/thread/statewire-history-edge.js +3 -1
  192. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  193. package/dist/collab/client.cjs +15 -3
  194. package/dist/collab/client.cjs.map +1 -1
  195. package/dist/collab/client.d.ts +4 -2
  196. package/dist/collab/client.js +16 -4
  197. package/dist/collab/client.js.map +1 -1
  198. package/dist/composer/index.d.ts +1 -1
  199. package/dist/composer/useReadComposerDraft.cjs +26 -7
  200. package/dist/composer/useReadComposerDraft.cjs.map +1 -1
  201. package/dist/composer/useReadComposerDraft.d.ts +23 -1
  202. package/dist/composer/useReadComposerDraft.js +27 -8
  203. package/dist/composer/useReadComposerDraft.js.map +1 -1
  204. package/dist/index.cjs +5 -0
  205. package/dist/index.cjs.map +1 -1
  206. package/dist/index.d.ts +5 -2
  207. package/dist/index.js +6 -1
  208. package/dist/index.js.map +1 -1
  209. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +207 -0
  210. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -0
  211. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +141 -0
  212. package/dist/keryx-http/src/unconfirmed-update-attributions.js +207 -0
  213. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -0
  214. package/dist/keryx-http/src/update-attributions.cjs +34 -4
  215. package/dist/keryx-http/src/update-attributions.cjs.map +1 -1
  216. package/dist/keryx-http/src/update-attributions.d.ts +36 -4
  217. package/dist/keryx-http/src/update-attributions.js +35 -5
  218. package/dist/keryx-http/src/update-attributions.js.map +1 -1
  219. package/dist/provider/AthenaProvider.cjs +4 -0
  220. package/dist/provider/AthenaProvider.cjs.map +1 -1
  221. package/dist/provider/AthenaProvider.d.ts +11 -1
  222. package/dist/provider/AthenaProvider.js +4 -0
  223. package/dist/provider/AthenaProvider.js.map +1 -1
  224. package/dist/react/package.json.cjs +1 -1
  225. package/dist/react/package.json.js +1 -1
  226. package/dist/styles.css +1 -1
  227. package/dist/threads/api.cjs.map +1 -1
  228. package/dist/threads/api.d.ts +15 -0
  229. package/dist/threads/api.js.map +1 -1
  230. package/dist/threads/conversation-list-fetcher.cjs +13 -5
  231. package/dist/threads/conversation-list-fetcher.cjs.map +1 -1
  232. package/dist/threads/conversation-list-fetcher.d.ts +4 -1
  233. package/dist/threads/conversation-list-fetcher.js +13 -5
  234. package/dist/threads/conversation-list-fetcher.js.map +1 -1
  235. package/dist/threads/statewire-thread-list.cjs +28 -6
  236. package/dist/threads/statewire-thread-list.cjs.map +1 -1
  237. package/dist/threads/statewire-thread-list.d.ts +20 -8
  238. package/dist/threads/statewire-thread-list.js +28 -6
  239. package/dist/threads/statewire-thread-list.js.map +1 -1
  240. package/dist/threads/use-athena-sessions.cjs +27 -5
  241. package/dist/threads/use-athena-sessions.cjs.map +1 -1
  242. package/dist/threads/use-athena-sessions.d.ts +10 -5
  243. package/dist/threads/use-athena-sessions.js +28 -6
  244. package/dist/threads/use-athena-sessions.js.map +1 -1
  245. package/dist/threads/use-rest-conversation-history.d.ts +1 -0
  246. package/dist/tools/write-todos-tool-ui.cjs +8 -0
  247. package/dist/tools/write-todos-tool-ui.cjs.map +1 -1
  248. package/dist/tools/write-todos-tool-ui.d.ts +2 -0
  249. package/dist/tools/write-todos-tool-ui.js +8 -0
  250. package/dist/tools/write-todos-tool-ui.js.map +1 -1
  251. package/dist/workbench/session-workbench-panes.cjs +1 -8
  252. package/dist/workbench/session-workbench-panes.cjs.map +1 -1
  253. package/dist/workbench/session-workbench-panes.js +2 -9
  254. package/dist/workbench/session-workbench-panes.js.map +1 -1
  255. 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
- /** The composer's unsent text; the destination prefills it and never sends it. */
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
- /** `/pending?destination=…&exclude=…` relative to `/api/handoffs`. */
433
- export declare function pendingHandoffPath({ destination, exclude, }: {
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 : ((_c = firstPage.data) == null ? void 0 : _c.hasMore) ?? 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. */