bunnyquery 1.8.13 → 1.8.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/engine.d.mts CHANGED
@@ -920,6 +920,20 @@ declare function classifyInlineLink(full: string, groups: Array<string | undefin
920
920
  * the placeholder href), so marking writes one key and the lookup tries all of
921
921
  * them.
922
922
  */
923
+ /**
924
+ * Unicode form is not stable across the places a storage path travels through.
925
+ *
926
+ * macOS hands the browser a DECOMPOSED (NFD) filename, so a Korean name like
927
+ * 운전면허-김대현.jpg arrives as 24 codepoints where the composed (NFC) form is 12.
928
+ * Nothing in this engine normalized either way, so the SAME file could be keyed under
929
+ * two different strings depending on which path it travelled: a mark left by a failed
930
+ * mint under one form would never be cleared by a successful load under the other, and
931
+ * the chip stayed greyed out as "(unavailable)" forever.
932
+ *
933
+ * NFC is the canonical choice: it is what the Unicode standard recommends for
934
+ * interchange, and it is the shorter, more common form on the wire.
935
+ */
936
+ declare function canonicalizePathForm(value: string): string;
923
937
  declare function linkUnavailableKeyForPath(remotePath: string): string;
924
938
  declare function linkUnavailableKeyForHref(href: string): string;
925
939
  /**
@@ -1040,6 +1054,26 @@ interface ImagePreviewContext {
1040
1054
  onLoad?: (remotePath: string) => void;
1041
1055
  /** A preview gave up. The caption chip is now the whole answer. */
1042
1056
  onError?: (remotePath: string, err: unknown) => void;
1057
+ /**
1058
+ * This element's box just changed size, and nothing asked it to.
1059
+ *
1060
+ * Fires at the points where a preview resizes with NO DOM event of its own to
1061
+ * announce it: the src lands (a src-less <img> is hidden, so this is where it
1062
+ * starts taking space), and the src is dropped for a retry, or a mint fails
1063
+ * outright, where an already-painted picture collapses back to nothing.
1064
+ * `load` and `error` are deliberately NOT routed through here — both views
1065
+ * listen for those on the message box itself, which is also how they cover the
1066
+ * images this module never sees, a markdown `![alt](url)` among them.
1067
+ *
1068
+ * Each of these slides everything below the element, and for a reader scrolled
1069
+ * up into history that is a jump in the middle of a sentence. onLoad cannot
1070
+ * stand in for it: both views answer that with a scroll-to-bottom-if-pinned,
1071
+ * which by definition does nothing for the reader this hurts.
1072
+ *
1073
+ * Views wire this to their scroll anchor's absorb(). Called SYNCHRONOUSLY, so
1074
+ * the measurement it takes is the one the reader is looking at.
1075
+ */
1076
+ onLayoutChange?: (img: PreviewImageEl, remotePath: string) => void;
1043
1077
  }
1044
1078
  /**
1045
1079
  * Drop cached preview urls, for one project or all of them.
@@ -1091,6 +1125,9 @@ declare function markImagePreviewStale(scope: string, remotePath: string): void;
1091
1125
  * querySelectorAll to an IntersectionObserver without an engine change.
1092
1126
  */
1093
1127
  declare function hydrateImagePreviews(imgs: ArrayLike<PreviewImageEl>, ctx: ImagePreviewContext): void;
1128
+ declare var PREVIEW_LAYOUT_BOX_SELECTOR: string;
1129
+ /** The element whose height a preview's own transitions actually change. */
1130
+ declare function previewLayoutBox<T extends PreviewImageEl>(img: T): T;
1094
1131
 
1095
1132
  /**
1096
1133
  * Chat timestamp formatting, shared so agent.vue and the widget render an
@@ -1395,217 +1432,6 @@ declare function getChatHistory(params: {
1395
1432
  * `[METHOD]url#service:` + the item's own `stamp:entropy` id. */
1396
1433
  declare function buildHistoryItemFullId(platform: 'claude' | 'openai', service: string, itemId: string): string;
1397
1434
 
1398
- /**
1399
- * History mapping (pure). Moved verbatim from the chatbox. The clear-horizon
1400
- * timestamp and the "Indexing: …" display label are INJECTED (clearedAt param,
1401
- * formatIndexingLabel callback) so the engine touches neither localStorage nor
1402
- * view-specific display formatting. projectId is passed for link sanitization.
1403
- */
1404
-
1405
- declare function filterListByClearHorizon(list: any[], clearedAt: number): any[];
1406
- declare function normalizeTextContent(content: any): string;
1407
- declare function extractLastUserTextFromRequest(requestBody: any): string;
1408
- /** The two openings an indexing prompt can have. A bg-queue item that starts with
1409
- * neither is an ordinary chat that happened to be routed onto that queue. */
1410
- declare function isIndexingRequestText(userText: any): boolean;
1411
- type IndexingRequestRef = {
1412
- name: string;
1413
- path?: string;
1414
- mime?: string;
1415
- size?: number;
1416
- /** A CONTINUE pass rather than the run's first. */
1417
- continued: boolean;
1418
- };
1419
- /**
1420
- * The file an indexing prompt is about, read back out of the prompt itself.
1421
- *
1422
- * The prompt is the only description of the pass that survives on the server, so
1423
- * this is how BOTH a history rebuild and a worker-minted pass the client never
1424
- * dispatched (ChatSession._adoptWorkerIndexingPasses) recover the file. Shared so
1425
- * the two produce the same `_indexFile`, which is what makes them group together.
1426
- */
1427
- declare function parseIndexingRequestText(userText: any): IndexingRequestRef | null;
1428
- /**
1429
- * One bounded look at the background-indexing queue: which files still have a
1430
- * pass pending or running? This is the same negative signal ChatSession's
1431
- * display layer relies on - for a worker-driven (auto_continue) run, only the
1432
- * queue can say the run is over, because the worker enqueues continuation
1433
- * passes the client never dispatched.
1434
- *
1435
- * Returns every storage path AND file name found on live passes (both, because
1436
- * older prompts may lack the storage-path line), plus `checked`: false when a
1437
- * page came back full, in which case absence from `keys` proves nothing and
1438
- * the caller must keep whatever state it already had.
1439
- *
1440
- * SCOPE: the probed queue is "<userId>-bg" - THIS user's dispatches only. A
1441
- * chain launched by another collaborator or a widget end-user lives on their
1442
- * queue and is invisible here, so "idle" must never be read as "nobody is
1443
- * indexing this file", only as "this user's runs are over". The durable done::
1444
- * marker (indexDoneUniqueId) is the cross-user signal.
1445
- */
1446
- declare function fetchLiveIndexingKeys(params: {
1447
- service: string;
1448
- owner: string;
1449
- platform: 'claude' | 'openai';
1450
- /** Same value the dispatch used - see bgIndexingQueueName. */
1451
- userId?: string;
1452
- }): Promise<{
1453
- keys: Set<string>;
1454
- checked: boolean;
1455
- at: number;
1456
- }>;
1457
- /** Test hook: drop split-fetch state (all keys, or one). */
1458
- declare function __resetSplitHistoryState(key?: string): void;
1459
- type SplitHistoryResult = {
1460
- list: any[];
1461
- endOfList: boolean;
1462
- startKeyHistory: any[];
1463
- /** True when this chat had never been walked in this session — the first
1464
- * paint. Consumers gate the "Loading indexing history" hint on it: a
1465
- * mid-walk tab return restarts the walk for cursor safety but must stay
1466
- * silent (flashing the hint on every return was the reported bug). */
1467
- firstLoad?: boolean;
1468
- /** Present only when `deferBg` was requested AND bg work remains: resolves
1469
- * with the stub batch fetched in the background (the per-key lock is held
1470
- * until it settles, so no other history call can interleave). The caller
1471
- * merges the batch by timestamp — the same path older pages use. */
1472
- bgPending?: Promise<{
1473
- list: any[];
1474
- endOfList: boolean;
1475
- }>;
1476
- };
1477
- declare function getSplitChatHistory(params: {
1478
- service: string;
1479
- owner: string;
1480
- platform: 'claude' | 'openai';
1481
- userId?: string;
1482
- }, fetchOptions: Record<string, any>,
1483
- /** Test seam: replaces getChatHistory. Not for production callers. */
1484
- _fetchImpl?: typeof getChatHistory): Promise<SplitHistoryResult>;
1485
- type MapHistoryOptions = {
1486
- clearedAt: number;
1487
- projectId: string;
1488
- /** View-side display formatter for "Indexing:/Reindexing: …" bubbles. */
1489
- formatIndexingLabel: (name: string, mime?: string, size?: number | null, storagePath?: string, reindex?: boolean, continued?: boolean) => string;
1490
- };
1491
- declare function mapHistoryListToMessages(list: any[], platform: 'claude' | 'openai', opts: MapHistoryOptions): {
1492
- messages: any[];
1493
- runningItemIds: string[];
1494
- };
1495
-
1496
- /**
1497
- * Keep older history REACHABLE by paging until the message box actually gains
1498
- * something to scroll to.
1499
- *
1500
- * Older history is paged in by one trigger only: the user scrolling to the top
1501
- * of the message box. That trigger has two ways to die, and collapsed indexing
1502
- * rows cause both:
1503
- *
1504
- * 1. The box never scrolls. A file's every indexing pass (the first plus every
1505
- * CONTINUE pass, request AND response bubble each) folds into ONE row, so a
1506
- * full history page — twenty-plus messages — can render as a single line.
1507
- * Content shorter than the viewport fires no scroll event, so page 2 is
1508
- * never requested and any conversation the user had before that upload is
1509
- * permanently out of reach.
1510
- * 2. The fetched page adds no height. A page that is entirely the same file's
1511
- * earlier passes joins the collapsed row already on screen and renders
1512
- * nothing new. The user, sitting at scrollTop 0, scrolls up again — and
1513
- * because the position never changed, no further scroll event fires.
1514
- *
1515
- * Both are the same shape: fetch, re-measure, and keep going until the user
1516
- * genuinely gained reachable content, history ran out, or the pager stopped
1517
- * advancing. `isSatisfied` is what differs between the two (can the box scroll
1518
- * at all / did it grow), so the loop below takes it as a predicate.
1519
- *
1520
- * DOM-free like the rest of the engine — the caller supplies the measurement and
1521
- * awaits its own render before measuring, so agent.vue and the widget run the
1522
- * identical loop over their own pagers.
1523
- */
1524
- /** Overflow (px) that counts as "the user can scroll here". Comfortably more
1525
- * than the 60px top threshold that triggers the next page, so a filled box has
1526
- * real room to scroll rather than sitting one pixel from the trigger. */
1527
- declare const HISTORY_FILL_SLACK_PX = 64;
1528
- /** Pages one fill pass will request before giving up. Reached only by a chat
1529
- * whose history really is dozens of pages of one file's indexing passes; the
1530
- * cap exists so a pager that stops advancing can never spin forever. */
1531
- declare const MAX_HISTORY_FILL_PAGES = 24;
1532
- type FillHistoryViewportOptions = {
1533
- /** The user has reachable content and paging can stop. Called AFTER the
1534
- * caller's own render has settled (nextTick / rAF), since only the caller
1535
- * knows when its view has painted — hence the allowance for a promise. */
1536
- isSatisfied: () => boolean | Promise<boolean>;
1537
- /** All history is loaded — nothing left to page in. */
1538
- isEndOfList: () => boolean;
1539
- /** A history request is already in flight. Waited out, not treated as a stop
1540
- * condition: a background first-page refresh (the queue-detect tick fires one
1541
- * every couple of seconds while a file is indexing) would otherwise swallow
1542
- * the user's scroll-up entirely, and scrolling up again from scrollTop 0
1543
- * produces no second event to retry with. */
1544
- isLoading: () => boolean;
1545
- /** Messages currently loaded. Used to detect a page that added nothing, which
1546
- * means the pager is not advancing and looping would never terminate. */
1547
- messageCount: () => number;
1548
- /** Fetch ONE older page (the caller's own fetchMore path, scroll-restore and
1549
- * all). Return `false` when the request was NOT issued (the caller's own
1550
- * single-flight guard swallowed it) so the loop retries instead of reading
1551
- * the unchanged message count as an exhausted pager. Anything else, including
1552
- * undefined, means it was attempted. */
1553
- fetchOlder: () => Promise<boolean | void | any>;
1554
- /** The chat this fill was started for is gone (project switched, view
1555
- * unmounted, gate token bumped). Checked between pages so a stale fill can
1556
- * never keep paging another chat's history. */
1557
- isStale?: () => boolean;
1558
- maxPages?: number;
1559
- };
1560
- /**
1561
- * Page older history until `isSatisfied`, until history runs out, or until the
1562
- * pager stops advancing. Never throws: a failed page ends the fill, and the
1563
- * user's own scrolling remains the fallback trigger.
1564
- */
1565
- declare function fillHistoryViewport(opts: FillHistoryViewportOptions): Promise<void>;
1566
- /**
1567
- * One fill loop per view, with predicates COMBINED rather than dropped.
1568
- *
1569
- * Fills come from several places at once — a first page finishing, a window
1570
- * resize, a row being collapsed, and the user's own scroll to the top — and a
1571
- * plain "one at a time, drop the rest" guard picks the wrong winner: a resize
1572
- * fill (satisfied the moment the box can scroll at all) would swallow the user's
1573
- * scroll-up (which needs content specifically ABOVE them), and the scroll-up
1574
- * cannot be retried, because a reader parked at scrollTop 0 produces no further
1575
- * scroll event. Dropping the guard entirely is no better: every frame of a
1576
- * window drag would start its own 24-page loop.
1577
- *
1578
- * So a request that arrives mid-loop ANDs its predicate into the running one:
1579
- * the loop then keeps paging until EVERY caller is satisfied. Predicates that
1580
- * come true are dropped as it goes, so the cost stays flat.
1581
- */
1582
- declare function createHistoryFiller(base: Omit<FillHistoryViewportOptions, 'isSatisfied'> & {
1583
- /** Fired when the loop starts FETCHING and when it stops, and only on a real
1584
- * change.
1585
- *
1586
- * This — not the caller's own per-request `isLoading` — is what "older
1587
- * history is still coming in" means to a view. A fill is many pages, and
1588
- * `isLoading` drops to false between every one of them, so anything
1589
- * rendered off it flickers once per page for the whole loop. A collapsed
1590
- * indexing row whose run begins above the loaded window renders exactly
1591
- * that ("still loading this run" vs a status it cannot know yet), which is
1592
- * why the loop has to publish its own span.
1593
- *
1594
- * Fetching, NOT requested. Most fills fetch nothing: they are fired on every
1595
- * window resize, every row a user collapses, and every first-page load, and
1596
- * the overwhelmingly common outcome is `isSatisfied` returning true on the
1597
- * first look. Announcing at request time published a true/false pair for
1598
- * each of those, and the widget's own satisfied-check spans two animation
1599
- * frames — long enough for the browser to PAINT the intermediate state. Every
1600
- * collapsed row strobed through "loading" on every resize tick. So the span
1601
- * opens at the first actual page request, which is also the first moment the
1602
- * claim is true. */
1603
- onRunningChange?: (running: boolean) => void;
1604
- }): {
1605
- fill: (isSatisfied: () => boolean | Promise<boolean>) => Promise<void>;
1606
- isRunning: () => boolean;
1607
- };
1608
-
1609
1435
  /**
1610
1436
  * ChatSession host adapter + state types.
1611
1437
  *
@@ -1792,6 +1618,20 @@ interface ChatHost {
1792
1618
  * that cannot scroll has no way to reach page 2 (see viewport_fill). Only
1793
1619
  * the view can measure that, which is why the engine merely announces it. */
1794
1620
  onHistoryLoaded?(fetchMore: boolean, token: number): void;
1621
+ /**
1622
+ * A list refresh just changed heights: put the reader back where they were.
1623
+ *
1624
+ * Called at BOTH moments a first-page refresh moves things — the surface page
1625
+ * landing, and the deferred background-indexing batch merging on top of it a
1626
+ * round trip later — because leaving a wrong position on screen between the two
1627
+ * is what reads as "the scroll jumped, then travelled somewhere else".
1628
+ *
1629
+ * The view owns the decision (it is the only side that can measure): pinned to
1630
+ * the bottom means the bottom AFTER the batch merged, anywhere else means the
1631
+ * exact line the reader was on. Falls back to scrollToBottomIfSticky when a host
1632
+ * does not implement it, which is the old behaviour.
1633
+ */
1634
+ settleScroll?(): void;
1795
1635
  cancelRequest(opts: {
1796
1636
  url: string;
1797
1637
  method: string;
@@ -1849,6 +1689,404 @@ interface ChatHost {
1849
1689
  updateComposerControls(): void;
1850
1690
  }
1851
1691
 
1692
+ /**
1693
+ * History mapping (pure). Moved verbatim from the chatbox. The clear-horizon
1694
+ * timestamp and the "Indexing: …" display label are INJECTED (clearedAt param,
1695
+ * formatIndexingLabel callback) so the engine touches neither localStorage nor
1696
+ * view-specific display formatting. projectId is passed for link sanitization.
1697
+ */
1698
+
1699
+ declare function filterListByClearHorizon(list: any[], clearedAt: number): any[];
1700
+ declare function normalizeTextContent(content: any): string;
1701
+ declare function extractLastUserTextFromRequest(requestBody: any): string;
1702
+ /** The two openings an indexing prompt can have. A bg-queue item that starts with
1703
+ * neither is an ordinary chat that happened to be routed onto that queue. */
1704
+ declare function isIndexingRequestText(userText: any): boolean;
1705
+ type IndexingRequestRef = {
1706
+ name: string;
1707
+ path?: string;
1708
+ mime?: string;
1709
+ size?: number;
1710
+ /** A CONTINUE pass rather than the run's first. */
1711
+ continued: boolean;
1712
+ };
1713
+ /**
1714
+ * The file an indexing prompt is about, read back out of the prompt itself.
1715
+ *
1716
+ * The prompt is the only description of the pass that survives on the server, so
1717
+ * this is how BOTH a history rebuild and a worker-minted pass the client never
1718
+ * dispatched (ChatSession._adoptWorkerIndexingPasses) recover the file. Shared so
1719
+ * the two produce the same `_indexFile`, which is what makes them group together.
1720
+ */
1721
+ declare function parseIndexingRequestText(userText: any): IndexingRequestRef | null;
1722
+ /**
1723
+ * One bounded look at the background-indexing queue: which files still have a
1724
+ * pass pending or running? This is the same negative signal ChatSession's
1725
+ * display layer relies on - for a worker-driven (auto_continue) run, only the
1726
+ * queue can say the run is over, because the worker enqueues continuation
1727
+ * passes the client never dispatched.
1728
+ *
1729
+ * Returns every storage path AND file name found on live passes (both, because
1730
+ * older prompts may lack the storage-path line), plus `checked`: false when a
1731
+ * page came back full, in which case absence from `keys` proves nothing and
1732
+ * the caller must keep whatever state it already had.
1733
+ *
1734
+ * SCOPE: the probed queue is "<userId>-bg" - THIS user's dispatches only. A
1735
+ * chain launched by another collaborator or a widget end-user lives on their
1736
+ * queue and is invisible here, so "idle" must never be read as "nobody is
1737
+ * indexing this file", only as "this user's runs are over". The durable done::
1738
+ * marker (indexDoneUniqueId) is the cross-user signal.
1739
+ */
1740
+ declare function fetchLiveIndexingKeys(params: {
1741
+ service: string;
1742
+ owner: string;
1743
+ platform: 'claude' | 'openai';
1744
+ /** Same value the dispatch used - see bgIndexingQueueName. */
1745
+ userId?: string;
1746
+ }): Promise<{
1747
+ keys: Set<string>;
1748
+ checked: boolean;
1749
+ at: number;
1750
+ }>;
1751
+ /** Test hook: drop split-fetch state (all keys, or one). */
1752
+ declare function __resetSplitHistoryState(key?: string): void;
1753
+ type SplitHistoryResult = {
1754
+ list: any[];
1755
+ endOfList: boolean;
1756
+ startKeyHistory: any[];
1757
+ /** True when this chat had never been walked in this session — the first
1758
+ * paint. Consumers gate the "Loading indexing history" hint on it: a
1759
+ * mid-walk tab return restarts the walk for cursor safety but must stay
1760
+ * silent (flashing the hint on every return was the reported bug). */
1761
+ firstLoad?: boolean;
1762
+ /** Present only when `deferBg` was requested AND bg work remains: resolves
1763
+ * with the stub batch fetched in the background (the per-key lock is held
1764
+ * until it settles, so no other history call can interleave). The caller
1765
+ * merges the batch by timestamp — the same path older pages use. */
1766
+ bgPending?: Promise<{
1767
+ list: any[];
1768
+ endOfList: boolean;
1769
+ }>;
1770
+ };
1771
+ declare function getSplitChatHistory(params: {
1772
+ service: string;
1773
+ owner: string;
1774
+ platform: 'claude' | 'openai';
1775
+ userId?: string;
1776
+ }, fetchOptions: Record<string, any>,
1777
+ /** Test seam: replaces getChatHistory. Not for production callers. */
1778
+ _fetchImpl?: typeof getChatHistory): Promise<SplitHistoryResult>;
1779
+ type MapHistoryOptions = {
1780
+ clearedAt: number;
1781
+ projectId: string;
1782
+ /** View-side display formatter for "Indexing:/Reindexing: …" bubbles. */
1783
+ formatIndexingLabel: (name: string, mime?: string, size?: number | null, storagePath?: string, reindex?: boolean, continued?: boolean) => string;
1784
+ };
1785
+ declare function mapHistoryListToMessages(list: any[], platform: 'claude' | 'openai', opts: MapHistoryOptions): {
1786
+ messages: any[];
1787
+ runningItemIds: string[];
1788
+ };
1789
+ interface RescueDecisionContext {
1790
+ /** Is this `_serverItemId` in the page that was just fetched? */
1791
+ hasServerId: (id: string) => boolean;
1792
+ /**
1793
+ * The fetched page already shows a non-background pending assistant.
1794
+ *
1795
+ * Only meaningful for a bubble with NO server id, where it is the sole
1796
+ * available answer to "is this turn already represented?". For a bubble that
1797
+ * HAS one, hasServerId answers exactly the same question exactly, and applying
1798
+ * this on top of it would drop an in-flight turn whose server copy simply is
1799
+ * not in the page that was fetched.
1800
+ */
1801
+ pageHasPendingAssistant: boolean;
1802
+ /** state.sending: an immediate send is in flight for this chat. */
1803
+ sending: boolean;
1804
+ /** The bubble directly after this one in the local list. */
1805
+ next?: ChatMessage | null;
1806
+ /** The chat this fetch is FOR. A bubble stamped for another must not cross. */
1807
+ loadKey?: string;
1808
+ }
1809
+ declare function shouldRescueInFlightMessage(m: ChatMessage, ctx: RescueDecisionContext): boolean;
1810
+
1811
+ /**
1812
+ * Hold the reader's place in the message list.
1813
+ *
1814
+ * A chat box mutates constantly WITHOUT the reader asking for it: an older page
1815
+ * prepends, a poll resolves, an indexing row splices in or changes label, a link
1816
+ * chip goes grey, an image preview finishes decoding, the "Fetching history..."
1817
+ * bar appears and disappears. Every one of those changes the height of something
1818
+ * that may sit ABOVE the viewport, and the browser answers by keeping scrollTop —
1819
+ * which slides the sentence the user was reading out from under them.
1820
+ *
1821
+ * Both clients had their own copy of a row anchor for the ONE case each could
1822
+ * bracket (agent.vue watched its row-key list, the widget bracketed its full
1823
+ * re-render). Everything else — anything that changed a height without changing
1824
+ * the row SET, and everything asynchronous — was uncovered in both. This is the
1825
+ * single implementation, and it covers both shapes:
1826
+ *
1827
+ * preserve(fn) / capture() + restore(a)
1828
+ * A mutation you can bracket. Measures immediately before and immediately
1829
+ * after, so it is exact even when the mutation tears the list down.
1830
+ *
1831
+ * remember() + hold()
1832
+ * A layout change you CANNOT bracket — an image decoding, a font arriving,
1833
+ * a re-parse triggered from a promise. `remember()` runs from the view's
1834
+ * scroll handler, so the anchor is always the reader's own last position;
1835
+ * `hold()` puts that position back whenever something settles.
1836
+ *
1837
+ * The staleness rule is what makes the unbracketed half safe. A layout change
1838
+ * above the viewport does NOT change scrollTop — the browser preserves it, which
1839
+ * is precisely why the content appears to jump. So a remembered anchor is still
1840
+ * valid exactly while `box.scrollTop` equals the value it was captured at. If it
1841
+ * differs, something moved the box on purpose (the user scrolled, a clamp fired,
1842
+ * or the browser's own scroll anchoring already compensated), and `hold()`
1843
+ * re-captures rather than dragging the reader back to a position they left.
1844
+ *
1845
+ * DOM-free like the rest of the engine: the element shapes below are structural,
1846
+ * so real DOM nodes satisfy them while this file imports nothing from lib.dom.
1847
+ */
1848
+ interface AnchorRect {
1849
+ top: number;
1850
+ }
1851
+ interface AnchorRowEl {
1852
+ getAttribute(name: string): string | null;
1853
+ getBoundingClientRect(): AnchorRect;
1854
+ offsetHeight: number;
1855
+ parentNode: unknown;
1856
+ }
1857
+ /** Anything inside the list that resizes on its own schedule. See absorb(). */
1858
+ interface AnchorGrowableEl {
1859
+ getBoundingClientRect(): AnchorRect;
1860
+ offsetHeight: number;
1861
+ }
1862
+ interface AnchorBoxEl {
1863
+ children: ArrayLike<AnchorRowEl>;
1864
+ getBoundingClientRect(): AnchorRect;
1865
+ scrollTop: number;
1866
+ scrollHeight: number;
1867
+ clientHeight: number;
1868
+ }
1869
+ interface RowAnchor {
1870
+ /** data-row-key of the anchored row, or null when nothing was anchorable. */
1871
+ key: string | null;
1872
+ /** Offset of that row from the top of the viewport. Negative above the fold. */
1873
+ top: number;
1874
+ /** data-row-pos, present only on rows that can RELOCATE (see below). */
1875
+ pos: string | null;
1876
+ /** scrollTop at capture time. The staleness check, and the raw fallback. */
1877
+ scrollTop: number;
1878
+ /**
1879
+ * scrollHeight at capture time. How much the list GREW is the best available
1880
+ * answer when the anchored row itself cannot be found again, and the bound on
1881
+ * how far a correction can legitimately be.
1882
+ */
1883
+ scrollHeight: number;
1884
+ /**
1885
+ * The anchored element itself. A view that patches in place (Vue) keeps the
1886
+ * same node across an update, so restore is one rect read instead of a scan;
1887
+ * a view that rebuilds the list (the widget) drops it and falls back to the
1888
+ * key. Never trusted without re-checking that it is still in the box.
1889
+ */
1890
+ el: AnchorRowEl | null;
1891
+ /**
1892
+ * The next few anchorable rows below the primary, each with its own offset.
1893
+ *
1894
+ * The primary row does not always survive: a refresh can drop it, a collapsed
1895
+ * indexing row can be re-identified, an expanded group can fold. Without a
1896
+ * fallback the only thing left is lost(), which guesses from the list's total
1897
+ * growth — and total growth includes everything added BELOW the reader, so a
1898
+ * merge that lands rows on both sides of them over-pays. A second row that is
1899
+ * still there beats any guess, and collecting them costs nothing: capture is
1900
+ * already walking these rows.
1901
+ */
1902
+ alts?: Array<{
1903
+ key: string;
1904
+ top: number;
1905
+ pos: string | null;
1906
+ el: AnchorRowEl;
1907
+ }>;
1908
+ }
1909
+ interface ScrollAnchorOptions {
1910
+ /** The scrolling message box, or null when it is not mounted. */
1911
+ getBox: () => AnchorBoxEl | null;
1912
+ /**
1913
+ * The reader is pinned to the bottom. There the bottom IS the anchor and the
1914
+ * scrollToBottom* paths own the position, so every method here no-ops.
1915
+ */
1916
+ isStuck: () => boolean;
1917
+ /**
1918
+ * The reader cannot see this box right now (the tab is hidden), so FREEZE:
1919
+ * remember where they were and refuse to move them.
1920
+ *
1921
+ * A hidden tab still runs everything that mutates the list — a resumed poll, a
1922
+ * head refresh and its deferred background batch, a settling request — and each
1923
+ * of those would otherwise write scrollTop against a layout nobody is looking at
1924
+ * and re-stamp the remembered position on the way through. The reader then comes
1925
+ * back to wherever the last of those writes happened to land, which is the
1926
+ * "somehow placed in the middle" they see, and only the NEXT correction puts
1927
+ * them right. So while frozen, reads still happen but nothing writes and nothing
1928
+ * re-stamps: the anchor holds the last position the reader actually had, and one
1929
+ * hold() on return puts them back on it.
1930
+ */
1931
+ isFrozen?: () => boolean;
1932
+ /**
1933
+ * Fall back to the raw scrollTop when the anchored row cannot be found again.
1934
+ *
1935
+ * For a view that REBUILDS the list (the widget's renderMessages), detaching
1936
+ * every child collapses scrollHeight and the browser clamps scrollTop to 0,
1937
+ * so the raw offset is strictly better than the clamp it would otherwise be
1938
+ * left with. For a view that patches in place (Vue) the browser has already
1939
+ * kept a sane position and re-imposing a stale offset is worse than nothing.
1940
+ */
1941
+ rawFallback?: boolean;
1942
+ }
1943
+ interface ScrollAnchor {
1944
+ /** Measure the reader's current place. Null while pinned to the bottom. */
1945
+ capture: () => RowAnchor | null;
1946
+ /** Put a captured place back. Safe to call with null. */
1947
+ restore: (anchor: RowAnchor | null) => void;
1948
+ /** capture -> mutate -> restore, for a mutation you can bracket. */
1949
+ preserve: <T>(mutate: () => T) => T;
1950
+ /** Record the reader's place. Call from the box's scroll handler. */
1951
+ remember: () => void;
1952
+ /** Put the remembered place back, if it is still the reader's own. */
1953
+ hold: () => void;
1954
+ /** The reader is going away: park the exact place they are leaving. */
1955
+ park: () => void;
1956
+ /**
1957
+ * They are back. Puts them on the parked place, and STAYS ARMED until it has
1958
+ * actually landed. Returns true when the host must pin to the bottom instead
1959
+ * (the reader left pinned), which only the host can do meaningfully.
1960
+ */
1961
+ settleReturn: () => boolean;
1962
+ /** A return is armed: its position, not the host's, decides scrollTop. */
1963
+ isReturning: () => boolean;
1964
+ /** Pin to the bottom, instantly, recording the write. The ONLY way to pin. */
1965
+ pinBottom: () => void;
1966
+ /** The box is not being painted (hidden tab). Shared so hosts agree. */
1967
+ isFrozen: () => boolean;
1968
+ /** Deprecated alias of settleReturn, kept so a stale dist does not break. */
1969
+ thaw: () => void;
1970
+ /** Absorb one element's own resize. See below. */
1971
+ absorb: (el: AnchorGrowableEl | null | undefined) => void;
1972
+ /** Drop the remembered place (chat switch, unmount). */
1973
+ forget: () => void;
1974
+ }
1975
+ declare function createScrollAnchor(options: ScrollAnchorOptions): ScrollAnchor;
1976
+
1977
+ /**
1978
+ * Keep older history REACHABLE by paging until the message box actually gains
1979
+ * something to scroll to.
1980
+ *
1981
+ * Older history is paged in by one trigger only: the user scrolling to the top
1982
+ * of the message box. That trigger has two ways to die, and collapsed indexing
1983
+ * rows cause both:
1984
+ *
1985
+ * 1. The box never scrolls. A file's every indexing pass (the first plus every
1986
+ * CONTINUE pass, request AND response bubble each) folds into ONE row, so a
1987
+ * full history page — twenty-plus messages — can render as a single line.
1988
+ * Content shorter than the viewport fires no scroll event, so page 2 is
1989
+ * never requested and any conversation the user had before that upload is
1990
+ * permanently out of reach.
1991
+ * 2. The fetched page adds no height. A page that is entirely the same file's
1992
+ * earlier passes joins the collapsed row already on screen and renders
1993
+ * nothing new. The user, sitting at scrollTop 0, scrolls up again — and
1994
+ * because the position never changed, no further scroll event fires.
1995
+ *
1996
+ * Both are the same shape: fetch, re-measure, and keep going until the user
1997
+ * genuinely gained reachable content, history ran out, or the pager stopped
1998
+ * advancing. `isSatisfied` is what differs between the two (can the box scroll
1999
+ * at all / did it grow), so the loop below takes it as a predicate.
2000
+ *
2001
+ * DOM-free like the rest of the engine — the caller supplies the measurement and
2002
+ * awaits its own render before measuring, so agent.vue and the widget run the
2003
+ * identical loop over their own pagers.
2004
+ */
2005
+ /** Overflow (px) that counts as "the user can scroll here". Comfortably more
2006
+ * than the 60px top threshold that triggers the next page, so a filled box has
2007
+ * real room to scroll rather than sitting one pixel from the trigger. */
2008
+ declare const HISTORY_FILL_SLACK_PX = 64;
2009
+ /** Pages one fill pass will request before giving up. Reached only by a chat
2010
+ * whose history really is dozens of pages of one file's indexing passes; the
2011
+ * cap exists so a pager that stops advancing can never spin forever. */
2012
+ declare const MAX_HISTORY_FILL_PAGES = 24;
2013
+ type FillHistoryViewportOptions = {
2014
+ /** The user has reachable content and paging can stop. Called AFTER the
2015
+ * caller's own render has settled (nextTick / rAF), since only the caller
2016
+ * knows when its view has painted — hence the allowance for a promise. */
2017
+ isSatisfied: () => boolean | Promise<boolean>;
2018
+ /** All history is loaded — nothing left to page in. */
2019
+ isEndOfList: () => boolean;
2020
+ /** A history request is already in flight. Waited out, not treated as a stop
2021
+ * condition: a background first-page refresh (the queue-detect tick fires one
2022
+ * every couple of seconds while a file is indexing) would otherwise swallow
2023
+ * the user's scroll-up entirely, and scrolling up again from scrollTop 0
2024
+ * produces no second event to retry with. */
2025
+ isLoading: () => boolean;
2026
+ /** Messages currently loaded. Used to detect a page that added nothing, which
2027
+ * means the pager is not advancing and looping would never terminate. */
2028
+ messageCount: () => number;
2029
+ /** Fetch ONE older page (the caller's own fetchMore path, scroll-restore and
2030
+ * all). Return `false` when the request was NOT issued (the caller's own
2031
+ * single-flight guard swallowed it) so the loop retries instead of reading
2032
+ * the unchanged message count as an exhausted pager. Anything else, including
2033
+ * undefined, means it was attempted. */
2034
+ fetchOlder: () => Promise<boolean | void | any>;
2035
+ /** The chat this fill was started for is gone (project switched, view
2036
+ * unmounted, gate token bumped). Checked between pages so a stale fill can
2037
+ * never keep paging another chat's history. */
2038
+ isStale?: () => boolean;
2039
+ maxPages?: number;
2040
+ };
2041
+ /**
2042
+ * Page older history until `isSatisfied`, until history runs out, or until the
2043
+ * pager stops advancing. Never throws: a failed page ends the fill, and the
2044
+ * user's own scrolling remains the fallback trigger.
2045
+ */
2046
+ declare function fillHistoryViewport(opts: FillHistoryViewportOptions): Promise<void>;
2047
+ /**
2048
+ * One fill loop per view, with predicates COMBINED rather than dropped.
2049
+ *
2050
+ * Fills come from several places at once — a first page finishing, a window
2051
+ * resize, a row being collapsed, and the user's own scroll to the top — and a
2052
+ * plain "one at a time, drop the rest" guard picks the wrong winner: a resize
2053
+ * fill (satisfied the moment the box can scroll at all) would swallow the user's
2054
+ * scroll-up (which needs content specifically ABOVE them), and the scroll-up
2055
+ * cannot be retried, because a reader parked at scrollTop 0 produces no further
2056
+ * scroll event. Dropping the guard entirely is no better: every frame of a
2057
+ * window drag would start its own 24-page loop.
2058
+ *
2059
+ * So a request that arrives mid-loop ANDs its predicate into the running one:
2060
+ * the loop then keeps paging until EVERY caller is satisfied. Predicates that
2061
+ * come true are dropped as it goes, so the cost stays flat.
2062
+ */
2063
+ declare function createHistoryFiller(base: Omit<FillHistoryViewportOptions, 'isSatisfied'> & {
2064
+ /** Fired when the loop starts FETCHING and when it stops, and only on a real
2065
+ * change.
2066
+ *
2067
+ * This — not the caller's own per-request `isLoading` — is what "older
2068
+ * history is still coming in" means to a view. A fill is many pages, and
2069
+ * `isLoading` drops to false between every one of them, so anything
2070
+ * rendered off it flickers once per page for the whole loop. A collapsed
2071
+ * indexing row whose run begins above the loaded window renders exactly
2072
+ * that ("still loading this run" vs a status it cannot know yet), which is
2073
+ * why the loop has to publish its own span.
2074
+ *
2075
+ * Fetching, NOT requested. Most fills fetch nothing: they are fired on every
2076
+ * window resize, every row a user collapses, and every first-page load, and
2077
+ * the overwhelmingly common outcome is `isSatisfied` returning true on the
2078
+ * first look. Announcing at request time published a true/false pair for
2079
+ * each of those, and the widget's own satisfied-check spans two animation
2080
+ * frames — long enough for the browser to PAINT the intermediate state. Every
2081
+ * collapsed row strobed through "loading" on every resize tick. So the span
2082
+ * opens at the first actual page request, which is also the first moment the
2083
+ * claim is true. */
2084
+ onRunningChange?: (running: boolean) => void;
2085
+ }): {
2086
+ fill: (isSatisfied: () => boolean | Promise<boolean>) => Promise<void>;
2087
+ isRunning: () => boolean;
2088
+ };
2089
+
1852
2090
  /**
1853
2091
  * Background file-indexing turns, collapsed into ONE row per file.
1854
2092
  *
@@ -2419,7 +2657,30 @@ declare class ChatSession {
2419
2657
  *
2420
2658
  * `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
2421
2659
  * poll simply cannot be stopped and is left running — see pausePolling.
2660
+ *
2661
+ * (This block documents _trackPoll, further down. The two methods below sit between it
2662
+ * and its subject.)
2663
+ */
2664
+ /**
2665
+ * Foreground poll with an early-probe race.
2666
+ *
2667
+ * skapi's poll() is a bare setInterval(fn, latency) with NO check at t=0, so the earliest a
2668
+ * reply can be observed is one full POLL_INTERVAL (3s) after dispatch. For a long generation
2669
+ * that granularity is free. For a SHORT one it is nearly pure dead time: a greeting that the
2670
+ * provider finishes in 1s still waits until the 3s tick, which measured as a large share of a
2671
+ * 5s "yo" round trip.
2672
+ *
2673
+ * So keep the 3s interval as the steady state, and additionally point-look-up the item a few
2674
+ * times early, on a widening schedule. Whichever answers first wins and the other is stopped.
2675
+ * The probe uses the csrHistoryItemLookup hook both clients already implement; without it this
2676
+ * degrades to exactly the old behaviour.
2677
+ *
2678
+ * FOREGROUND ONLY. Background indexing polls keep the flat cadence: nobody is watching them,
2679
+ * and they are the ones bounded by MAX_CONCURRENT_BG_POLLS, so adding probes there would spend
2680
+ * the request budget the cap exists to protect.
2422
2681
  */
2682
+ attachForegroundPoll(source: any, itemId: string, opts?: any): any;
2683
+ private _fgPollWithEarlyProbe;
2423
2684
  private _trackPoll;
2424
2685
  /** Background polls currently attached, for the MAX_CONCURRENT_BG_POLLS budget.
2425
2686
  * Counts the registry rather than a separate tally so it cannot drift: every
@@ -2473,6 +2734,40 @@ declare class ChatSession {
2473
2734
  * still render) and a later expand retries. */
2474
2735
  hydrateCompactItems(itemIds: string[]): Promise<void>;
2475
2736
  updateHistoryCache(): void;
2737
+ /**
2738
+ * Give the immediate-send pair the server's id for their turn, the moment the
2739
+ * dispatch learns it.
2740
+ *
2741
+ * WHY THIS EXISTS. An immediate send pushes its user bubble and its
2742
+ * "Thinking..." placeholder locally, and until now neither ever carried a
2743
+ * _serverItemId — only the QUEUED path stamped one, off its ack. So for the
2744
+ * whole life of the turn there was no way to tell the local copy and the
2745
+ * server's copy of the SAME turn apart, and the history merge fell back to a
2746
+ * heuristic: rescue the local pair unless the freshly-fetched page happens to
2747
+ * contain a pending assistant.
2748
+ *
2749
+ * That heuristic has a hole exactly one poll interval wide. The server settles
2750
+ * the request; for up to POLL_INTERVAL the client has not noticed, so
2751
+ * `state.sending` is still true and the local pair is still on screen — while a
2752
+ * history fetch issued in that window returns the turn ALREADY SETTLED, with no
2753
+ * pending assistant in it. The rescue then re-appends the local pair below the
2754
+ * server's copy (the question, twice), and when the poll finally resolves,
2755
+ * typewriteLatestReply writes the answer into the rescued placeholder because it
2756
+ * is the only pending assistant left (the answer, twice). updateHistoryCache
2757
+ * persists the result, so it survives every later visit.
2758
+ *
2759
+ * Navigating away while waiting and coming back is what lands a fetch in that
2760
+ * window: a remount runs refreshGate -> a fresh first page, at an arbitrary
2761
+ * moment relative to the 3s poll.
2762
+ *
2763
+ * With the id on the bubbles, both clients' rescue loops skip them through the
2764
+ * dedup they already have (`_serverItemId is in this page`), the reply the
2765
+ * dispatch caches inherits the id too, and nothing needs a new special case.
2766
+ *
2767
+ * Matched by _localId, never by index: a file's indexing rows are spliced in
2768
+ * above these bubbles while the request is in flight.
2769
+ */
2770
+ private _stampTurnWithItemId;
2476
2771
  /**
2477
2772
  * Land a resolved reply in the history cache of a chat that is NOT currently
2478
2773
  * visible, without touching state.messages. Mirrors the cache-only path in
@@ -2575,6 +2870,25 @@ declare class ChatSession {
2575
2870
  */
2576
2871
  settleStagedMessage(stageId: string): void;
2577
2872
  dispatchComposedMessage(composed: string, useBgQueue?: boolean, composedForLlm?: string, extractContent?: any, fileUrls?: any, pinned?: PinnedDispatchContext): void;
2873
+ /**
2874
+ * Scroll for a dispatch that is going out NOW, but never for one arriving late.
2875
+ *
2876
+ * A turn with attachments does not dispatch when the user hits Send: it waits out
2877
+ * its uploads and then its whole indexing chain, which is minutes
2878
+ * (awaitIndexingDrained). By then the reader has very often scrolled up into
2879
+ * history to pass the time, and the forcing scrollToBottom yanked them out of it
2880
+ * — and worse, force-pinned stickToBottom, which no-ops every method on the
2881
+ * scroll anchor and re-arms the queue-detect poll whose only bail is
2882
+ * !stickToBottom.
2883
+ *
2884
+ * `stageId` is the exact marker for that case: only the attachment path ever
2885
+ * produces one. The gesture itself was already paid for at stage time, where
2886
+ * stageOutgoingMessage forces the scroll while the user is still looking at the
2887
+ * composer. Deliberately NOT gated on "did we find the staged bubble" — a remount
2888
+ * rebuilds from the cache and the turn is appended rather than replaced, which is
2889
+ * just as late and just as unrequested.
2890
+ */
2891
+ private scrollForDispatch;
2578
2892
  promoteNextBgQueuedToRunning(): void;
2579
2893
  promoteNextQueuedToRunning(): void;
2580
2894
  /**
@@ -2593,6 +2907,24 @@ declare class ChatSession {
2593
2907
  private _ownThinkingIndex;
2594
2908
  resolveQueuedUserBubble(serverId?: string): number | undefined;
2595
2909
  insertAtTarget(msg: ChatMessage, targetIdx: number): void;
2910
+ /**
2911
+ * The server's OWN copy of this turn is already on screen.
2912
+ *
2913
+ * A first-page fetch can land between the server settling the item and this
2914
+ * poll's tick, and now that the local bubbles carry the item id
2915
+ * (_stampTurnWithItemId) the rescue correctly drops them and renders the
2916
+ * server's settled pair instead. There is then nothing left to resolve: the
2917
+ * -1 fallback would push the answer in a SECOND time at the bottom of the list,
2918
+ * and the positional fallbacks would hijack some other turn's bubble.
2919
+ *
2920
+ * The USER bubble counts, not just a settled assistant: an item whose answer is
2921
+ * empty produces no assistant bubble at all in the mapper, and that variant
2922
+ * would otherwise still bottom-push "No text response received...". While a turn
2923
+ * is genuinely live its user bubble is always pending (the queued branch sets
2924
+ * isPendingQueued, promoteNextQueuedToRunning sets isPendingInProcess), so this
2925
+ * cannot fire early.
2926
+ */
2927
+ private _turnAlreadyRendered;
2596
2928
  onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string, ownerKey?: string): void;
2597
2929
  onQueuedSendError(_composed: string, err: any, serverId?: string, ownerKey?: string): void;
2598
2930
  cancelQueuedMessage(msg: ChatMessage, idx: number): void;
@@ -2795,4 +3127,4 @@ declare class ChatSession {
2795
3127
  bumpGate(): void;
2796
3128
  }
2797
3129
 
2798
- export { type AiAgentPlatform, type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, BOM, BOM_EXTS, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_INPUT_CAP_RATIO, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatGreetingParams, type ChatGreetingParts, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_CONTEXT_WINDOW, DEFAULT_OPENAI_MODEL, type DisplayEntry, EMPTY_INDEXING_REPLY, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, EXT_CONTENT_TYPES, type EncodingClass, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_BUDGET_RATIO, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, HTML_EXTS, HTML_HEAD_WINDOW, IMAGE_PREVIEWS_PER_MESSAGE, INDEXING_COMPLETE_MARKER, INLINE_LINK_GLYPH, INLINE_LINK_UNAVAILABLE_GLYPH, INLINE_LINK_UNAVAILABLE_SUFFIX, INPUT_CAP_RATIO, type ImagePreviewContext, type IndexRunPatch, type IndexRunStatus, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingRequestRef, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkMarkupOptions, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_CONCURRENT_BG_POLLS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_OUTPUT_BY_MODEL, MAX_OUTPUT_TOKENS, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MINT_CACHE_GENERATION, MIN_INPUT_TOKEN_BUDGET, MIN_PER_REQUEST_INPUT_CAP, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, PRESIGN_SAFETY_MARGIN_MS, PREVIEWABLE_IMAGE_CONTENT_TYPES, PREVIEW_BROWSER_CACHE_SECONDS, PREVIEW_URL_EXPIRES_SECONDS, type ParsedAiAgent, type PinnedDispatchContext, type PreviewImageEl, RENDER_FROM_TOKEN, RTF_EXTS, RUN_RECORD_WORKING_STALE_MS, type RenderableInlineLink, type RunStubInfo, TOOL_AND_RESPONSE_BUFFER, type VisionProfile, XML_EXTS, __resetSplitHistoryState, applyEncodingDeclaration, bgIndexingQueueName, buildAiAgentValue, buildBoundedChatMessages, buildChatDisplayList, buildChatGreeting, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildHistoryItemFullId, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, clearImagePreviewCache, composeUserMessage, configureChatEngine, contentTypeForExt, createHistoryFiller, createInlineLinkRegex, encodePathSegments, encodingClassForExt, ensureHtmlCharset, ensureXmlEncoding, escapeInlineHtml, escapeRtfNonAscii, estimateMessageTokens, estimateTextTokens, extOf, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fetchLiveIndexingKeys, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, getInputTokenBudget, getMaxOutputTokens, getModelContextWindow, getProjectContextWindow, getSplitChatHistory, getVisionProfile, groupAttachmentFailures, hasBom, hydrateImagePreviews, indexDoneUniqueId, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isIndexingRequestText, isLinkUnavailable, isNonRetryableRequestError, isOfficeFile, isPreviewableImagePath, isProviderApiKeyError, isServerExtractable, isServiceDbAttachmentHref, linkUnavailableKeyForHref, linkUnavailableKeyForPath, linkUnavailableKeysForPath, listClaudeModels, listOpenAIModels, looksLikeRtf, makeExtractPlaceholder, mapHistoryListToMessages, markImagePreviewStale, mintCacheBustStamp, needsBomForExt, normalizeAttachmentPathCandidate, normalizeExt, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAiAgentValue, parseAttachmentContent, parseIndexingLabel, parseIndexingRequestText, peekImagePreviewUrl, prepareDownloadText, presignExpiryEpochMs, previewImageContentType, previewMintCacheToken, previewableExtOf, readExpiredAttachmentHref, registerAttachmentParser, registerModelContextWindows, renderInlineLinkHtml, repairUrlEntities, repairUrlWhitespace, resolveImagePreviewUrl, runIndexUniqueId, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, setProjectContextWindow, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, upsertIndexRunRecordSafe, wallClockNow };
3130
+ export { type AiAgentPlatform, type AnchorBoxEl, type AnchorRowEl, type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, BOM, BOM_EXTS, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_INPUT_CAP_RATIO, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatGreetingParams, type ChatGreetingParts, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_CONTEXT_WINDOW, DEFAULT_OPENAI_MODEL, type DisplayEntry, EMPTY_INDEXING_REPLY, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, EXT_CONTENT_TYPES, type EncodingClass, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_BUDGET_RATIO, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, HTML_EXTS, HTML_HEAD_WINDOW, IMAGE_PREVIEWS_PER_MESSAGE, INDEXING_COMPLETE_MARKER, INLINE_LINK_GLYPH, INLINE_LINK_UNAVAILABLE_GLYPH, INLINE_LINK_UNAVAILABLE_SUFFIX, INPUT_CAP_RATIO, type ImagePreviewContext, type IndexRunPatch, type IndexRunStatus, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingRequestRef, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkMarkupOptions, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_CONCURRENT_BG_POLLS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_OUTPUT_BY_MODEL, MAX_OUTPUT_TOKENS, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MINT_CACHE_GENERATION, MIN_INPUT_TOKEN_BUDGET, MIN_PER_REQUEST_INPUT_CAP, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, PRESIGN_SAFETY_MARGIN_MS, PREVIEWABLE_IMAGE_CONTENT_TYPES, PREVIEW_BROWSER_CACHE_SECONDS, PREVIEW_LAYOUT_BOX_SELECTOR, PREVIEW_URL_EXPIRES_SECONDS, type ParsedAiAgent, type PinnedDispatchContext, type PreviewImageEl, RENDER_FROM_TOKEN, RTF_EXTS, RUN_RECORD_WORKING_STALE_MS, type RenderableInlineLink, type RescueDecisionContext, type RowAnchor, type RunStubInfo, type ScrollAnchor, type ScrollAnchorOptions, TOOL_AND_RESPONSE_BUFFER, type VisionProfile, XML_EXTS, __resetSplitHistoryState, applyEncodingDeclaration, bgIndexingQueueName, buildAiAgentValue, buildBoundedChatMessages, buildChatDisplayList, buildChatGreeting, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildHistoryItemFullId, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, canonicalizePathForm, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, clearImagePreviewCache, composeUserMessage, configureChatEngine, contentTypeForExt, createHistoryFiller, createInlineLinkRegex, createScrollAnchor, encodePathSegments, encodingClassForExt, ensureHtmlCharset, ensureXmlEncoding, escapeInlineHtml, escapeRtfNonAscii, estimateMessageTokens, estimateTextTokens, extOf, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fetchLiveIndexingKeys, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, getInputTokenBudget, getMaxOutputTokens, getModelContextWindow, getProjectContextWindow, getSplitChatHistory, getVisionProfile, groupAttachmentFailures, hasBom, hydrateImagePreviews, indexDoneUniqueId, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isIndexingRequestText, isLinkUnavailable, isNonRetryableRequestError, isOfficeFile, isPreviewableImagePath, isProviderApiKeyError, isServerExtractable, isServiceDbAttachmentHref, linkUnavailableKeyForHref, linkUnavailableKeyForPath, linkUnavailableKeysForPath, listClaudeModels, listOpenAIModels, looksLikeRtf, makeExtractPlaceholder, mapHistoryListToMessages, markImagePreviewStale, mintCacheBustStamp, needsBomForExt, normalizeAttachmentPathCandidate, normalizeExt, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAiAgentValue, parseAttachmentContent, parseIndexingLabel, parseIndexingRequestText, peekImagePreviewUrl, prepareDownloadText, presignExpiryEpochMs, previewImageContentType, previewLayoutBox, previewMintCacheToken, previewableExtOf, readExpiredAttachmentHref, registerAttachmentParser, registerModelContextWindows, renderInlineLinkHtml, repairUrlEntities, repairUrlWhitespace, resolveImagePreviewUrl, runIndexUniqueId, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, setProjectContextWindow, shouldRescueInFlightMessage, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, upsertIndexRunRecordSafe, wallClockNow };