bunnyquery 1.8.13 → 1.8.14
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/bunnyquery.css +8 -0
- package/bunnyquery.js +508 -90
- package/dist/engine.cjs +446 -51
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +482 -212
- package/dist/engine.d.ts +482 -212
- package/dist/engine.mjs +442 -52
- package/dist/engine.mjs.map +1 -1
- package/package.json +3 -2
- package/src/engine/history.ts +64 -0
- package/src/engine/image_preview.ts +0 -0
- package/src/engine/index.ts +17 -0
- package/src/engine/link_markup.ts +9 -1
- package/src/engine/links.ts +31 -3
- package/src/engine/scroll_anchor.ts +367 -0
- package/src/engine/session.ts +302 -60
- package/src/widget.css +8 -0
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 `` 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
|
*
|
|
@@ -1849,6 +1675,356 @@ interface ChatHost {
|
|
|
1849
1675
|
updateComposerControls(): void;
|
|
1850
1676
|
}
|
|
1851
1677
|
|
|
1678
|
+
/**
|
|
1679
|
+
* History mapping (pure). Moved verbatim from the chatbox. The clear-horizon
|
|
1680
|
+
* timestamp and the "Indexing: …" display label are INJECTED (clearedAt param,
|
|
1681
|
+
* formatIndexingLabel callback) so the engine touches neither localStorage nor
|
|
1682
|
+
* view-specific display formatting. projectId is passed for link sanitization.
|
|
1683
|
+
*/
|
|
1684
|
+
|
|
1685
|
+
declare function filterListByClearHorizon(list: any[], clearedAt: number): any[];
|
|
1686
|
+
declare function normalizeTextContent(content: any): string;
|
|
1687
|
+
declare function extractLastUserTextFromRequest(requestBody: any): string;
|
|
1688
|
+
/** The two openings an indexing prompt can have. A bg-queue item that starts with
|
|
1689
|
+
* neither is an ordinary chat that happened to be routed onto that queue. */
|
|
1690
|
+
declare function isIndexingRequestText(userText: any): boolean;
|
|
1691
|
+
type IndexingRequestRef = {
|
|
1692
|
+
name: string;
|
|
1693
|
+
path?: string;
|
|
1694
|
+
mime?: string;
|
|
1695
|
+
size?: number;
|
|
1696
|
+
/** A CONTINUE pass rather than the run's first. */
|
|
1697
|
+
continued: boolean;
|
|
1698
|
+
};
|
|
1699
|
+
/**
|
|
1700
|
+
* The file an indexing prompt is about, read back out of the prompt itself.
|
|
1701
|
+
*
|
|
1702
|
+
* The prompt is the only description of the pass that survives on the server, so
|
|
1703
|
+
* this is how BOTH a history rebuild and a worker-minted pass the client never
|
|
1704
|
+
* dispatched (ChatSession._adoptWorkerIndexingPasses) recover the file. Shared so
|
|
1705
|
+
* the two produce the same `_indexFile`, which is what makes them group together.
|
|
1706
|
+
*/
|
|
1707
|
+
declare function parseIndexingRequestText(userText: any): IndexingRequestRef | null;
|
|
1708
|
+
/**
|
|
1709
|
+
* One bounded look at the background-indexing queue: which files still have a
|
|
1710
|
+
* pass pending or running? This is the same negative signal ChatSession's
|
|
1711
|
+
* display layer relies on - for a worker-driven (auto_continue) run, only the
|
|
1712
|
+
* queue can say the run is over, because the worker enqueues continuation
|
|
1713
|
+
* passes the client never dispatched.
|
|
1714
|
+
*
|
|
1715
|
+
* Returns every storage path AND file name found on live passes (both, because
|
|
1716
|
+
* older prompts may lack the storage-path line), plus `checked`: false when a
|
|
1717
|
+
* page came back full, in which case absence from `keys` proves nothing and
|
|
1718
|
+
* the caller must keep whatever state it already had.
|
|
1719
|
+
*
|
|
1720
|
+
* SCOPE: the probed queue is "<userId>-bg" - THIS user's dispatches only. A
|
|
1721
|
+
* chain launched by another collaborator or a widget end-user lives on their
|
|
1722
|
+
* queue and is invisible here, so "idle" must never be read as "nobody is
|
|
1723
|
+
* indexing this file", only as "this user's runs are over". The durable done::
|
|
1724
|
+
* marker (indexDoneUniqueId) is the cross-user signal.
|
|
1725
|
+
*/
|
|
1726
|
+
declare function fetchLiveIndexingKeys(params: {
|
|
1727
|
+
service: string;
|
|
1728
|
+
owner: string;
|
|
1729
|
+
platform: 'claude' | 'openai';
|
|
1730
|
+
/** Same value the dispatch used - see bgIndexingQueueName. */
|
|
1731
|
+
userId?: string;
|
|
1732
|
+
}): Promise<{
|
|
1733
|
+
keys: Set<string>;
|
|
1734
|
+
checked: boolean;
|
|
1735
|
+
at: number;
|
|
1736
|
+
}>;
|
|
1737
|
+
/** Test hook: drop split-fetch state (all keys, or one). */
|
|
1738
|
+
declare function __resetSplitHistoryState(key?: string): void;
|
|
1739
|
+
type SplitHistoryResult = {
|
|
1740
|
+
list: any[];
|
|
1741
|
+
endOfList: boolean;
|
|
1742
|
+
startKeyHistory: any[];
|
|
1743
|
+
/** True when this chat had never been walked in this session — the first
|
|
1744
|
+
* paint. Consumers gate the "Loading indexing history" hint on it: a
|
|
1745
|
+
* mid-walk tab return restarts the walk for cursor safety but must stay
|
|
1746
|
+
* silent (flashing the hint on every return was the reported bug). */
|
|
1747
|
+
firstLoad?: boolean;
|
|
1748
|
+
/** Present only when `deferBg` was requested AND bg work remains: resolves
|
|
1749
|
+
* with the stub batch fetched in the background (the per-key lock is held
|
|
1750
|
+
* until it settles, so no other history call can interleave). The caller
|
|
1751
|
+
* merges the batch by timestamp — the same path older pages use. */
|
|
1752
|
+
bgPending?: Promise<{
|
|
1753
|
+
list: any[];
|
|
1754
|
+
endOfList: boolean;
|
|
1755
|
+
}>;
|
|
1756
|
+
};
|
|
1757
|
+
declare function getSplitChatHistory(params: {
|
|
1758
|
+
service: string;
|
|
1759
|
+
owner: string;
|
|
1760
|
+
platform: 'claude' | 'openai';
|
|
1761
|
+
userId?: string;
|
|
1762
|
+
}, fetchOptions: Record<string, any>,
|
|
1763
|
+
/** Test seam: replaces getChatHistory. Not for production callers. */
|
|
1764
|
+
_fetchImpl?: typeof getChatHistory): Promise<SplitHistoryResult>;
|
|
1765
|
+
type MapHistoryOptions = {
|
|
1766
|
+
clearedAt: number;
|
|
1767
|
+
projectId: string;
|
|
1768
|
+
/** View-side display formatter for "Indexing:/Reindexing: …" bubbles. */
|
|
1769
|
+
formatIndexingLabel: (name: string, mime?: string, size?: number | null, storagePath?: string, reindex?: boolean, continued?: boolean) => string;
|
|
1770
|
+
};
|
|
1771
|
+
declare function mapHistoryListToMessages(list: any[], platform: 'claude' | 'openai', opts: MapHistoryOptions): {
|
|
1772
|
+
messages: any[];
|
|
1773
|
+
runningItemIds: string[];
|
|
1774
|
+
};
|
|
1775
|
+
interface RescueDecisionContext {
|
|
1776
|
+
/** Is this `_serverItemId` in the page that was just fetched? */
|
|
1777
|
+
hasServerId: (id: string) => boolean;
|
|
1778
|
+
/**
|
|
1779
|
+
* The fetched page already shows a non-background pending assistant.
|
|
1780
|
+
*
|
|
1781
|
+
* Only meaningful for a bubble with NO server id, where it is the sole
|
|
1782
|
+
* available answer to "is this turn already represented?". For a bubble that
|
|
1783
|
+
* HAS one, hasServerId answers exactly the same question exactly, and applying
|
|
1784
|
+
* this on top of it would drop an in-flight turn whose server copy simply is
|
|
1785
|
+
* not in the page that was fetched.
|
|
1786
|
+
*/
|
|
1787
|
+
pageHasPendingAssistant: boolean;
|
|
1788
|
+
/** state.sending: an immediate send is in flight for this chat. */
|
|
1789
|
+
sending: boolean;
|
|
1790
|
+
/** The bubble directly after this one in the local list. */
|
|
1791
|
+
next?: ChatMessage | null;
|
|
1792
|
+
/** The chat this fetch is FOR. A bubble stamped for another must not cross. */
|
|
1793
|
+
loadKey?: string;
|
|
1794
|
+
}
|
|
1795
|
+
declare function shouldRescueInFlightMessage(m: ChatMessage, ctx: RescueDecisionContext): boolean;
|
|
1796
|
+
|
|
1797
|
+
/**
|
|
1798
|
+
* Hold the reader's place in the message list.
|
|
1799
|
+
*
|
|
1800
|
+
* A chat box mutates constantly WITHOUT the reader asking for it: an older page
|
|
1801
|
+
* prepends, a poll resolves, an indexing row splices in or changes label, a link
|
|
1802
|
+
* chip goes grey, an image preview finishes decoding, the "Fetching history..."
|
|
1803
|
+
* bar appears and disappears. Every one of those changes the height of something
|
|
1804
|
+
* that may sit ABOVE the viewport, and the browser answers by keeping scrollTop —
|
|
1805
|
+
* which slides the sentence the user was reading out from under them.
|
|
1806
|
+
*
|
|
1807
|
+
* Both clients had their own copy of a row anchor for the ONE case each could
|
|
1808
|
+
* bracket (agent.vue watched its row-key list, the widget bracketed its full
|
|
1809
|
+
* re-render). Everything else — anything that changed a height without changing
|
|
1810
|
+
* the row SET, and everything asynchronous — was uncovered in both. This is the
|
|
1811
|
+
* single implementation, and it covers both shapes:
|
|
1812
|
+
*
|
|
1813
|
+
* preserve(fn) / capture() + restore(a)
|
|
1814
|
+
* A mutation you can bracket. Measures immediately before and immediately
|
|
1815
|
+
* after, so it is exact even when the mutation tears the list down.
|
|
1816
|
+
*
|
|
1817
|
+
* remember() + hold()
|
|
1818
|
+
* A layout change you CANNOT bracket — an image decoding, a font arriving,
|
|
1819
|
+
* a re-parse triggered from a promise. `remember()` runs from the view's
|
|
1820
|
+
* scroll handler, so the anchor is always the reader's own last position;
|
|
1821
|
+
* `hold()` puts that position back whenever something settles.
|
|
1822
|
+
*
|
|
1823
|
+
* The staleness rule is what makes the unbracketed half safe. A layout change
|
|
1824
|
+
* above the viewport does NOT change scrollTop — the browser preserves it, which
|
|
1825
|
+
* is precisely why the content appears to jump. So a remembered anchor is still
|
|
1826
|
+
* valid exactly while `box.scrollTop` equals the value it was captured at. If it
|
|
1827
|
+
* differs, something moved the box on purpose (the user scrolled, a clamp fired,
|
|
1828
|
+
* or the browser's own scroll anchoring already compensated), and `hold()`
|
|
1829
|
+
* re-captures rather than dragging the reader back to a position they left.
|
|
1830
|
+
*
|
|
1831
|
+
* DOM-free like the rest of the engine: the element shapes below are structural,
|
|
1832
|
+
* so real DOM nodes satisfy them while this file imports nothing from lib.dom.
|
|
1833
|
+
*/
|
|
1834
|
+
interface AnchorRect {
|
|
1835
|
+
top: number;
|
|
1836
|
+
}
|
|
1837
|
+
interface AnchorRowEl {
|
|
1838
|
+
getAttribute(name: string): string | null;
|
|
1839
|
+
getBoundingClientRect(): AnchorRect;
|
|
1840
|
+
offsetHeight: number;
|
|
1841
|
+
parentNode: unknown;
|
|
1842
|
+
}
|
|
1843
|
+
/** Anything inside the list that resizes on its own schedule. See absorb(). */
|
|
1844
|
+
interface AnchorGrowableEl {
|
|
1845
|
+
getBoundingClientRect(): AnchorRect;
|
|
1846
|
+
offsetHeight: number;
|
|
1847
|
+
}
|
|
1848
|
+
interface AnchorBoxEl {
|
|
1849
|
+
children: ArrayLike<AnchorRowEl>;
|
|
1850
|
+
getBoundingClientRect(): AnchorRect;
|
|
1851
|
+
scrollTop: number;
|
|
1852
|
+
scrollHeight: number;
|
|
1853
|
+
clientHeight: number;
|
|
1854
|
+
}
|
|
1855
|
+
interface RowAnchor {
|
|
1856
|
+
/** data-row-key of the anchored row, or null when nothing was anchorable. */
|
|
1857
|
+
key: string | null;
|
|
1858
|
+
/** Offset of that row from the top of the viewport. Negative above the fold. */
|
|
1859
|
+
top: number;
|
|
1860
|
+
/** data-row-pos, present only on rows that can RELOCATE (see below). */
|
|
1861
|
+
pos: string | null;
|
|
1862
|
+
/** scrollTop at capture time. The staleness check, and the raw fallback. */
|
|
1863
|
+
scrollTop: number;
|
|
1864
|
+
/**
|
|
1865
|
+
* scrollHeight at capture time. How much the list GREW is the best available
|
|
1866
|
+
* answer when the anchored row itself cannot be found again, and the bound on
|
|
1867
|
+
* how far a correction can legitimately be.
|
|
1868
|
+
*/
|
|
1869
|
+
scrollHeight: number;
|
|
1870
|
+
/**
|
|
1871
|
+
* The anchored element itself. A view that patches in place (Vue) keeps the
|
|
1872
|
+
* same node across an update, so restore is one rect read instead of a scan;
|
|
1873
|
+
* a view that rebuilds the list (the widget) drops it and falls back to the
|
|
1874
|
+
* key. Never trusted without re-checking that it is still in the box.
|
|
1875
|
+
*/
|
|
1876
|
+
el: AnchorRowEl | null;
|
|
1877
|
+
}
|
|
1878
|
+
interface ScrollAnchorOptions {
|
|
1879
|
+
/** The scrolling message box, or null when it is not mounted. */
|
|
1880
|
+
getBox: () => AnchorBoxEl | null;
|
|
1881
|
+
/**
|
|
1882
|
+
* The reader is pinned to the bottom. There the bottom IS the anchor and the
|
|
1883
|
+
* scrollToBottom* paths own the position, so every method here no-ops.
|
|
1884
|
+
*/
|
|
1885
|
+
isStuck: () => boolean;
|
|
1886
|
+
/**
|
|
1887
|
+
* Fall back to the raw scrollTop when the anchored row cannot be found again.
|
|
1888
|
+
*
|
|
1889
|
+
* For a view that REBUILDS the list (the widget's renderMessages), detaching
|
|
1890
|
+
* every child collapses scrollHeight and the browser clamps scrollTop to 0,
|
|
1891
|
+
* so the raw offset is strictly better than the clamp it would otherwise be
|
|
1892
|
+
* left with. For a view that patches in place (Vue) the browser has already
|
|
1893
|
+
* kept a sane position and re-imposing a stale offset is worse than nothing.
|
|
1894
|
+
*/
|
|
1895
|
+
rawFallback?: boolean;
|
|
1896
|
+
}
|
|
1897
|
+
interface ScrollAnchor {
|
|
1898
|
+
/** Measure the reader's current place. Null while pinned to the bottom. */
|
|
1899
|
+
capture: () => RowAnchor | null;
|
|
1900
|
+
/** Put a captured place back. Safe to call with null. */
|
|
1901
|
+
restore: (anchor: RowAnchor | null) => void;
|
|
1902
|
+
/** capture -> mutate -> restore, for a mutation you can bracket. */
|
|
1903
|
+
preserve: <T>(mutate: () => T) => T;
|
|
1904
|
+
/** Record the reader's place. Call from the box's scroll handler. */
|
|
1905
|
+
remember: () => void;
|
|
1906
|
+
/** Put the remembered place back, if it is still the reader's own. */
|
|
1907
|
+
hold: () => void;
|
|
1908
|
+
/** Absorb one element's own resize. See below. */
|
|
1909
|
+
absorb: (el: AnchorGrowableEl | null | undefined) => void;
|
|
1910
|
+
/** Drop the remembered place (chat switch, unmount). */
|
|
1911
|
+
forget: () => void;
|
|
1912
|
+
}
|
|
1913
|
+
declare function createScrollAnchor(options: ScrollAnchorOptions): ScrollAnchor;
|
|
1914
|
+
|
|
1915
|
+
/**
|
|
1916
|
+
* Keep older history REACHABLE by paging until the message box actually gains
|
|
1917
|
+
* something to scroll to.
|
|
1918
|
+
*
|
|
1919
|
+
* Older history is paged in by one trigger only: the user scrolling to the top
|
|
1920
|
+
* of the message box. That trigger has two ways to die, and collapsed indexing
|
|
1921
|
+
* rows cause both:
|
|
1922
|
+
*
|
|
1923
|
+
* 1. The box never scrolls. A file's every indexing pass (the first plus every
|
|
1924
|
+
* CONTINUE pass, request AND response bubble each) folds into ONE row, so a
|
|
1925
|
+
* full history page — twenty-plus messages — can render as a single line.
|
|
1926
|
+
* Content shorter than the viewport fires no scroll event, so page 2 is
|
|
1927
|
+
* never requested and any conversation the user had before that upload is
|
|
1928
|
+
* permanently out of reach.
|
|
1929
|
+
* 2. The fetched page adds no height. A page that is entirely the same file's
|
|
1930
|
+
* earlier passes joins the collapsed row already on screen and renders
|
|
1931
|
+
* nothing new. The user, sitting at scrollTop 0, scrolls up again — and
|
|
1932
|
+
* because the position never changed, no further scroll event fires.
|
|
1933
|
+
*
|
|
1934
|
+
* Both are the same shape: fetch, re-measure, and keep going until the user
|
|
1935
|
+
* genuinely gained reachable content, history ran out, or the pager stopped
|
|
1936
|
+
* advancing. `isSatisfied` is what differs between the two (can the box scroll
|
|
1937
|
+
* at all / did it grow), so the loop below takes it as a predicate.
|
|
1938
|
+
*
|
|
1939
|
+
* DOM-free like the rest of the engine — the caller supplies the measurement and
|
|
1940
|
+
* awaits its own render before measuring, so agent.vue and the widget run the
|
|
1941
|
+
* identical loop over their own pagers.
|
|
1942
|
+
*/
|
|
1943
|
+
/** Overflow (px) that counts as "the user can scroll here". Comfortably more
|
|
1944
|
+
* than the 60px top threshold that triggers the next page, so a filled box has
|
|
1945
|
+
* real room to scroll rather than sitting one pixel from the trigger. */
|
|
1946
|
+
declare const HISTORY_FILL_SLACK_PX = 64;
|
|
1947
|
+
/** Pages one fill pass will request before giving up. Reached only by a chat
|
|
1948
|
+
* whose history really is dozens of pages of one file's indexing passes; the
|
|
1949
|
+
* cap exists so a pager that stops advancing can never spin forever. */
|
|
1950
|
+
declare const MAX_HISTORY_FILL_PAGES = 24;
|
|
1951
|
+
type FillHistoryViewportOptions = {
|
|
1952
|
+
/** The user has reachable content and paging can stop. Called AFTER the
|
|
1953
|
+
* caller's own render has settled (nextTick / rAF), since only the caller
|
|
1954
|
+
* knows when its view has painted — hence the allowance for a promise. */
|
|
1955
|
+
isSatisfied: () => boolean | Promise<boolean>;
|
|
1956
|
+
/** All history is loaded — nothing left to page in. */
|
|
1957
|
+
isEndOfList: () => boolean;
|
|
1958
|
+
/** A history request is already in flight. Waited out, not treated as a stop
|
|
1959
|
+
* condition: a background first-page refresh (the queue-detect tick fires one
|
|
1960
|
+
* every couple of seconds while a file is indexing) would otherwise swallow
|
|
1961
|
+
* the user's scroll-up entirely, and scrolling up again from scrollTop 0
|
|
1962
|
+
* produces no second event to retry with. */
|
|
1963
|
+
isLoading: () => boolean;
|
|
1964
|
+
/** Messages currently loaded. Used to detect a page that added nothing, which
|
|
1965
|
+
* means the pager is not advancing and looping would never terminate. */
|
|
1966
|
+
messageCount: () => number;
|
|
1967
|
+
/** Fetch ONE older page (the caller's own fetchMore path, scroll-restore and
|
|
1968
|
+
* all). Return `false` when the request was NOT issued (the caller's own
|
|
1969
|
+
* single-flight guard swallowed it) so the loop retries instead of reading
|
|
1970
|
+
* the unchanged message count as an exhausted pager. Anything else, including
|
|
1971
|
+
* undefined, means it was attempted. */
|
|
1972
|
+
fetchOlder: () => Promise<boolean | void | any>;
|
|
1973
|
+
/** The chat this fill was started for is gone (project switched, view
|
|
1974
|
+
* unmounted, gate token bumped). Checked between pages so a stale fill can
|
|
1975
|
+
* never keep paging another chat's history. */
|
|
1976
|
+
isStale?: () => boolean;
|
|
1977
|
+
maxPages?: number;
|
|
1978
|
+
};
|
|
1979
|
+
/**
|
|
1980
|
+
* Page older history until `isSatisfied`, until history runs out, or until the
|
|
1981
|
+
* pager stops advancing. Never throws: a failed page ends the fill, and the
|
|
1982
|
+
* user's own scrolling remains the fallback trigger.
|
|
1983
|
+
*/
|
|
1984
|
+
declare function fillHistoryViewport(opts: FillHistoryViewportOptions): Promise<void>;
|
|
1985
|
+
/**
|
|
1986
|
+
* One fill loop per view, with predicates COMBINED rather than dropped.
|
|
1987
|
+
*
|
|
1988
|
+
* Fills come from several places at once — a first page finishing, a window
|
|
1989
|
+
* resize, a row being collapsed, and the user's own scroll to the top — and a
|
|
1990
|
+
* plain "one at a time, drop the rest" guard picks the wrong winner: a resize
|
|
1991
|
+
* fill (satisfied the moment the box can scroll at all) would swallow the user's
|
|
1992
|
+
* scroll-up (which needs content specifically ABOVE them), and the scroll-up
|
|
1993
|
+
* cannot be retried, because a reader parked at scrollTop 0 produces no further
|
|
1994
|
+
* scroll event. Dropping the guard entirely is no better: every frame of a
|
|
1995
|
+
* window drag would start its own 24-page loop.
|
|
1996
|
+
*
|
|
1997
|
+
* So a request that arrives mid-loop ANDs its predicate into the running one:
|
|
1998
|
+
* the loop then keeps paging until EVERY caller is satisfied. Predicates that
|
|
1999
|
+
* come true are dropped as it goes, so the cost stays flat.
|
|
2000
|
+
*/
|
|
2001
|
+
declare function createHistoryFiller(base: Omit<FillHistoryViewportOptions, 'isSatisfied'> & {
|
|
2002
|
+
/** Fired when the loop starts FETCHING and when it stops, and only on a real
|
|
2003
|
+
* change.
|
|
2004
|
+
*
|
|
2005
|
+
* This — not the caller's own per-request `isLoading` — is what "older
|
|
2006
|
+
* history is still coming in" means to a view. A fill is many pages, and
|
|
2007
|
+
* `isLoading` drops to false between every one of them, so anything
|
|
2008
|
+
* rendered off it flickers once per page for the whole loop. A collapsed
|
|
2009
|
+
* indexing row whose run begins above the loaded window renders exactly
|
|
2010
|
+
* that ("still loading this run" vs a status it cannot know yet), which is
|
|
2011
|
+
* why the loop has to publish its own span.
|
|
2012
|
+
*
|
|
2013
|
+
* Fetching, NOT requested. Most fills fetch nothing: they are fired on every
|
|
2014
|
+
* window resize, every row a user collapses, and every first-page load, and
|
|
2015
|
+
* the overwhelmingly common outcome is `isSatisfied` returning true on the
|
|
2016
|
+
* first look. Announcing at request time published a true/false pair for
|
|
2017
|
+
* each of those, and the widget's own satisfied-check spans two animation
|
|
2018
|
+
* frames — long enough for the browser to PAINT the intermediate state. Every
|
|
2019
|
+
* collapsed row strobed through "loading" on every resize tick. So the span
|
|
2020
|
+
* opens at the first actual page request, which is also the first moment the
|
|
2021
|
+
* claim is true. */
|
|
2022
|
+
onRunningChange?: (running: boolean) => void;
|
|
2023
|
+
}): {
|
|
2024
|
+
fill: (isSatisfied: () => boolean | Promise<boolean>) => Promise<void>;
|
|
2025
|
+
isRunning: () => boolean;
|
|
2026
|
+
};
|
|
2027
|
+
|
|
1852
2028
|
/**
|
|
1853
2029
|
* Background file-indexing turns, collapsed into ONE row per file.
|
|
1854
2030
|
*
|
|
@@ -2419,7 +2595,30 @@ declare class ChatSession {
|
|
|
2419
2595
|
*
|
|
2420
2596
|
* `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
|
|
2421
2597
|
* poll simply cannot be stopped and is left running — see pausePolling.
|
|
2598
|
+
*
|
|
2599
|
+
* (This block documents _trackPoll, further down. The two methods below sit between it
|
|
2600
|
+
* and its subject.)
|
|
2422
2601
|
*/
|
|
2602
|
+
/**
|
|
2603
|
+
* Foreground poll with an early-probe race.
|
|
2604
|
+
*
|
|
2605
|
+
* skapi's poll() is a bare setInterval(fn, latency) with NO check at t=0, so the earliest a
|
|
2606
|
+
* reply can be observed is one full POLL_INTERVAL (3s) after dispatch. For a long generation
|
|
2607
|
+
* that granularity is free. For a SHORT one it is nearly pure dead time: a greeting that the
|
|
2608
|
+
* provider finishes in 1s still waits until the 3s tick, which measured as a large share of a
|
|
2609
|
+
* 5s "yo" round trip.
|
|
2610
|
+
*
|
|
2611
|
+
* So keep the 3s interval as the steady state, and additionally point-look-up the item a few
|
|
2612
|
+
* times early, on a widening schedule. Whichever answers first wins and the other is stopped.
|
|
2613
|
+
* The probe uses the csrHistoryItemLookup hook both clients already implement; without it this
|
|
2614
|
+
* degrades to exactly the old behaviour.
|
|
2615
|
+
*
|
|
2616
|
+
* FOREGROUND ONLY. Background indexing polls keep the flat cadence: nobody is watching them,
|
|
2617
|
+
* and they are the ones bounded by MAX_CONCURRENT_BG_POLLS, so adding probes there would spend
|
|
2618
|
+
* the request budget the cap exists to protect.
|
|
2619
|
+
*/
|
|
2620
|
+
attachForegroundPoll(source: any, itemId: string, opts?: any): any;
|
|
2621
|
+
private _fgPollWithEarlyProbe;
|
|
2423
2622
|
private _trackPoll;
|
|
2424
2623
|
/** Background polls currently attached, for the MAX_CONCURRENT_BG_POLLS budget.
|
|
2425
2624
|
* Counts the registry rather than a separate tally so it cannot drift: every
|
|
@@ -2473,6 +2672,40 @@ declare class ChatSession {
|
|
|
2473
2672
|
* still render) and a later expand retries. */
|
|
2474
2673
|
hydrateCompactItems(itemIds: string[]): Promise<void>;
|
|
2475
2674
|
updateHistoryCache(): void;
|
|
2675
|
+
/**
|
|
2676
|
+
* Give the immediate-send pair the server's id for their turn, the moment the
|
|
2677
|
+
* dispatch learns it.
|
|
2678
|
+
*
|
|
2679
|
+
* WHY THIS EXISTS. An immediate send pushes its user bubble and its
|
|
2680
|
+
* "Thinking..." placeholder locally, and until now neither ever carried a
|
|
2681
|
+
* _serverItemId — only the QUEUED path stamped one, off its ack. So for the
|
|
2682
|
+
* whole life of the turn there was no way to tell the local copy and the
|
|
2683
|
+
* server's copy of the SAME turn apart, and the history merge fell back to a
|
|
2684
|
+
* heuristic: rescue the local pair unless the freshly-fetched page happens to
|
|
2685
|
+
* contain a pending assistant.
|
|
2686
|
+
*
|
|
2687
|
+
* That heuristic has a hole exactly one poll interval wide. The server settles
|
|
2688
|
+
* the request; for up to POLL_INTERVAL the client has not noticed, so
|
|
2689
|
+
* `state.sending` is still true and the local pair is still on screen — while a
|
|
2690
|
+
* history fetch issued in that window returns the turn ALREADY SETTLED, with no
|
|
2691
|
+
* pending assistant in it. The rescue then re-appends the local pair below the
|
|
2692
|
+
* server's copy (the question, twice), and when the poll finally resolves,
|
|
2693
|
+
* typewriteLatestReply writes the answer into the rescued placeholder because it
|
|
2694
|
+
* is the only pending assistant left (the answer, twice). updateHistoryCache
|
|
2695
|
+
* persists the result, so it survives every later visit.
|
|
2696
|
+
*
|
|
2697
|
+
* Navigating away while waiting and coming back is what lands a fetch in that
|
|
2698
|
+
* window: a remount runs refreshGate -> a fresh first page, at an arbitrary
|
|
2699
|
+
* moment relative to the 3s poll.
|
|
2700
|
+
*
|
|
2701
|
+
* With the id on the bubbles, both clients' rescue loops skip them through the
|
|
2702
|
+
* dedup they already have (`_serverItemId is in this page`), the reply the
|
|
2703
|
+
* dispatch caches inherits the id too, and nothing needs a new special case.
|
|
2704
|
+
*
|
|
2705
|
+
* Matched by _localId, never by index: a file's indexing rows are spliced in
|
|
2706
|
+
* above these bubbles while the request is in flight.
|
|
2707
|
+
*/
|
|
2708
|
+
private _stampTurnWithItemId;
|
|
2476
2709
|
/**
|
|
2477
2710
|
* Land a resolved reply in the history cache of a chat that is NOT currently
|
|
2478
2711
|
* visible, without touching state.messages. Mirrors the cache-only path in
|
|
@@ -2575,6 +2808,25 @@ declare class ChatSession {
|
|
|
2575
2808
|
*/
|
|
2576
2809
|
settleStagedMessage(stageId: string): void;
|
|
2577
2810
|
dispatchComposedMessage(composed: string, useBgQueue?: boolean, composedForLlm?: string, extractContent?: any, fileUrls?: any, pinned?: PinnedDispatchContext): void;
|
|
2811
|
+
/**
|
|
2812
|
+
* Scroll for a dispatch that is going out NOW, but never for one arriving late.
|
|
2813
|
+
*
|
|
2814
|
+
* A turn with attachments does not dispatch when the user hits Send: it waits out
|
|
2815
|
+
* its uploads and then its whole indexing chain, which is minutes
|
|
2816
|
+
* (awaitIndexingDrained). By then the reader has very often scrolled up into
|
|
2817
|
+
* history to pass the time, and the forcing scrollToBottom yanked them out of it
|
|
2818
|
+
* — and worse, force-pinned stickToBottom, which no-ops every method on the
|
|
2819
|
+
* scroll anchor and re-arms the queue-detect poll whose only bail is
|
|
2820
|
+
* !stickToBottom.
|
|
2821
|
+
*
|
|
2822
|
+
* `stageId` is the exact marker for that case: only the attachment path ever
|
|
2823
|
+
* produces one. The gesture itself was already paid for at stage time, where
|
|
2824
|
+
* stageOutgoingMessage forces the scroll while the user is still looking at the
|
|
2825
|
+
* composer. Deliberately NOT gated on "did we find the staged bubble" — a remount
|
|
2826
|
+
* rebuilds from the cache and the turn is appended rather than replaced, which is
|
|
2827
|
+
* just as late and just as unrequested.
|
|
2828
|
+
*/
|
|
2829
|
+
private scrollForDispatch;
|
|
2578
2830
|
promoteNextBgQueuedToRunning(): void;
|
|
2579
2831
|
promoteNextQueuedToRunning(): void;
|
|
2580
2832
|
/**
|
|
@@ -2593,6 +2845,24 @@ declare class ChatSession {
|
|
|
2593
2845
|
private _ownThinkingIndex;
|
|
2594
2846
|
resolveQueuedUserBubble(serverId?: string): number | undefined;
|
|
2595
2847
|
insertAtTarget(msg: ChatMessage, targetIdx: number): void;
|
|
2848
|
+
/**
|
|
2849
|
+
* The server's OWN copy of this turn is already on screen.
|
|
2850
|
+
*
|
|
2851
|
+
* A first-page fetch can land between the server settling the item and this
|
|
2852
|
+
* poll's tick, and now that the local bubbles carry the item id
|
|
2853
|
+
* (_stampTurnWithItemId) the rescue correctly drops them and renders the
|
|
2854
|
+
* server's settled pair instead. There is then nothing left to resolve: the
|
|
2855
|
+
* -1 fallback would push the answer in a SECOND time at the bottom of the list,
|
|
2856
|
+
* and the positional fallbacks would hijack some other turn's bubble.
|
|
2857
|
+
*
|
|
2858
|
+
* The USER bubble counts, not just a settled assistant: an item whose answer is
|
|
2859
|
+
* empty produces no assistant bubble at all in the mapper, and that variant
|
|
2860
|
+
* would otherwise still bottom-push "No text response received...". While a turn
|
|
2861
|
+
* is genuinely live its user bubble is always pending (the queued branch sets
|
|
2862
|
+
* isPendingQueued, promoteNextQueuedToRunning sets isPendingInProcess), so this
|
|
2863
|
+
* cannot fire early.
|
|
2864
|
+
*/
|
|
2865
|
+
private _turnAlreadyRendered;
|
|
2596
2866
|
onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string, ownerKey?: string): void;
|
|
2597
2867
|
onQueuedSendError(_composed: string, err: any, serverId?: string, ownerKey?: string): void;
|
|
2598
2868
|
cancelQueuedMessage(msg: ChatMessage, idx: number): void;
|
|
@@ -2795,4 +3065,4 @@ declare class ChatSession {
|
|
|
2795
3065
|
bumpGate(): void;
|
|
2796
3066
|
}
|
|
2797
3067
|
|
|
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 };
|
|
3068
|
+
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 };
|