@athenaintel/react 0.50.0 → 0.53.0

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 (112) hide show
  1. package/README.md +24 -0
  2. package/dist/agent-runtime/src/desktop-handoff.d.ts +10 -0
  3. package/dist/agent-runtime/src/edit-command.cjs +21 -0
  4. package/dist/agent-runtime/src/edit-command.cjs.map +1 -1
  5. package/dist/agent-runtime/src/edit-command.d.ts +8 -0
  6. package/dist/agent-runtime/src/edit-command.js +21 -0
  7. package/dist/agent-runtime/src/edit-command.js.map +1 -1
  8. package/dist/agent-runtime/src/handoff-arrival.cjs +19 -0
  9. package/dist/agent-runtime/src/handoff-arrival.cjs.map +1 -1
  10. package/dist/agent-runtime/src/handoff-arrival.d.ts +18 -3
  11. package/dist/agent-runtime/src/handoff-arrival.js +20 -1
  12. package/dist/agent-runtime/src/handoff-arrival.js.map +1 -1
  13. package/dist/agent-runtime/src/handoff-cursor-id.cjs +6 -0
  14. package/dist/agent-runtime/src/handoff-cursor-id.cjs.map +1 -0
  15. package/dist/agent-runtime/src/handoff-cursor-id.d.ts +12 -0
  16. package/dist/agent-runtime/src/handoff-cursor-id.js +6 -0
  17. package/dist/agent-runtime/src/handoff-cursor-id.js.map +1 -0
  18. package/dist/agent-runtime/src/handoff-cursor.cjs +88 -0
  19. package/dist/agent-runtime/src/handoff-cursor.cjs.map +1 -0
  20. package/dist/agent-runtime/src/handoff-cursor.d.ts +120 -0
  21. package/dist/agent-runtime/src/handoff-cursor.js +88 -0
  22. package/dist/agent-runtime/src/handoff-cursor.js.map +1 -0
  23. package/dist/agent-runtime/src/index.d.ts +4 -3
  24. package/dist/agent-runtime/src/thread-handoff-flow-machine.d.ts +6 -0
  25. package/dist/agent-runtime/src/thread-handoff-protocol.d.ts +114 -4
  26. package/dist/agent-runtime/src/thread-handoff.d.ts +1 -1
  27. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs +8 -3
  28. package/dist/agent-runtime/src/use-deep-agent-window-sync.cjs.map +1 -1
  29. package/dist/agent-runtime/src/use-deep-agent-window-sync.d.ts +7 -0
  30. package/dist/agent-runtime/src/use-deep-agent-window-sync.js +9 -4
  31. package/dist/agent-runtime/src/use-deep-agent-window-sync.js.map +1 -1
  32. package/dist/agent-runtime/src/use-thread-handoff-flow.d.ts +5 -0
  33. package/dist/chat/AthenaChat.cjs +27 -12
  34. package/dist/chat/AthenaChat.cjs.map +1 -1
  35. package/dist/chat/AthenaChat.d.ts +13 -0
  36. package/dist/chat/AthenaChat.js +27 -12
  37. package/dist/chat/AthenaChat.js.map +1 -1
  38. package/dist/chat/AthenaPlanStrip.cjs +97 -0
  39. package/dist/chat/AthenaPlanStrip.cjs.map +1 -0
  40. package/dist/chat/AthenaPlanStrip.d.ts +15 -0
  41. package/dist/chat/AthenaPlanStrip.js +97 -0
  42. package/dist/chat/AthenaPlanStrip.js.map +1 -0
  43. package/dist/chat/super-grouping/SuperGroupingCard.cjs +4 -2
  44. package/dist/chat/super-grouping/SuperGroupingCard.cjs.map +1 -1
  45. package/dist/chat/super-grouping/SuperGroupingCard.js +4 -2
  46. package/dist/chat/super-grouping/SuperGroupingCard.js.map +1 -1
  47. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs +4 -2
  48. package/dist/chat/super-grouping/SuperGroupingTranscript.cjs.map +1 -1
  49. package/dist/chat/super-grouping/SuperGroupingTranscript.js +4 -2
  50. package/dist/chat/super-grouping/SuperGroupingTranscript.js.map +1 -1
  51. package/dist/chat/super-grouping/activity-timeline.cjs +6 -4
  52. package/dist/chat/super-grouping/activity-timeline.cjs.map +1 -1
  53. package/dist/chat/super-grouping/activity-timeline.d.ts +9 -1
  54. package/dist/chat/super-grouping/activity-timeline.js +6 -4
  55. package/dist/chat/super-grouping/activity-timeline.js.map +1 -1
  56. package/dist/chat/super-grouping/index.d.ts +1 -1
  57. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs +17 -0
  58. package/dist/chat/super-grouping/use-earlier-steps-unloaded.cjs.map +1 -0
  59. package/dist/chat/super-grouping/use-earlier-steps-unloaded.d.ts +8 -0
  60. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js +17 -0
  61. package/dist/chat/super-grouping/use-earlier-steps-unloaded.js.map +1 -0
  62. package/dist/chat-ui/index.cjs +8 -0
  63. package/dist/chat-ui/index.cjs.map +1 -1
  64. package/dist/chat-ui/index.d.ts +2 -1
  65. package/dist/chat-ui/index.js +9 -1
  66. package/dist/chat-ui/index.js.map +1 -1
  67. package/dist/chat-ui/mentions/mention-chip.cjs +1 -12
  68. package/dist/chat-ui/mentions/mention-chip.cjs.map +1 -1
  69. package/dist/chat-ui/mentions/mention-chip.js +1 -12
  70. package/dist/chat-ui/mentions/mention-chip.js.map +1 -1
  71. package/dist/chat-ui/mentions/use-mention-asset.cjs +18 -3
  72. package/dist/chat-ui/mentions/use-mention-asset.cjs.map +1 -1
  73. package/dist/chat-ui/mentions/use-mention-asset.js +19 -4
  74. package/dist/chat-ui/mentions/use-mention-asset.js.map +1 -1
  75. package/dist/chat-ui/thread/chat-thread-shell.cjs +2 -0
  76. package/dist/chat-ui/thread/chat-thread-shell.cjs.map +1 -1
  77. package/dist/chat-ui/thread/chat-thread-shell.d.ts +4 -0
  78. package/dist/chat-ui/thread/chat-thread-shell.js +2 -0
  79. package/dist/chat-ui/thread/chat-thread-shell.js.map +1 -1
  80. package/dist/chat-ui/thread/handoff-cursor.cjs +216 -0
  81. package/dist/chat-ui/thread/handoff-cursor.cjs.map +1 -0
  82. package/dist/chat-ui/thread/handoff-cursor.d.ts +57 -0
  83. package/dist/chat-ui/thread/handoff-cursor.js +216 -0
  84. package/dist/chat-ui/thread/handoff-cursor.js.map +1 -0
  85. package/dist/chat-ui/thread/statewire-history-edge.cjs +2 -0
  86. package/dist/chat-ui/thread/statewire-history-edge.cjs.map +1 -1
  87. package/dist/chat-ui/thread/statewire-history-edge.d.ts +2 -0
  88. package/dist/chat-ui/thread/statewire-history-edge.js +3 -1
  89. package/dist/chat-ui/thread/statewire-history-edge.js.map +1 -1
  90. package/dist/index.cjs +2 -0
  91. package/dist/index.cjs.map +1 -1
  92. package/dist/index.d.ts +2 -1
  93. package/dist/index.js +2 -0
  94. package/dist/index.js.map +1 -1
  95. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs +20 -3
  96. package/dist/keryx-http/src/unconfirmed-update-attributions.cjs.map +1 -1
  97. package/dist/keryx-http/src/unconfirmed-update-attributions.d.ts +11 -2
  98. package/dist/keryx-http/src/unconfirmed-update-attributions.js +21 -4
  99. package/dist/keryx-http/src/unconfirmed-update-attributions.js.map +1 -1
  100. package/dist/react/package.json.cjs +1 -1
  101. package/dist/react/package.json.js +1 -1
  102. package/dist/styles.css +1 -1
  103. package/dist/tools/write-todos-tool-ui.cjs +8 -0
  104. package/dist/tools/write-todos-tool-ui.cjs.map +1 -1
  105. package/dist/tools/write-todos-tool-ui.d.ts +2 -0
  106. package/dist/tools/write-todos-tool-ui.js +8 -0
  107. package/dist/tools/write-todos-tool-ui.js.map +1 -1
  108. package/dist/workbench/session-workbench-panes.cjs +1 -8
  109. package/dist/workbench/session-workbench-panes.cjs.map +1 -1
  110. package/dist/workbench/session-workbench-panes.js +2 -9
  111. package/dist/workbench/session-workbench-panes.js.map +1 -1
  112. package/package.json +2 -2
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoff-cursor.cjs","sources":["../../../../agent-runtime/src/handoff-cursor.ts"],"sourcesContent":["/**\n * Where a handed-off conversation opens: at the message the source was\n * reading (\"the view at the last message the source showed\"), not just at the\n * bottom.\n *\n * The source reads its own transcript when the person issues the handover\n * (`readingCursorOf` on the web, the visible rows on the phone): `null` when\n * it was at the bottom, else the id of the last message fully in view or,\n * when one long message fills the view, the message at the reading line. The\n * id travels as the handover's `cursor` (an opaque id from the person's own\n * thread, never content). The destination records it with the arrival\n * (`recordHandoffArrival({ cursor })`) and its transcript reveals that message\n * once it has it, paging older history in when needed\n * (`handoffCursorRevealStep`), and highlights it briefly. A cursor it can't\n * find leaves the view at the bottom, as before: it is a hint, never a wait.\n */\nimport { getExternalStoreMessages, type ThreadMessage } from '@assistant-ui/core';\nimport { parseHandoffCursor } from './handoff-cursor-id';\n\n/** Whether older history is left to page in (`null` until the window is known). */\nexport { deepAgentHistoryHasOlder } from './edit-command';\n\n/** How far from the bottom still counts as \"at the bottom\": no cursor is sent. */\nexport const READING_CURSOR_BOTTOM_TOLERANCE_PX = 48;\n/** The reading line, as a fraction of the visible height from its top. */\nexport const READING_LINE_FRACTION = 1 / 3;\n\n/** One rendered message row, in the viewport's coordinates. */\nexport interface ReadingRow {\n id: string;\n top: number;\n bottom: number;\n}\n\n/**\n * The reading position among `rows` (in document order) visible between `top`\n * and `bottom`: the last row fully inside, else the row across the reading\n * line (a message taller than the view). `null` when neither is a message id.\n */\nexport function readingCursorFromRows({\n top,\n bottom,\n rows,\n}: {\n top: number;\n bottom: number;\n rows: readonly ReadingRow[];\n}): string | null {\n const line = top + (bottom - top) * READING_LINE_FRACTION;\n let lastFull: ReadingRow | null = null;\n let atLine: ReadingRow | null = null;\n for (const row of rows) {\n if (row.bottom <= row.top) continue;\n if (row.top >= top - 1 && row.bottom <= bottom + 1) lastFull = row;\n if (row.top <= line && row.bottom > line) atLine = row;\n }\n return parseHandoffCursor((lastFull ?? atLine)?.id);\n}\n\n/**\n * The reading position of a list that reports which rows are visible, in\n * order and partly visible ones included, but not where they sit (a native\n * list's viewability). Only the first and last visible rows can be cut off,\n * so with three or more the last fully visible one is the one before the\n * last; with fewer, the first (the one across the reading line, as far as the\n * list can tell). Callers check \"at the bottom\" themselves.\n */\nexport function readingCursorFromVisibleIds(ids: readonly string[]): string | null {\n return parseHandoffCursor(ids.length >= 3 ? ids[ids.length - 2] : ids[0]);\n}\n\ninterface BoxLike {\n getBoundingClientRect(): { top: number; bottom: number };\n}\n\ninterface RowLike extends BoxLike {\n getAttribute(name: string): string | null;\n}\n\n/** The scroll container of a web transcript (structural, so Core needs no DOM types). */\nexport interface ReadingViewportLike extends BoxLike {\n scrollTop: number;\n scrollHeight: number;\n clientHeight: number;\n querySelectorAll(selectors: string): ArrayLike<RowLike>;\n}\n\n/** assistant-ui stamps every message row with its id. */\nexport const MESSAGE_ROW_ID_ATTRIBUTE = 'data-message-id';\n\n/**\n * Where a web transcript is being read: `null` at the bottom, else the message\n * id `readingCursorFromRows` picks. `coveredBottomPx` is what a sticky footer\n * (the composer) hides at the bottom of the viewport.\n */\nexport function readingCursorOf({\n viewport,\n coveredBottomPx = 0,\n idAttribute = MESSAGE_ROW_ID_ATTRIBUTE,\n}: {\n viewport: ReadingViewportLike;\n coveredBottomPx?: number;\n idAttribute?: string;\n}): string | null {\n const fromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;\n if (fromBottom <= READING_CURSOR_BOTTOM_TOLERANCE_PX) return null;\n const box = viewport.getBoundingClientRect();\n const rows: ReadingRow[] = Array.from(viewport.querySelectorAll(`[${idAttribute}]`), (row) => {\n const rect = row.getBoundingClientRect();\n return { id: row.getAttribute(idAttribute) ?? '', top: rect.top, bottom: rect.bottom };\n });\n return readingCursorFromRows({\n top: box.top,\n bottom: Math.max(box.top, box.bottom - coveredBottomPx),\n rows,\n });\n}\n\n/**\n * The id of the displayed message that holds `cursor`. A displayed message is\n * one agent turn whose id is its first wire message's, and that id changes\n * when an older page folds into it, so the cursor is matched against every\n * wire message behind each row as well.\n */\nexport function handoffCursorMessageId({\n messages,\n cursor,\n}: {\n messages: readonly ThreadMessage[];\n cursor: string;\n}): string | null {\n for (const message of messages) {\n if (message.id === cursor) return message.id;\n }\n for (const message of messages) {\n const sources: unknown = getExternalStoreMessages(message);\n if (!Array.isArray(sources)) continue;\n for (const source of sources) {\n if (source && typeof source === 'object' && (source as { id?: unknown }).id === cursor) {\n return message.id;\n }\n }\n }\n return null;\n}\n\n/** Pages of older history one landing may load looking for its message. */\nexport const HANDOFF_CURSOR_MAX_HISTORY_PAGES = 20;\n/**\n * How long a landing waits without progress before staying at the bottom: a\n * transcript still attaching, or a page of older history that never lands.\n * Each page that lands starts the wait again, so a long conversation on a slow\n * connection still reaches its message within the page budget.\n */\nexport const HANDOFF_CURSOR_WAIT_MS = 30_000;\n/** How long the revealed message stays highlighted. */\nexport const HANDOFF_CURSOR_HIGHLIGHT_MS = 2_400;\n/**\n * How long the revealed message is held in view while the transcript settles\n * (an attaching thread can still scroll itself to the bottom). Any scroll the\n * person makes ends it at once.\n */\nexport const HANDOFF_CURSOR_SETTLE_MS = 1_500;\n\nexport type HandoffCursorRevealStep = 'reveal' | 'wait' | 'page' | 'bottom';\n\n/**\n * What a destination does next while opening at a cursor: `reveal` the message\n * it found; `wait` while the transcript (or a page it asked for) is loading or\n * whether older history exists is not known yet (`hasOlder: null`: a cached\n * transcript shows before its history window resolves); `page` older history\n * in while some is left and the budget allows; else stay at the `bottom`, the\n * fallback for a cursor that can't be found.\n */\nexport function handoffCursorRevealStep({\n found,\n loading,\n hasOlder,\n pagesLoaded,\n elapsedMs,\n}: {\n found: boolean;\n loading: boolean;\n hasOlder: boolean | null;\n pagesLoaded: number;\n /** Since the landing last made progress: it started, or a page of older history landed. */\n elapsedMs: number;\n}): HandoffCursorRevealStep {\n if (found) return 'reveal';\n if (elapsedMs >= HANDOFF_CURSOR_WAIT_MS) return 'bottom';\n if (loading || hasOlder === null) return 'wait';\n if (hasOlder && pagesLoaded < HANDOFF_CURSOR_MAX_HISTORY_PAGES) return 'page';\n return 'bottom';\n}\n"],"names":["parseHandoffCursor","getExternalStoreMessages"],"mappings":";;;;AAuBO,MAAM,qCAAqC;AAE3C,MAAM,wBAAwB,IAAI;AAclC,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF,GAIkB;;AAChB,QAAM,OAAO,OAAO,SAAS,OAAO;AACpC,MAAI,WAA8B;AAClC,MAAI,SAA4B;AAChC,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,UAAU,IAAI,IAAK;AAC3B,QAAI,IAAI,OAAO,MAAM,KAAK,IAAI,UAAU,SAAS,EAAG,YAAW;AAC/D,QAAI,IAAI,OAAO,QAAQ,IAAI,SAAS,KAAM,UAAS;AAAA,EACrD;AACA,SAAOA,oCAAoB,iBAAY,WAAZ,mBAAqB,EAAE;AACpD;AA+BO,MAAM,2BAA2B;AAOjC,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,kBAAkB;AAAA,EAClB,cAAc;AAChB,GAIkB;AAChB,QAAM,aAAa,SAAS,eAAe,SAAS,YAAY,SAAS;AACzE,MAAI,cAAc,mCAAoC,QAAO;AAC7D,QAAM,MAAM,SAAS,sBAAA;AACrB,QAAM,OAAqB,MAAM,KAAK,SAAS,iBAAiB,IAAI,WAAW,GAAG,GAAG,CAAC,QAAQ;AAC5F,UAAM,OAAO,IAAI,sBAAA;AACjB,WAAO,EAAE,IAAI,IAAI,aAAa,WAAW,KAAK,IAAI,KAAK,KAAK,KAAK,QAAQ,KAAK,OAAA;AAAA,EAChF,CAAC;AACD,SAAO,sBAAsB;AAAA,IAC3B,KAAK,IAAI;AAAA,IACT,QAAQ,KAAK,IAAI,IAAI,KAAK,IAAI,SAAS,eAAe;AAAA,IACtD;AAAA,EAAA,CACD;AACH;AAQO,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AACF,GAGkB;AAChB,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,OAAO,OAAQ,QAAO,QAAQ;AAAA,EAC5C;AACA,aAAW,WAAW,UAAU;AAC9B,UAAM,UAAmBC,KAAAA,yBAAyB,OAAO;AACzD,QAAI,CAAC,MAAM,QAAQ,OAAO,EAAG;AAC7B,eAAW,UAAU,SAAS;AAC5B,UAAI,UAAU,OAAO,WAAW,YAAa,OAA4B,OAAO,QAAQ;AACtF,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,mCAAmC;AAOzC,MAAM,yBAAyB;AAE/B,MAAM,8BAA8B;AAMpC,MAAM,2BAA2B;AAYjC,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAO4B;AAC1B,MAAI,MAAO,QAAO;AAClB,MAAI,aAAa,uBAAwB,QAAO;AAChD,MAAI,WAAW,aAAa,KAAM,QAAO;AACzC,MAAI,YAAY,cAAc,iCAAkC,QAAO;AACvE,SAAO;AACT;;;;;;;;;;;;"}
@@ -0,0 +1,120 @@
1
+ /**
2
+ * Where a handed-off conversation opens: at the message the source was
3
+ * reading ("the view at the last message the source showed"), not just at the
4
+ * bottom.
5
+ *
6
+ * The source reads its own transcript when the person issues the handover
7
+ * (`readingCursorOf` on the web, the visible rows on the phone): `null` when
8
+ * it was at the bottom, else the id of the last message fully in view or,
9
+ * when one long message fills the view, the message at the reading line. The
10
+ * id travels as the handover's `cursor` (an opaque id from the person's own
11
+ * thread, never content). The destination records it with the arrival
12
+ * (`recordHandoffArrival({ cursor })`) and its transcript reveals that message
13
+ * once it has it, paging older history in when needed
14
+ * (`handoffCursorRevealStep`), and highlights it briefly. A cursor it can't
15
+ * find leaves the view at the bottom, as before: it is a hint, never a wait.
16
+ */
17
+ import { type ThreadMessage } from '@assistant-ui/core';
18
+ /** Whether older history is left to page in (`null` until the window is known). */
19
+ export { deepAgentHistoryHasOlder } from './edit-command';
20
+ /** How far from the bottom still counts as "at the bottom": no cursor is sent. */
21
+ export declare const READING_CURSOR_BOTTOM_TOLERANCE_PX = 48;
22
+ /** The reading line, as a fraction of the visible height from its top. */
23
+ export declare const READING_LINE_FRACTION: number;
24
+ /** One rendered message row, in the viewport's coordinates. */
25
+ export interface ReadingRow {
26
+ id: string;
27
+ top: number;
28
+ bottom: number;
29
+ }
30
+ /**
31
+ * The reading position among `rows` (in document order) visible between `top`
32
+ * and `bottom`: the last row fully inside, else the row across the reading
33
+ * line (a message taller than the view). `null` when neither is a message id.
34
+ */
35
+ export declare function readingCursorFromRows({ top, bottom, rows, }: {
36
+ top: number;
37
+ bottom: number;
38
+ rows: readonly ReadingRow[];
39
+ }): string | null;
40
+ /**
41
+ * The reading position of a list that reports which rows are visible, in
42
+ * order and partly visible ones included, but not where they sit (a native
43
+ * list's viewability). Only the first and last visible rows can be cut off,
44
+ * so with three or more the last fully visible one is the one before the
45
+ * last; with fewer, the first (the one across the reading line, as far as the
46
+ * list can tell). Callers check "at the bottom" themselves.
47
+ */
48
+ export declare function readingCursorFromVisibleIds(ids: readonly string[]): string | null;
49
+ interface BoxLike {
50
+ getBoundingClientRect(): {
51
+ top: number;
52
+ bottom: number;
53
+ };
54
+ }
55
+ interface RowLike extends BoxLike {
56
+ getAttribute(name: string): string | null;
57
+ }
58
+ /** The scroll container of a web transcript (structural, so Core needs no DOM types). */
59
+ export interface ReadingViewportLike extends BoxLike {
60
+ scrollTop: number;
61
+ scrollHeight: number;
62
+ clientHeight: number;
63
+ querySelectorAll(selectors: string): ArrayLike<RowLike>;
64
+ }
65
+ /** assistant-ui stamps every message row with its id. */
66
+ export declare const MESSAGE_ROW_ID_ATTRIBUTE = "data-message-id";
67
+ /**
68
+ * Where a web transcript is being read: `null` at the bottom, else the message
69
+ * id `readingCursorFromRows` picks. `coveredBottomPx` is what a sticky footer
70
+ * (the composer) hides at the bottom of the viewport.
71
+ */
72
+ export declare function readingCursorOf({ viewport, coveredBottomPx, idAttribute, }: {
73
+ viewport: ReadingViewportLike;
74
+ coveredBottomPx?: number;
75
+ idAttribute?: string;
76
+ }): string | null;
77
+ /**
78
+ * The id of the displayed message that holds `cursor`. A displayed message is
79
+ * one agent turn whose id is its first wire message's, and that id changes
80
+ * when an older page folds into it, so the cursor is matched against every
81
+ * wire message behind each row as well.
82
+ */
83
+ export declare function handoffCursorMessageId({ messages, cursor, }: {
84
+ messages: readonly ThreadMessage[];
85
+ cursor: string;
86
+ }): string | null;
87
+ /** Pages of older history one landing may load looking for its message. */
88
+ export declare const HANDOFF_CURSOR_MAX_HISTORY_PAGES = 20;
89
+ /**
90
+ * How long a landing waits without progress before staying at the bottom: a
91
+ * transcript still attaching, or a page of older history that never lands.
92
+ * Each page that lands starts the wait again, so a long conversation on a slow
93
+ * connection still reaches its message within the page budget.
94
+ */
95
+ export declare const HANDOFF_CURSOR_WAIT_MS = 30000;
96
+ /** How long the revealed message stays highlighted. */
97
+ export declare const HANDOFF_CURSOR_HIGHLIGHT_MS = 2400;
98
+ /**
99
+ * How long the revealed message is held in view while the transcript settles
100
+ * (an attaching thread can still scroll itself to the bottom). Any scroll the
101
+ * person makes ends it at once.
102
+ */
103
+ export declare const HANDOFF_CURSOR_SETTLE_MS = 1500;
104
+ export type HandoffCursorRevealStep = 'reveal' | 'wait' | 'page' | 'bottom';
105
+ /**
106
+ * What a destination does next while opening at a cursor: `reveal` the message
107
+ * it found; `wait` while the transcript (or a page it asked for) is loading or
108
+ * whether older history exists is not known yet (`hasOlder: null`: a cached
109
+ * transcript shows before its history window resolves); `page` older history
110
+ * in while some is left and the budget allows; else stay at the `bottom`, the
111
+ * fallback for a cursor that can't be found.
112
+ */
113
+ export declare function handoffCursorRevealStep({ found, loading, hasOlder, pagesLoaded, elapsedMs, }: {
114
+ found: boolean;
115
+ loading: boolean;
116
+ hasOlder: boolean | null;
117
+ pagesLoaded: number;
118
+ /** Since the landing last made progress: it started, or a page of older history landed. */
119
+ elapsedMs: number;
120
+ }): HandoffCursorRevealStep;
@@ -0,0 +1,88 @@
1
+ import { getExternalStoreMessages } from "@assistant-ui/core";
2
+ import { parseHandoffCursor } from "./handoff-cursor-id.js";
3
+ const READING_CURSOR_BOTTOM_TOLERANCE_PX = 48;
4
+ const READING_LINE_FRACTION = 1 / 3;
5
+ function readingCursorFromRows({
6
+ top,
7
+ bottom,
8
+ rows
9
+ }) {
10
+ var _a;
11
+ const line = top + (bottom - top) * READING_LINE_FRACTION;
12
+ let lastFull = null;
13
+ let atLine = null;
14
+ for (const row of rows) {
15
+ if (row.bottom <= row.top) continue;
16
+ if (row.top >= top - 1 && row.bottom <= bottom + 1) lastFull = row;
17
+ if (row.top <= line && row.bottom > line) atLine = row;
18
+ }
19
+ return parseHandoffCursor((_a = lastFull ?? atLine) == null ? void 0 : _a.id);
20
+ }
21
+ const MESSAGE_ROW_ID_ATTRIBUTE = "data-message-id";
22
+ function readingCursorOf({
23
+ viewport,
24
+ coveredBottomPx = 0,
25
+ idAttribute = MESSAGE_ROW_ID_ATTRIBUTE
26
+ }) {
27
+ const fromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;
28
+ if (fromBottom <= READING_CURSOR_BOTTOM_TOLERANCE_PX) return null;
29
+ const box = viewport.getBoundingClientRect();
30
+ const rows = Array.from(viewport.querySelectorAll(`[${idAttribute}]`), (row) => {
31
+ const rect = row.getBoundingClientRect();
32
+ return { id: row.getAttribute(idAttribute) ?? "", top: rect.top, bottom: rect.bottom };
33
+ });
34
+ return readingCursorFromRows({
35
+ top: box.top,
36
+ bottom: Math.max(box.top, box.bottom - coveredBottomPx),
37
+ rows
38
+ });
39
+ }
40
+ function handoffCursorMessageId({
41
+ messages,
42
+ cursor
43
+ }) {
44
+ for (const message of messages) {
45
+ if (message.id === cursor) return message.id;
46
+ }
47
+ for (const message of messages) {
48
+ const sources = getExternalStoreMessages(message);
49
+ if (!Array.isArray(sources)) continue;
50
+ for (const source of sources) {
51
+ if (source && typeof source === "object" && source.id === cursor) {
52
+ return message.id;
53
+ }
54
+ }
55
+ }
56
+ return null;
57
+ }
58
+ const HANDOFF_CURSOR_MAX_HISTORY_PAGES = 20;
59
+ const HANDOFF_CURSOR_WAIT_MS = 3e4;
60
+ const HANDOFF_CURSOR_HIGHLIGHT_MS = 2400;
61
+ const HANDOFF_CURSOR_SETTLE_MS = 1500;
62
+ function handoffCursorRevealStep({
63
+ found,
64
+ loading,
65
+ hasOlder,
66
+ pagesLoaded,
67
+ elapsedMs
68
+ }) {
69
+ if (found) return "reveal";
70
+ if (elapsedMs >= HANDOFF_CURSOR_WAIT_MS) return "bottom";
71
+ if (loading || hasOlder === null) return "wait";
72
+ if (hasOlder && pagesLoaded < HANDOFF_CURSOR_MAX_HISTORY_PAGES) return "page";
73
+ return "bottom";
74
+ }
75
+ export {
76
+ HANDOFF_CURSOR_HIGHLIGHT_MS,
77
+ HANDOFF_CURSOR_MAX_HISTORY_PAGES,
78
+ HANDOFF_CURSOR_SETTLE_MS,
79
+ HANDOFF_CURSOR_WAIT_MS,
80
+ MESSAGE_ROW_ID_ATTRIBUTE,
81
+ READING_CURSOR_BOTTOM_TOLERANCE_PX,
82
+ READING_LINE_FRACTION,
83
+ handoffCursorMessageId,
84
+ handoffCursorRevealStep,
85
+ readingCursorFromRows,
86
+ readingCursorOf
87
+ };
88
+ //# sourceMappingURL=handoff-cursor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"handoff-cursor.js","sources":["../../../../agent-runtime/src/handoff-cursor.ts"],"sourcesContent":["/**\n * Where a handed-off conversation opens: at the message the source was\n * reading (\"the view at the last message the source showed\"), not just at the\n * bottom.\n *\n * The source reads its own transcript when the person issues the handover\n * (`readingCursorOf` on the web, the visible rows on the phone): `null` when\n * it was at the bottom, else the id of the last message fully in view or,\n * when one long message fills the view, the message at the reading line. The\n * id travels as the handover's `cursor` (an opaque id from the person's own\n * thread, never content). The destination records it with the arrival\n * (`recordHandoffArrival({ cursor })`) and its transcript reveals that message\n * once it has it, paging older history in when needed\n * (`handoffCursorRevealStep`), and highlights it briefly. A cursor it can't\n * find leaves the view at the bottom, as before: it is a hint, never a wait.\n */\nimport { getExternalStoreMessages, type ThreadMessage } from '@assistant-ui/core';\nimport { parseHandoffCursor } from './handoff-cursor-id';\n\n/** Whether older history is left to page in (`null` until the window is known). */\nexport { deepAgentHistoryHasOlder } from './edit-command';\n\n/** How far from the bottom still counts as \"at the bottom\": no cursor is sent. */\nexport const READING_CURSOR_BOTTOM_TOLERANCE_PX = 48;\n/** The reading line, as a fraction of the visible height from its top. */\nexport const READING_LINE_FRACTION = 1 / 3;\n\n/** One rendered message row, in the viewport's coordinates. */\nexport interface ReadingRow {\n id: string;\n top: number;\n bottom: number;\n}\n\n/**\n * The reading position among `rows` (in document order) visible between `top`\n * and `bottom`: the last row fully inside, else the row across the reading\n * line (a message taller than the view). `null` when neither is a message id.\n */\nexport function readingCursorFromRows({\n top,\n bottom,\n rows,\n}: {\n top: number;\n bottom: number;\n rows: readonly ReadingRow[];\n}): string | null {\n const line = top + (bottom - top) * READING_LINE_FRACTION;\n let lastFull: ReadingRow | null = null;\n let atLine: ReadingRow | null = null;\n for (const row of rows) {\n if (row.bottom <= row.top) continue;\n if (row.top >= top - 1 && row.bottom <= bottom + 1) lastFull = row;\n if (row.top <= line && row.bottom > line) atLine = row;\n }\n return parseHandoffCursor((lastFull ?? atLine)?.id);\n}\n\n/**\n * The reading position of a list that reports which rows are visible, in\n * order and partly visible ones included, but not where they sit (a native\n * list's viewability). Only the first and last visible rows can be cut off,\n * so with three or more the last fully visible one is the one before the\n * last; with fewer, the first (the one across the reading line, as far as the\n * list can tell). Callers check \"at the bottom\" themselves.\n */\nexport function readingCursorFromVisibleIds(ids: readonly string[]): string | null {\n return parseHandoffCursor(ids.length >= 3 ? ids[ids.length - 2] : ids[0]);\n}\n\ninterface BoxLike {\n getBoundingClientRect(): { top: number; bottom: number };\n}\n\ninterface RowLike extends BoxLike {\n getAttribute(name: string): string | null;\n}\n\n/** The scroll container of a web transcript (structural, so Core needs no DOM types). */\nexport interface ReadingViewportLike extends BoxLike {\n scrollTop: number;\n scrollHeight: number;\n clientHeight: number;\n querySelectorAll(selectors: string): ArrayLike<RowLike>;\n}\n\n/** assistant-ui stamps every message row with its id. */\nexport const MESSAGE_ROW_ID_ATTRIBUTE = 'data-message-id';\n\n/**\n * Where a web transcript is being read: `null` at the bottom, else the message\n * id `readingCursorFromRows` picks. `coveredBottomPx` is what a sticky footer\n * (the composer) hides at the bottom of the viewport.\n */\nexport function readingCursorOf({\n viewport,\n coveredBottomPx = 0,\n idAttribute = MESSAGE_ROW_ID_ATTRIBUTE,\n}: {\n viewport: ReadingViewportLike;\n coveredBottomPx?: number;\n idAttribute?: string;\n}): string | null {\n const fromBottom = viewport.scrollHeight - viewport.scrollTop - viewport.clientHeight;\n if (fromBottom <= READING_CURSOR_BOTTOM_TOLERANCE_PX) return null;\n const box = viewport.getBoundingClientRect();\n const rows: ReadingRow[] = Array.from(viewport.querySelectorAll(`[${idAttribute}]`), (row) => {\n const rect = row.getBoundingClientRect();\n return { id: row.getAttribute(idAttribute) ?? '', top: rect.top, bottom: rect.bottom };\n });\n return readingCursorFromRows({\n top: box.top,\n bottom: Math.max(box.top, box.bottom - coveredBottomPx),\n rows,\n });\n}\n\n/**\n * The id of the displayed message that holds `cursor`. A displayed message is\n * one agent turn whose id is its first wire message's, and that id changes\n * when an older page folds into it, so the cursor is matched against every\n * wire message behind each row as well.\n */\nexport function handoffCursorMessageId({\n messages,\n cursor,\n}: {\n messages: readonly ThreadMessage[];\n cursor: string;\n}): string | null {\n for (const message of messages) {\n if (message.id === cursor) return message.id;\n }\n for (const message of messages) {\n const sources: unknown = getExternalStoreMessages(message);\n if (!Array.isArray(sources)) continue;\n for (const source of sources) {\n if (source && typeof source === 'object' && (source as { id?: unknown }).id === cursor) {\n return message.id;\n }\n }\n }\n return null;\n}\n\n/** Pages of older history one landing may load looking for its message. */\nexport const HANDOFF_CURSOR_MAX_HISTORY_PAGES = 20;\n/**\n * How long a landing waits without progress before staying at the bottom: a\n * transcript still attaching, or a page of older history that never lands.\n * Each page that lands starts the wait again, so a long conversation on a slow\n * connection still reaches its message within the page budget.\n */\nexport const HANDOFF_CURSOR_WAIT_MS = 30_000;\n/** How long the revealed message stays highlighted. */\nexport const HANDOFF_CURSOR_HIGHLIGHT_MS = 2_400;\n/**\n * How long the revealed message is held in view while the transcript settles\n * (an attaching thread can still scroll itself to the bottom). Any scroll the\n * person makes ends it at once.\n */\nexport const HANDOFF_CURSOR_SETTLE_MS = 1_500;\n\nexport type HandoffCursorRevealStep = 'reveal' | 'wait' | 'page' | 'bottom';\n\n/**\n * What a destination does next while opening at a cursor: `reveal` the message\n * it found; `wait` while the transcript (or a page it asked for) is loading or\n * whether older history exists is not known yet (`hasOlder: null`: a cached\n * transcript shows before its history window resolves); `page` older history\n * in while some is left and the budget allows; else stay at the `bottom`, the\n * fallback for a cursor that can't be found.\n */\nexport function handoffCursorRevealStep({\n found,\n loading,\n hasOlder,\n pagesLoaded,\n elapsedMs,\n}: {\n found: boolean;\n loading: boolean;\n hasOlder: boolean | null;\n pagesLoaded: number;\n /** Since the landing last made progress: it started, or a page of older history landed. */\n elapsedMs: number;\n}): HandoffCursorRevealStep {\n if (found) return 'reveal';\n if (elapsedMs >= HANDOFF_CURSOR_WAIT_MS) return 'bottom';\n if (loading || hasOlder === null) return 'wait';\n if (hasOlder && pagesLoaded < HANDOFF_CURSOR_MAX_HISTORY_PAGES) return 'page';\n return 'bottom';\n}\n"],"names":[],"mappings":";;AAuBO,MAAM,qCAAqC;AAE3C,MAAM,wBAAwB,IAAI;AAclC,SAAS,sBAAsB;AAAA,EACpC;AAAA,EACA;AAAA,EACA;AACF,GAIkB;;AAChB,QAAM,OAAO,OAAO,SAAS,OAAO;AACpC,MAAI,WAA8B;AAClC,MAAI,SAA4B;AAChC,aAAW,OAAO,MAAM;AACtB,QAAI,IAAI,UAAU,IAAI,IAAK;AAC3B,QAAI,IAAI,OAAO,MAAM,KAAK,IAAI,UAAU,SAAS,EAAG,YAAW;AAC/D,QAAI,IAAI,OAAO,QAAQ,IAAI,SAAS,KAAM,UAAS;AAAA,EACrD;AACA,SAAO,oBAAoB,iBAAY,WAAZ,mBAAqB,EAAE;AACpD;AA+BO,MAAM,2BAA2B;AAOjC,SAAS,gBAAgB;AAAA,EAC9B;AAAA,EACA,kBAAkB;AAAA,EAClB,cAAc;AAChB,GAIkB;AAChB,QAAM,aAAa,SAAS,eAAe,SAAS,YAAY,SAAS;AACzE,MAAI,cAAc,mCAAoC,QAAO;AAC7D,QAAM,MAAM,SAAS,sBAAA;AACrB,QAAM,OAAqB,MAAM,KAAK,SAAS,iBAAiB,IAAI,WAAW,GAAG,GAAG,CAAC,QAAQ;AAC5F,UAAM,OAAO,IAAI,sBAAA;AACjB,WAAO,EAAE,IAAI,IAAI,aAAa,WAAW,KAAK,IAAI,KAAK,KAAK,KAAK,QAAQ,KAAK,OAAA;AAAA,EAChF,CAAC;AACD,SAAO,sBAAsB;AAAA,IAC3B,KAAK,IAAI;AAAA,IACT,QAAQ,KAAK,IAAI,IAAI,KAAK,IAAI,SAAS,eAAe;AAAA,IACtD;AAAA,EAAA,CACD;AACH;AAQO,SAAS,uBAAuB;AAAA,EACrC;AAAA,EACA;AACF,GAGkB;AAChB,aAAW,WAAW,UAAU;AAC9B,QAAI,QAAQ,OAAO,OAAQ,QAAO,QAAQ;AAAA,EAC5C;AACA,aAAW,WAAW,UAAU;AAC9B,UAAM,UAAmB,yBAAyB,OAAO;AACzD,QAAI,CAAC,MAAM,QAAQ,OAAO,EAAG;AAC7B,eAAW,UAAU,SAAS;AAC5B,UAAI,UAAU,OAAO,WAAW,YAAa,OAA4B,OAAO,QAAQ;AACtF,eAAO,QAAQ;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AACA,SAAO;AACT;AAGO,MAAM,mCAAmC;AAOzC,MAAM,yBAAyB;AAE/B,MAAM,8BAA8B;AAMpC,MAAM,2BAA2B;AAYjC,SAAS,wBAAwB;AAAA,EACtC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAO4B;AAC1B,MAAI,MAAO,QAAO;AAClB,MAAI,aAAa,uBAAwB,QAAO;AAChD,MAAI,WAAW,aAAa,KAAM,QAAO;AACzC,MAAI,YAAY,cAAc,iCAAkC,QAAO;AACvE,SAAO;AACT;"}
@@ -21,7 +21,7 @@ export { ATHENA_TRUNCATED_TOOL_OUTPUT_ARTIFACT_KEY, buildHistoryMessageCommand,
21
21
  export { buildHistoryLaneCommand, type DeepAgentLaneHistoryState, type DeepAgentLaneTarget, deepAgentLaneHistoryQueryKey, type HistoryLaneCommand, type HistoryLanePage, isWholeCardRefusal, mergeLaneHistory, readHistoryLanePage, useDeepAgentLaneHistory, } from "./lane-history";
22
22
  export { buildHistoryLaneImageCommand, type DeepAgentLaneImagesState, deepAgentLaneImageQueryKey, type HistoryLaneImageChunk, type HistoryLaneImageCommand, type InlineImage, type InlineImageChunk, type LaneImageRef, laneImageKey, omittedLaneImages, readChunkedInlineImage, readHistoryLaneImageChunk, readInlineImageChunk, readLaneImage, readLaneImageTile, showLaneImages, useDeepAgentLaneImages, } from "./lane-images";
23
23
  export { createDeepAgentLaneFollowRegistry, type DeepAgentLaneFollowRegistry, type DeepAgentLaneFollowRequest, type DeepAgentLaneFollows, isLaneFollowRequest, laneFollowRequest, mergeFollowedLane, readDeepAgentFollowedWindow, readDeepAgentLaneFollows, useDeepAgentLaneFollow, useDeepAgentLaneFollowSync, type UseDeepAgentLaneFollowSyncOptions, } from "./lane-follow";
24
- export { buildDeepAgentEditCommand, type DeepAgentPreparedMessageMetadata, type DeepAgentStatewireExtras, isSourceBelowLoadedWindow, loadOlderDeepAgentHistoryPage, MAX_EDIT_ANCHOR_HISTORY_LOADS, readDeepAgentStatewireExtras, resolveDeepAgentEditCommand, resolveWireAnchorMessageId, } from "./edit-command";
24
+ export { buildDeepAgentEditCommand, deepAgentHistoryHasOlder, type DeepAgentPreparedMessageMetadata, type DeepAgentStatewireExtras, isSourceBelowLoadedWindow, loadOlderDeepAgentHistoryPage, MAX_EDIT_ANCHOR_HISTORY_LOADS, readDeepAgentStatewireExtras, resolveDeepAgentEditCommand, resolveWireAnchorMessageId, } from "./edit-command";
25
25
  export { DEEP_AGENT_MAX_CHAINED_HISTORY_LOADS, type DeepAgentHistoryLoadEvent, type DeepAgentWindowSyncMessage, type DeepAgentWindowSyncState, useDeepAgentMessageWindow, useDeepAgentWindowSync, type UseDeepAgentWindowSyncOptions, } from "./use-deep-agent-window-sync";
26
26
  export { useDeepAgentMessageEditSubmit } from "./use-deep-agent-edit-submit";
27
27
  export { createDeepAgentToolOutputStore, type DeepAgentToolOutputEntry, type DeepAgentToolOutputStore, type DeepAgentToolOutputStoreOptions, } from "./tool-outputs";
@@ -75,8 +75,9 @@ export { type DeepAgentAttachSource, type DeepAgentHeadersResolver, readSessionP
75
75
  export { GITHUB_EVENT_SOURCE_KEY, type GithubWakeEvent, githubWakeEventLabel, githubWakeEventUrl, githubWakeMarkerText, githubWakeReference, isGithubWakeMessage, readGithubWakeEvent, } from "./github-wake-event";
76
76
  export { githubSubscriptionHeadline, isSubscriptionWakeMessage, readSubscriptionWakeEvent, SUBSCRIPTION_EVENT_KEY, type SubscriptionWakeChange, type SubscriptionWakeEvent, type SubscriptionWakeGithub, subscriptionPlaceWords, subscriptionWakeDetail, subscriptionWakeGithub, subscriptionWakeHeadline, } from "./subscription-wake-event";
77
77
  export { describeTerminationReason, readRuntimeMessageFromSourceMessages, readSoleRuntimeMessageKind, readTerminationReason, runtimeMessageMarkerText, TERMINATION_REASONS, type AthenaRuntimeMessage, type AthenaRuntimeMessageKind, type AthenaRuntimeMessageSeverity, type AthenaTerminationReason, type TerminationDescription, } from "./runtime-messages";
78
- export { buildHandoffQrSvg, buildMobileHandoffSchemeUrl, classifyHandoffTransportError, type CreateThreadHandoffInput, type CreateThreadHandoffResponse, createThreadHandoffResponseSchema, describeHandoffTransportError, type DestinationReadiness, HANDOFF_DESTINATION_LABELS, HANDOFF_DESTINATIONS, 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, handoffHttpError, handoffReadinessResponseSchema, handoffStateSchema, isHandoffGoneError, isHandoffPermanentError, isRetryableHandoffHttpStatus, isOfficeHandoffDestination, isServerHandoffDestination, isTerminalHandoffState, type MobileHandoffLaunch, type MobileLaunchMode, mobileHandoffLaunchSchema, OFFICE_HANDOFF_DESTINATIONS, type OfficeHandoffDestination, type OfficeHandoffLaunch, officeHandoffLaunchSchema, type OfficeInvitationLaunch, officeInvitationLaunchSchema, type OfficeLaunchMode, officeLaunchModeSchema, threadHandoffCreateBody, parseHandoffIdParam, presentHandoffDestination, readinessByDestination, SERVER_HANDOFF_DESTINATIONS, type ServerHandoffDestination, selectMobileLaunchMode, supportsDesktopHandoffSource, type ThreadHandoffTransport, type ThreadHandoffView, threadHandoffViewSchema, waitForHandoffOutcome, type WaitForHandoffOutcomeOptions, 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, 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, livenessAgo, presentSurfaceLiveness, type SurfaceLiveness, type SurfaceLivenessBasis, surfaceLivenessBasisSchema, surfaceLivenessBySurface, type SurfaceLivenessPresentation, surfaceLivenessSchema, type SurfaceLivenessState, surfaceLivenessStateSchema, type SurfaceLivenessTone, supportsSurfaceLiveness, } from "./thread-handoff";
78
+ export { buildHandoffQrSvg, buildMobileHandoffSchemeUrl, classifyHandoffTransportError, type CreateThreadHandoffInput, type CreateThreadHandoffResponse, createThreadHandoffResponseSchema, describeHandoffTransportError, type DestinationReadiness, HANDOFF_DESTINATION_LABELS, HANDOFF_DESTINATIONS, 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, handoffHttpError, handoffReadinessResponseSchema, handoffStateSchema, isHandoffGoneError, isHandoffPermanentError, isRetryableHandoffHttpStatus, isOfficeHandoffDestination, isServerHandoffDestination, isTerminalHandoffState, type MobileHandoffLaunch, type MobileLaunchMode, mobileHandoffLaunchSchema, OFFICE_HANDOFF_DESTINATIONS, type OfficeHandoffDestination, type OfficeHandoffLaunch, officeHandoffLaunchSchema, type OfficeInvitationLaunch, officeInvitationLaunchSchema, type OfficeLaunchMode, officeLaunchModeSchema, threadHandoffCreateBody, parseHandoffIdParam, presentHandoffDestination, readinessByDestination, SERVER_HANDOFF_DESTINATIONS, type ServerHandoffDestination, selectMobileLaunchMode, supportsDesktopHandoffSource, type ThreadHandoffTransport, type ThreadHandoffView, threadHandoffViewSchema, waitForHandoffOutcome, type WaitForHandoffOutcomeOptions, 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";
79
79
  export { DEFAULT_MOBILE_HANDOFF_TIMEOUT_MS, DEFAULT_OFFICE_HANDOFF_TIMEOUT_MS, DEFAULT_PICKUP_HANDOFF_TIMEOUT_MS, type StartThreadHandoffInput, type ThreadHandoffFlowCallbacks, type ThreadHandoffFlowController, type ThreadHandoffFlowPhase, useThreadHandoffFlow, type UseThreadHandoffFlowOptions, } from "./use-thread-handoff-flow";
80
- export { anchorHandoffArrival, clearHandoffArrival, type HandoffArrival, HandoffArrivalContext, MAX_HANDOFF_ARRIVALS, readHandoffArrival, recordHandoffArrival, resetHandoffArrivalsForTests, subscribeHandoffArrivals, useHandoffArrival, useHandoffArrivalAfter, } from "./handoff-arrival";
80
+ export { anchorHandoffArrival, clearHandoffArrival, type HandoffArrival, HandoffArrivalContext, MAX_HANDOFF_ARRIVALS, readHandoffArrival, recordHandoffArrival, resetHandoffArrivalsForTests, settleHandoffArrivalCursor, subscribeHandoffArrivals, useHandoffArrival, useHandoffArrivalAfter, useHandoffArrivalCursor, } from "./handoff-arrival";
81
+ export { HANDOFF_CURSOR_HIGHLIGHT_MS, HANDOFF_CURSOR_MAX_HISTORY_PAGES, HANDOFF_CURSOR_SETTLE_MS, HANDOFF_CURSOR_WAIT_MS, handoffCursorMessageId, handoffCursorRevealStep, type HandoffCursorRevealStep, MESSAGE_ROW_ID_ATTRIBUTE, READING_CURSOR_BOTTOM_TOLERANCE_PX, READING_LINE_FRACTION, type ReadingRow, type ReadingViewportLike, readingCursorFromRows, readingCursorFromVisibleIds, readingCursorOf, } from "./handoff-cursor";
81
82
  export { connectVoiceMedia, VOICE_MICROPHONE_CAPTURE } from './voice-media';
82
83
  export type { VoiceMediaDriver, VoiceMediaEvent, VoiceMediaEventType, VoiceMediaHandlers, VoiceMediaSession, } from './voice-media';
@@ -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";
@@ -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";
@@ -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
@@ -170,6 +171,7 @@ export declare const handoffReadinessResponseSchema: z.ZodObject<{
170
171
  supports_office_invitations: z.ZodOptional<z.ZodBoolean>;
171
172
  supports_desktop_source: z.ZodOptional<z.ZodBoolean>;
172
173
  supports_surface_destinations: z.ZodOptional<z.ZodBoolean>;
174
+ supports_handoff_cursor: z.ZodOptional<z.ZodBoolean>;
173
175
  supports_surface_liveness: z.ZodOptional<z.ZodBoolean>;
174
176
  surfaces: z.ZodCatch<z.ZodOptional<z.ZodPipe<z.ZodArray<z.ZodUnknown>, z.ZodTransform<{
175
177
  surface: "chrome" | "desktop" | "mobile" | "spaces";
@@ -183,6 +185,8 @@ export type HandoffReadinessResponse = z.infer<typeof handoffReadinessResponseSc
183
185
  export declare const supportsDesktopHandoffSource: (readiness: HandoffReadinessResponse | null | undefined) => boolean;
184
186
  /** Whether the server can broker the four-surface destinations; absent means an older agora. */
185
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;
186
190
  /** Whether the server reports where the person's apps were last seen; absent means an older agora. */
187
191
  export declare const supportsSurfaceLiveness: (readiness: HandoffReadinessResponse | null | undefined) => boolean;
188
192
  /** Index liveness rows by surface; `null` where the server sent none (or can't report liveness). */
@@ -256,8 +260,17 @@ export declare const threadHandoffViewSchema: z.ZodObject<{
256
260
  voice: "voice";
257
261
  }>>;
258
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>;
259
266
  }, z.core.$strip>;
260
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;
261
274
  export declare const mobileHandoffLaunchSchema: z.ZodObject<{
262
275
  kind: z.ZodLiteral<"mobile">;
263
276
  push_attempted: z.ZodBoolean;
@@ -400,6 +413,9 @@ export declare const createThreadHandoffResponseSchema: z.ZodObject<{
400
413
  voice: "voice";
401
414
  }>>;
402
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>;
403
419
  }, z.core.$strip>;
404
420
  launch: z.ZodDiscriminatedUnion<[z.ZodObject<{
405
421
  kind: z.ZodLiteral<"mobile">;
@@ -457,6 +473,13 @@ export interface CreateThreadHandoffInput {
457
473
  draft?: string;
458
474
  /** `voice` offers "Continue by voice" on the destination. Omitted means text. */
459
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;
460
483
  }
461
484
  /**
462
485
  * The draft a create carries, the one rule every source follows. An Office
@@ -475,7 +498,7 @@ export declare function handoffDraftToCarry({ destination, draft, }: {
475
498
  /** A phone invites the computer's pane; other sources keep their existing launch. */
476
499
  export declare function resolveOfficeHandoffLaunchMode({ source, destination, officeLaunchMode, }: CreateThreadHandoffInput): OfficeLaunchMode | undefined;
477
500
  /** The same request body for every host's authenticated HTTP adapter. */
478
- 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): {
479
502
  thread_id: string;
480
503
  destination: "chrome" | "desktop" | "excel" | "mobile" | "next" | "powerpoint" | "spaces" | "word";
481
504
  source: HandoffSource;
@@ -483,6 +506,7 @@ export declare function threadHandoffCreateBody({ threadId, destination, source,
483
506
  office_launch_mode?: "invitation" | "new_document" | undefined;
484
507
  draft?: string | undefined;
485
508
  mode?: "voice" | undefined;
509
+ cursor?: string | undefined;
486
510
  };
487
511
  /** Keep a draft within the server's limit at a character boundary; never send more. */
488
512
  export declare const clampHandoffDraft: (draft: string) => string;
@@ -528,10 +552,15 @@ export declare function pickupExcludeIds({ issuedHere, declined, }: {
528
552
  issuedHere: Iterable<string>;
529
553
  declined: Iterable<string>;
530
554
  }): string[];
531
- /** `/pending?destination=…&exclude=…` relative to `/api/handoffs`. */
532
- 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, }: {
533
561
  destination: Exclude<ThreadHandoffDestination, 'desktop'> | 'desktop';
534
562
  exclude?: Iterable<string>;
563
+ includeImplicit?: boolean;
535
564
  }): string;
536
565
  export declare const claimThreadHandoffResponseSchema: z.ZodObject<{
537
566
  handoff: z.ZodObject<{
@@ -582,6 +611,9 @@ export declare const claimThreadHandoffResponseSchema: z.ZodObject<{
582
611
  voice: "voice";
583
612
  }>>;
584
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>;
585
617
  }, z.core.$strip>;
586
618
  }, z.core.$strip>;
587
619
  export type ClaimThreadHandoffResponse = z.infer<typeof claimThreadHandoffResponseSchema>;
@@ -635,6 +667,9 @@ export declare const pendingThreadHandoffResponseSchema: z.ZodNullable<z.ZodObje
635
667
  voice: "voice";
636
668
  }>>;
637
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>;
638
673
  }, z.core.$strip>;
639
674
  }, z.core.$strip>>;
640
675
  /**
@@ -650,6 +685,8 @@ export interface ThreadHandoffReceiverTransport {
650
685
  destination: Exclude<ThreadHandoffDestination, 'desktop'>;
651
686
  /** Tickets this receiver declined or issued itself; the newest other one is returned. */
652
687
  exclude?: readonly string[];
688
+ /** Also an implicit ticket, on a `next` peek ({@link pendingHandoffPath}). */
689
+ includeImplicit?: boolean;
653
690
  signal?: AbortSignal;
654
691
  }) => Promise<ClaimThreadHandoffResponse | null>;
655
692
  claim: (options: {
@@ -667,16 +704,84 @@ export interface ThreadHandoffReceiverTransport {
667
704
  signal?: AbortSignal;
668
705
  }) => Promise<ThreadHandoffView>;
669
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>;
670
770
  /**
671
771
  * Whether a pending handoff should be offered on this surface: still open,
672
772
  * not one this device issued itself, and not one it already declined.
673
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.
674
778
  */
675
- export declare function shouldOfferPendingHandoff({ pending, issuedHere, declined, now, }: {
779
+ export declare function shouldOfferPendingHandoff({ pending, issuedHere, declined, now, implicit, }: {
676
780
  pending: ThreadHandoffView | null | undefined;
677
781
  issuedHere: ReadonlySet<string>;
678
782
  declined: ReadonlySet<string>;
679
783
  now?: number;
784
+ implicit?: ImplicitPickupContext;
680
785
  }): boolean;
681
786
  /**
682
787
  * Surface-specific HTTP adapter. Olympus binds it to `apiClient`; the
@@ -741,6 +846,11 @@ export interface HandoffLanding {
741
846
  * "Continue by voice"; nothing starts the microphone on its own.
742
847
  */
743
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;
744
854
  }
745
855
  export declare const handoffLandingFromView: (view: ThreadHandoffView) => HandoffLanding;
746
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, 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, 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, livenessAgo, presentSurfaceLiveness, type SurfaceLiveness, type SurfaceLivenessBasis, surfaceLivenessBasisSchema, surfaceLivenessBySurface, type SurfaceLivenessPresentation, surfaceLivenessSchema, type SurfaceLivenessState, surfaceLivenessStateSchema, type SurfaceLivenessTone, supportsSurfaceLiveness, } 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';
@@ -33,6 +33,9 @@ function pageShowedEarlierMessage(from, messages) {
33
33
  }
34
34
  return messages.length > from.count;
35
35
  }
36
+ function isReply(message) {
37
+ return (message == null ? void 0 : message.role) !== void 0 && message.role !== "user";
38
+ }
36
39
  function opensMidTurn(messages) {
37
40
  const first = messages[0];
38
41
  if (first === void 0) return false;
@@ -130,15 +133,17 @@ function useDeepAgentWindowSync({
130
133
  if (chainRef.current.loads >= maxChainedLoads) return;
131
134
  chainRef.current.loads += 1;
132
135
  }
136
+ const insideTurn = lastPageUnseen || isReply(messages[0]);
137
+ const stated = insideTurn && messageWindow.pageInterestRequests(window, runconfigContract.DEEP_AGENT_MAX_MESSAGE_WINDOW) || pageRequests;
133
138
  pageFromRef.current = { count: messages.length, secondId: (_a = messages[1]) == null ? void 0 : _a.id };
134
139
  const page = window.pages + 1;
135
- const request = pageRequests[pageRequests.length - 1];
140
+ const request = stated[stated.length - 1];
136
141
  inflightRef.current = true;
137
142
  setLoading(true);
138
143
  const startedAt = nowRef.current();
139
- invoke(runconfigContract.DEEP_AGENT_INTEREST_PROTOCOL, "set", [pageRequests]).then(() => {
144
+ invoke(runconfigContract.DEEP_AGENT_INTEREST_PROTOCOL, "set", [stated]).then(() => {
140
145
  var _a2;
141
- statedRef.current = pageRequests;
146
+ statedRef.current = stated;
142
147
  (_a2 = onHistoryLoadRef.current) == null ? void 0 : _a2.call(onHistoryLoadRef, {
143
148
  page,
144
149
  request,