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.mjs CHANGED
@@ -899,18 +899,31 @@ function classifyInlineLink(full, groups, ctx) {
899
899
  part: { type: "link", label: truncateLabelForDisplay(urlLabel), fullLabel: urlLabel, href: originalHref, expired: false }
900
900
  });
901
901
  }
902
+ function canonicalizePathForm(value) {
903
+ if (!value) return value;
904
+ try {
905
+ return value.normalize("NFC");
906
+ } catch (e) {
907
+ return value;
908
+ }
909
+ }
902
910
  function linkUnavailableKeyForPath(remotePath) {
903
- return "path:" + (remotePath || "");
911
+ return "path:" + canonicalizePathForm(remotePath || "");
904
912
  }
905
913
  function linkUnavailableKeyForHref(href) {
906
- return "href:" + (href || "");
914
+ var carried = readExpiredAttachmentHref(href);
915
+ if (carried) return linkUnavailableKeyForPath(carried);
916
+ return "href:" + canonicalizePathForm(href || "");
907
917
  }
908
918
  function linkUnavailableKeysForPath(remotePath) {
909
919
  if (!remotePath) return [];
910
- return [
920
+ var keys = [
911
921
  linkUnavailableKeyForPath(remotePath),
912
922
  linkUnavailableKeyForHref(buildDisplayExpiredAttachmentHref(remotePath))
913
923
  ];
924
+ return keys.filter(function(k, i) {
925
+ return keys.indexOf(k) === i;
926
+ });
914
927
  }
915
928
  function isLinkUnavailable(link, map) {
916
929
  if (!link || !map) return false;
@@ -1311,7 +1324,7 @@ function renderInlineLinkHtml(link, opts) {
1311
1324
  if (link.remotePath) attrs.push('data-bq-remote-path="' + escapeInlineHtml(link.remotePath) + '"');
1312
1325
  if (link.fullLabel) attrs.push('data-bq-full-label="' + escapeInlineHtml(link.fullLabel) + '"');
1313
1326
  if (!preview) return "<a " + attrs.join(" ") + ">" + escapeInlineHtml(labelText) + "</a>";
1314
- return "<a " + attrs.join(" ") + '><img class="bq-img-preview" alt="' + escapeInlineHtml(full) + '" data-bq-img-path="' + escapeInlineHtml(link.remotePath || "") + '" data-bq-img-type="' + escapeInlineHtml(link.image ? link.image.contentType : "") + '" loading="lazy" decoding="async"><span class="bq-loader" data-bq-img-loader="1"></span><span class="bq-img-preview-caption" translate="no">' + escapeInlineHtml(labelText) + "</span></a>";
1327
+ return "<a " + attrs.join(" ") + '><img class="bq-img-preview" alt="' + escapeInlineHtml(full) + '" data-bq-img-path="' + escapeInlineHtml(link.remotePath || "") + '" data-bq-img-type="' + escapeInlineHtml(link.image ? link.image.contentType : "") + '" decoding="async"><span class="bq-loader" data-bq-img-loader="1"></span><span class="bq-img-preview-caption" translate="no">' + escapeInlineHtml(labelText) + "</span></a>";
1315
1328
  }
1316
1329
 
1317
1330
  // src/engine/image_preview.ts
@@ -1397,31 +1410,48 @@ function hydrateOne(img, ctx) {
1397
1410
  var warm = peekImagePreviewUrl(ctx, path);
1398
1411
  if (warm) {
1399
1412
  img.setAttribute("src", warm);
1413
+ notifyLayoutChange(img, ctx, path);
1400
1414
  return;
1401
1415
  }
1402
1416
  resolveImagePreviewUrl(ctx, path, type).then(function(url) {
1403
1417
  if (img.getAttribute("data-bq-img-state") !== "loading") return;
1404
1418
  img.setAttribute("src", url);
1419
+ notifyLayoutChange(img, ctx, path);
1405
1420
  }, function(e) {
1406
1421
  img.setAttribute("data-bq-img-state", "error");
1422
+ notifyLayoutChange(img, ctx, path);
1407
1423
  if (ctx.onError) ctx.onError(path, e);
1408
1424
  });
1409
1425
  }
1410
1426
  function onImageError(img, ctx, path, type) {
1411
1427
  if (img.getAttribute("data-bq-img-retry") === "1") {
1412
1428
  img.setAttribute("data-bq-img-state", "error");
1429
+ notifyLayoutChange(img, ctx, path);
1413
1430
  if (ctx.onError) ctx.onError(path, new Error("image preview failed to load"));
1414
1431
  return;
1415
1432
  }
1416
1433
  img.setAttribute("data-bq-img-retry", "1");
1417
1434
  img.removeAttribute("src");
1435
+ notifyLayoutChange(img, ctx, path);
1418
1436
  resolveImagePreviewUrl(ctx, path, type, true).then(function(url) {
1419
1437
  img.setAttribute("src", url);
1438
+ notifyLayoutChange(img, ctx, path);
1420
1439
  }, function(e) {
1421
1440
  img.setAttribute("data-bq-img-state", "error");
1441
+ notifyLayoutChange(img, ctx, path);
1422
1442
  if (ctx.onError) ctx.onError(path, e);
1423
1443
  });
1424
1444
  }
1445
+ function notifyLayoutChange(img, ctx, path) {
1446
+ if (!ctx.onLayoutChange) return;
1447
+ ctx.onLayoutChange(previewLayoutBox(img), path);
1448
+ }
1449
+ var PREVIEW_LAYOUT_BOX_SELECTOR = "a.bq-link-button.is-image-preview";
1450
+ function previewLayoutBox(img) {
1451
+ var closest = img.closest;
1452
+ if (typeof closest !== "function") return img;
1453
+ return closest.call(img, PREVIEW_LAYOUT_BOX_SELECTOR) || img;
1454
+ }
1425
1455
 
1426
1456
  // src/engine/time.ts
1427
1457
  function wallClockNow() {
@@ -2529,6 +2559,327 @@ function mapHistoryListToMessages(list, platform, opts) {
2529
2559
  }
2530
2560
  return { messages: mapped, runningItemIds };
2531
2561
  }
2562
+ function shouldRescueInFlightMessage(m, ctx) {
2563
+ if (!m) return false;
2564
+ if (m.isBackgroundTask) return false;
2565
+ if (m._ownerKey !== void 0 && ctx.loadKey !== void 0 && m._ownerKey !== ctx.loadKey) return false;
2566
+ if (m._serverItemId && ctx.hasServerId(m._serverItemId)) return false;
2567
+ if (m._stageId) return true;
2568
+ if (!m._serverItemId && ctx.pageHasPendingAssistant) return false;
2569
+ if (m.isSendingToServer || m.isPendingQueued || m.isPendingInProcess || m.isPending) return true;
2570
+ if (ctx.sending && m.role === "user") {
2571
+ var next = ctx.next;
2572
+ if (!next || next.isBackgroundTask || !next.isPending) return false;
2573
+ return next._serverItemId === void 0 || next._serverItemId === m._serverItemId;
2574
+ }
2575
+ return false;
2576
+ }
2577
+
2578
+ // src/engine/scroll_anchor.ts
2579
+ var ROW_KEY_ATTR = "data-row-key";
2580
+ var ROW_POS_ATTR = "data-row-pos";
2581
+ var MAX_ALTS = 2;
2582
+ var ALT_SCAN_LIMIT = 64;
2583
+ var UNKNOWN_ROW_POS = "\0?";
2584
+ function createScrollAnchor(options) {
2585
+ var held = null;
2586
+ var seen = typeof WeakMap === "function" ? /* @__PURE__ */ new WeakMap() : null;
2587
+ function capture() {
2588
+ var box = options.getBox();
2589
+ if (!box || options.isStuck()) return null;
2590
+ var boxTop = box.getBoundingClientRect().top;
2591
+ var kids = box.children;
2592
+ var fallback = null;
2593
+ var fallbackAt = -1;
2594
+ for (var i = 0; i < kids.length; i++) {
2595
+ var el = kids[i];
2596
+ if (!el || typeof el.getAttribute !== "function") continue;
2597
+ var key = el.getAttribute(ROW_KEY_ATTR);
2598
+ if (!key) continue;
2599
+ var top = el.getBoundingClientRect().top - boxTop;
2600
+ if (top + el.offsetHeight <= 0) continue;
2601
+ if (top >= box.clientHeight) break;
2602
+ var rawPos = el.getAttribute(ROW_POS_ATTR);
2603
+ var pos = rawPos === null ? null : rawPos || UNKNOWN_ROW_POS;
2604
+ var cand = {
2605
+ key,
2606
+ top,
2607
+ pos,
2608
+ scrollTop: box.scrollTop,
2609
+ scrollHeight: box.scrollHeight,
2610
+ el
2611
+ };
2612
+ if (rawPos === null) {
2613
+ cand.alts = collectAlts(box, boxTop, i + 1);
2614
+ return cand;
2615
+ }
2616
+ if (!fallback) {
2617
+ fallback = cand;
2618
+ fallbackAt = i;
2619
+ }
2620
+ }
2621
+ if (fallback) {
2622
+ fallback.alts = collectAlts(box, boxTop, fallbackAt + 1, true);
2623
+ return fallback;
2624
+ }
2625
+ return {
2626
+ key: null,
2627
+ top: 0,
2628
+ pos: null,
2629
+ scrollTop: box.scrollTop,
2630
+ scrollHeight: box.scrollHeight,
2631
+ el: null
2632
+ };
2633
+ }
2634
+ function collectAlts(box, boxTop, from, ordinaryOnly) {
2635
+ var out = [];
2636
+ var kids = box.children;
2637
+ var stop = Math.min(kids.length, from + ALT_SCAN_LIMIT);
2638
+ for (var i = from; i < stop && out.length < MAX_ALTS; i++) {
2639
+ var el = kids[i];
2640
+ if (!el || typeof el.getAttribute !== "function") continue;
2641
+ var key = el.getAttribute(ROW_KEY_ATTR);
2642
+ if (!key) continue;
2643
+ var rawPos = el.getAttribute(ROW_POS_ATTR);
2644
+ if (ordinaryOnly && rawPos !== null) continue;
2645
+ out.push({
2646
+ key,
2647
+ top: el.getBoundingClientRect().top - boxTop,
2648
+ pos: rawPos === null ? null : rawPos || UNKNOWN_ROW_POS,
2649
+ el
2650
+ });
2651
+ }
2652
+ return out.length ? out : void 0;
2653
+ }
2654
+ function findRow(box, anchor) {
2655
+ var el = anchor.el;
2656
+ if (el && el.parentNode === box) return el;
2657
+ if (!anchor.key) return null;
2658
+ var kids = box.children;
2659
+ for (var i = 0; i < kids.length; i++) {
2660
+ var kid = kids[i];
2661
+ if (!kid || typeof kid.getAttribute !== "function") continue;
2662
+ if (kid.getAttribute(ROW_KEY_ATTR) === anchor.key) return kid;
2663
+ }
2664
+ return null;
2665
+ }
2666
+ var sawFrozen = false;
2667
+ var parked = null;
2668
+ var parkedStuck = false;
2669
+ var returning = false;
2670
+ var wroteTop = -1;
2671
+ var restoreExact = true;
2672
+ var restorePinned = false;
2673
+ function frozen() {
2674
+ var f = !!options.isFrozen && options.isFrozen();
2675
+ if (f) sawFrozen = true;
2676
+ return f;
2677
+ }
2678
+ function restore(anchor, unbounded) {
2679
+ if (frozen()) return;
2680
+ var box = options.getBox();
2681
+ if (!box || !anchor || options.isStuck()) return;
2682
+ var el = findRow(box, anchor);
2683
+ if (el) {
2684
+ var livePos = el.getAttribute(ROW_POS_ATTR) || UNKNOWN_ROW_POS;
2685
+ if (anchor.pos !== null && anchor.pos !== UNKNOWN_ROW_POS && livePos !== UNKNOWN_ROW_POS && livePos !== anchor.pos) el = null;
2686
+ }
2687
+ if (el) {
2688
+ var boxTop = box.getBoundingClientRect().top;
2689
+ var delta = el.getBoundingClientRect().top - boxTop - anchor.top;
2690
+ var slack = Math.abs(box.scrollHeight - anchor.scrollHeight) + box.clientHeight;
2691
+ if (!unbounded && (delta > slack || delta < -slack)) {
2692
+ lost(box, anchor);
2693
+ return;
2694
+ }
2695
+ var want = box.scrollTop + delta;
2696
+ if (delta >= 1 || delta <= -1) box.scrollTop += delta;
2697
+ wroteTop = box.scrollTop;
2698
+ restorePinned = true;
2699
+ restoreExact = box.scrollTop >= want - 1 && box.scrollTop <= want + 1;
2700
+ held = {
2701
+ key: anchor.key,
2702
+ top: anchor.top,
2703
+ pos: anchor.pos,
2704
+ scrollTop: box.scrollTop,
2705
+ scrollHeight: box.scrollHeight,
2706
+ el,
2707
+ // Carried, not dropped: one successful restore used to disarm the
2708
+ // standbys for every later hold.
2709
+ alts: anchor.alts
2710
+ };
2711
+ return;
2712
+ }
2713
+ var alts = anchor.alts;
2714
+ for (var ai = 0; alts && ai < alts.length; ai++) {
2715
+ var alt = alts[ai];
2716
+ var ael = findRow(box, { key: alt.key, top: alt.top, pos: alt.pos, scrollTop: anchor.scrollTop, scrollHeight: anchor.scrollHeight, el: alt.el });
2717
+ if (!ael) continue;
2718
+ if (alt.pos !== null && alt.pos !== UNKNOWN_ROW_POS) {
2719
+ var altLive = ael.getAttribute(ROW_POS_ATTR) || UNKNOWN_ROW_POS;
2720
+ if (altLive !== UNKNOWN_ROW_POS && altLive !== alt.pos) continue;
2721
+ }
2722
+ var aboxTop = box.getBoundingClientRect().top;
2723
+ var adelta = ael.getBoundingClientRect().top - aboxTop - alt.top;
2724
+ var aslack = Math.abs(box.scrollHeight - anchor.scrollHeight) + box.clientHeight;
2725
+ if (!unbounded && (adelta > aslack || adelta < -aslack)) continue;
2726
+ var awant = box.scrollTop + adelta;
2727
+ if (adelta >= 1 || adelta <= -1) box.scrollTop += adelta;
2728
+ wroteTop = box.scrollTop;
2729
+ restorePinned = true;
2730
+ restoreExact = box.scrollTop >= awant - 1 && box.scrollTop <= awant + 1;
2731
+ held = {
2732
+ key: alt.key,
2733
+ top: alt.top,
2734
+ pos: alt.pos,
2735
+ scrollTop: box.scrollTop,
2736
+ scrollHeight: box.scrollHeight,
2737
+ el: ael,
2738
+ alts: alts.slice(ai + 1)
2739
+ };
2740
+ return;
2741
+ }
2742
+ lost(box, anchor);
2743
+ }
2744
+ function lost(box, anchor) {
2745
+ held = null;
2746
+ var grew = box.scrollHeight - anchor.scrollHeight;
2747
+ if (grew > 0) {
2748
+ box.scrollTop = anchor.scrollTop + grew;
2749
+ wroteTop = box.scrollTop;
2750
+ return;
2751
+ }
2752
+ if (options.rawFallback) {
2753
+ box.scrollTop = anchor.scrollTop;
2754
+ wroteTop = box.scrollTop;
2755
+ }
2756
+ }
2757
+ function preserve(mutate) {
2758
+ var anchor = capture();
2759
+ var result = mutate();
2760
+ restore(anchor);
2761
+ return result;
2762
+ }
2763
+ function remember() {
2764
+ var b0 = options.getBox();
2765
+ if (returning && b0 && b0.scrollTop !== wroteTop) {
2766
+ parked = null;
2767
+ parkedStuck = false;
2768
+ returning = false;
2769
+ }
2770
+ if (frozen()) return;
2771
+ held = capture();
2772
+ }
2773
+ function hold() {
2774
+ if (frozen()) return;
2775
+ if (sawFrozen || returning) {
2776
+ settleReturn();
2777
+ return;
2778
+ }
2779
+ var box = options.getBox();
2780
+ if (!box || options.isStuck()) {
2781
+ held = null;
2782
+ return;
2783
+ }
2784
+ if (!held) {
2785
+ held = capture();
2786
+ return;
2787
+ }
2788
+ if (box.scrollTop !== held.scrollTop) {
2789
+ held = capture();
2790
+ return;
2791
+ }
2792
+ restore(held);
2793
+ }
2794
+ function absorb(el) {
2795
+ if (!el) return;
2796
+ var box = options.getBox();
2797
+ if (!box) return;
2798
+ var h = el.offsetHeight;
2799
+ var prev = seen ? seen.get(el) : void 0;
2800
+ if (seen) seen.set(el, h);
2801
+ if (options.isStuck()) return;
2802
+ if (prev === void 0) prev = 0;
2803
+ var delta = h - prev;
2804
+ if (delta === 0) return;
2805
+ if (frozen()) {
2806
+ foldFrozenGrowth(box, el, delta);
2807
+ return;
2808
+ }
2809
+ if (el.getBoundingClientRect().top >= box.getBoundingClientRect().top) return;
2810
+ box.scrollTop += delta;
2811
+ wroteTop = box.scrollTop;
2812
+ if (held) held = capture();
2813
+ }
2814
+ function park() {
2815
+ parked = capture();
2816
+ parkedStuck = !!options.isStuck();
2817
+ returning = true;
2818
+ }
2819
+ function pinBottom() {
2820
+ var box = options.getBox();
2821
+ if (!box) return;
2822
+ box.scrollTop = box.scrollHeight;
2823
+ wroteTop = box.scrollTop;
2824
+ }
2825
+ function settleReturn() {
2826
+ if (frozen()) return false;
2827
+ sawFrozen = false;
2828
+ if (parkedStuck) {
2829
+ pinBottom();
2830
+ return true;
2831
+ }
2832
+ var target = parked || held;
2833
+ restoreExact = true;
2834
+ restorePinned = false;
2835
+ restore(target, true);
2836
+ parked = !restorePinned || !restoreExact ? target : null;
2837
+ returning = !!parked;
2838
+ return false;
2839
+ }
2840
+ function isReturning() {
2841
+ return returning;
2842
+ }
2843
+ function thaw() {
2844
+ settleReturn();
2845
+ }
2846
+ function foldFrozenGrowth(box, el, delta) {
2847
+ var elTop = el.getBoundingClientRect().top;
2848
+ foldInto(box, held, elTop, delta);
2849
+ foldInto(box, parked, elTop, delta);
2850
+ }
2851
+ function foldInto(box, a, elTop, delta) {
2852
+ if (!a || a.top >= 0) return;
2853
+ var rowEl = findRow(box, a);
2854
+ if (!rowEl) return;
2855
+ var within = elTop - rowEl.getBoundingClientRect().top;
2856
+ if (within < 0 || within >= rowEl.offsetHeight) return;
2857
+ if (within >= -a.top) return;
2858
+ a.top -= delta;
2859
+ a.scrollHeight += delta;
2860
+ }
2861
+ function forget() {
2862
+ held = null;
2863
+ parked = null;
2864
+ parkedStuck = false;
2865
+ returning = false;
2866
+ }
2867
+ return {
2868
+ capture,
2869
+ restore,
2870
+ preserve,
2871
+ remember,
2872
+ hold,
2873
+ park,
2874
+ settleReturn,
2875
+ isReturning,
2876
+ pinBottom,
2877
+ isFrozen: frozen,
2878
+ thaw,
2879
+ absorb,
2880
+ forget
2881
+ };
2882
+ }
2532
2883
 
2533
2884
  // src/engine/viewport_fill.ts
2534
2885
  var HISTORY_FILL_SLACK_PX = 64;
@@ -2640,6 +2991,7 @@ var WORKER_PASS_ADOPT_LIMIT = 20;
2640
2991
  var LIVE_INDEX_SNAPSHOT_MAX_AGE_MS = 5e3;
2641
2992
  var INDEX_DISPATCH_CLAIM_MS = 2 * 60 * 1e3;
2642
2993
  var WORKER_PASS_ADOPT_ATTEMPTS = [0, 2e3, 6e3];
2994
+ var EARLY_PROBE_SCHEDULE_MS = [400, 900, 1700];
2643
2995
  var INDEXING_DRAIN_BUSY_POLL_MS = 8e3;
2644
2996
  var INDEXING_DRAIN_CONFIRM_POLL_MS = 3e3;
2645
2997
  var INDEXING_DRAIN_IDLE_LOOKS = 2;
@@ -3035,7 +3387,93 @@ var ChatSession = class {
3035
3387
  *
3036
3388
  * `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
3037
3389
  * poll simply cannot be stopped and is left running — see pausePolling.
3390
+ *
3391
+ * (This block documents _trackPoll, further down. The two methods below sit between it
3392
+ * and its subject.)
3038
3393
  */
3394
+ /**
3395
+ * Foreground poll with an early-probe race.
3396
+ *
3397
+ * skapi's poll() is a bare setInterval(fn, latency) with NO check at t=0, so the earliest a
3398
+ * reply can be observed is one full POLL_INTERVAL (3s) after dispatch. For a long generation
3399
+ * that granularity is free. For a SHORT one it is nearly pure dead time: a greeting that the
3400
+ * provider finishes in 1s still waits until the 3s tick, which measured as a large share of a
3401
+ * 5s "yo" round trip.
3402
+ *
3403
+ * So keep the 3s interval as the steady state, and additionally point-look-up the item a few
3404
+ * times early, on a widening schedule. Whichever answers first wins and the other is stopped.
3405
+ * The probe uses the csrHistoryItemLookup hook both clients already implement; without it this
3406
+ * degrades to exactly the old behaviour.
3407
+ *
3408
+ * FOREGROUND ONLY. Background indexing polls keep the flat cadence: nobody is watching them,
3409
+ * and they are the ones bounded by MAX_CONCURRENT_BG_POLLS, so adding probes there would spend
3410
+ * the request budget the cap exists to protect.
3411
+ */
3412
+ attachForegroundPoll(source, itemId, opts) {
3413
+ return this._fgPollWithEarlyProbe(source, itemId, opts);
3414
+ }
3415
+ _fgPollWithEarlyProbe(source, itemId, opts) {
3416
+ var base = source.poll(Object.assign({ latency: POLL_INTERVAL }, opts || {}));
3417
+ var lookup = chatEngineConfig().csrHistoryItemLookup;
3418
+ var ident = this.host.getIdentity();
3419
+ var platform = ident && ident.platform;
3420
+ if (!lookup || !itemId || !ident || !ident.projectId || platform !== "claude" && platform !== "openai") {
3421
+ return base;
3422
+ }
3423
+ var settled = false;
3424
+ var timers = [];
3425
+ var clearProbes = function() {
3426
+ for (var i = 0; i < timers.length; i++) clearTimeout(timers[i]);
3427
+ timers = [];
3428
+ };
3429
+ var stopBase = base && typeof base.stop === "function" ? base.stop.bind(base) : null;
3430
+ var raced = new Promise(function(resolve, reject) {
3431
+ base.then(function(res) {
3432
+ if (settled) return;
3433
+ settled = true;
3434
+ clearProbes();
3435
+ resolve(res);
3436
+ }, function(err) {
3437
+ if (settled) return;
3438
+ settled = true;
3439
+ clearProbes();
3440
+ reject(err);
3441
+ });
3442
+ var fullId = buildHistoryItemFullId(platform, ident.projectId, itemId);
3443
+ EARLY_PROBE_SCHEDULE_MS.forEach(function(delay) {
3444
+ timers.push(setTimeout(function() {
3445
+ if (settled) return;
3446
+ Promise.resolve(lookup(fullId, ident.projectId, ident.owner)).then(function(body) {
3447
+ if (settled || !body || typeof body !== "object") return;
3448
+ if (body.status === "pending" || body.status === "running") return;
3449
+ if (isPollStopped(body)) return;
3450
+ settled = true;
3451
+ clearProbes();
3452
+ if (stopBase) {
3453
+ try {
3454
+ stopBase();
3455
+ } catch (e) {
3456
+ }
3457
+ }
3458
+ if (opts && typeof opts.onResponse === "function") {
3459
+ try {
3460
+ opts.onResponse(body);
3461
+ } catch (e) {
3462
+ }
3463
+ }
3464
+ resolve(body);
3465
+ }, function() {
3466
+ });
3467
+ }, delay));
3468
+ });
3469
+ });
3470
+ raced.stop = function() {
3471
+ settled = true;
3472
+ clearProbes();
3473
+ if (stopBase) stopBase();
3474
+ };
3475
+ return raced;
3476
+ }
3039
3477
  _trackPoll(id, kind, p) {
3040
3478
  var stop = p && typeof p.stop === "function" ? p.stop.bind(p) : void 0;
3041
3479
  if (!stop) {
@@ -3211,6 +3649,68 @@ var ChatSession = class {
3211
3649
  startKeyHistory: this.state.historyStartKeyHistory.slice()
3212
3650
  };
3213
3651
  }
3652
+ /**
3653
+ * Give the immediate-send pair the server's id for their turn, the moment the
3654
+ * dispatch learns it.
3655
+ *
3656
+ * WHY THIS EXISTS. An immediate send pushes its user bubble and its
3657
+ * "Thinking..." placeholder locally, and until now neither ever carried a
3658
+ * _serverItemId — only the QUEUED path stamped one, off its ack. So for the
3659
+ * whole life of the turn there was no way to tell the local copy and the
3660
+ * server's copy of the SAME turn apart, and the history merge fell back to a
3661
+ * heuristic: rescue the local pair unless the freshly-fetched page happens to
3662
+ * contain a pending assistant.
3663
+ *
3664
+ * That heuristic has a hole exactly one poll interval wide. The server settles
3665
+ * the request; for up to POLL_INTERVAL the client has not noticed, so
3666
+ * `state.sending` is still true and the local pair is still on screen — while a
3667
+ * history fetch issued in that window returns the turn ALREADY SETTLED, with no
3668
+ * pending assistant in it. The rescue then re-appends the local pair below the
3669
+ * server's copy (the question, twice), and when the poll finally resolves,
3670
+ * typewriteLatestReply writes the answer into the rescued placeholder because it
3671
+ * is the only pending assistant left (the answer, twice). updateHistoryCache
3672
+ * persists the result, so it survives every later visit.
3673
+ *
3674
+ * Navigating away while waiting and coming back is what lands a fetch in that
3675
+ * window: a remount runs refreshGate -> a fresh first page, at an arbitrary
3676
+ * moment relative to the 3s poll.
3677
+ *
3678
+ * With the id on the bubbles, both clients' rescue loops skip them through the
3679
+ * dedup they already have (`_serverItemId is in this page`), the reply the
3680
+ * dispatch caches inherits the id too, and nothing needs a new special case.
3681
+ *
3682
+ * Matched by _localId, never by index: a file's indexing rows are spliced in
3683
+ * above these bubbles while the request is in flight.
3684
+ */
3685
+ _stampTurnWithItemId(key, userLid, placeholderLid, itemId) {
3686
+ if (!itemId) return;
3687
+ var changed = false;
3688
+ var stamp = function(list) {
3689
+ var hit = false;
3690
+ for (var i = 0; i < list.length; i++) {
3691
+ var m = list[i];
3692
+ if (!m || !m._localId) continue;
3693
+ if (m._localId !== userLid && m._localId !== placeholderLid) continue;
3694
+ if (m._serverItemId === itemId) continue;
3695
+ list[i] = Object.assign({}, m, { _serverItemId: itemId });
3696
+ hit = true;
3697
+ }
3698
+ return hit;
3699
+ };
3700
+ changed = stamp(this.state.messages);
3701
+ var cached = key ? this.aiChatHistoryCache[key] : void 0;
3702
+ if (cached) {
3703
+ var msgs = cached.messages.slice();
3704
+ if (stamp(msgs)) {
3705
+ this.aiChatHistoryCache[key] = {
3706
+ messages: msgs,
3707
+ endOfList: cached.endOfList,
3708
+ startKeyHistory: cached.startKeyHistory
3709
+ };
3710
+ }
3711
+ }
3712
+ if (changed) this.host.notify();
3713
+ }
3214
3714
  /**
3215
3715
  * Land a resolved reply in the history cache of a chat that is NOT currently
3216
3716
  * visible, without touching state.messages. Mirrors the cache-only path in
@@ -3292,8 +3792,9 @@ var ChatSession = class {
3292
3792
  if (initial.id) {
3293
3793
  if (dispatchItemId && dispatchItemId !== initial.id) self.historyItemPolls.delete(dispatchItemId);
3294
3794
  dispatchItemId = initial.id;
3795
+ if (typeof params.onItemId === "function") params.onItemId(initial.id);
3295
3796
  }
3296
- var dp = initial.poll({ latency: POLL_INTERVAL });
3797
+ var dp = self._fgPollWithEarlyProbe(initial, initial.id);
3297
3798
  if (initial.id) self._trackPoll(initial.id, "fg", dp);
3298
3799
  return dp;
3299
3800
  }
@@ -3318,28 +3819,9 @@ var ChatSession = class {
3318
3819
  }).then(function(result) {
3319
3820
  delete self.pendingAgentRequests[params.key];
3320
3821
  if (dispatchItemId) self.historyItemPolls.delete(dispatchItemId);
3321
- var existing = self.aiChatHistoryCache[params.key] || { messages: [], endOfList: false, startKeyHistory: [] };
3322
3822
  var reply = { role: "assistant", content: result.content, isError: result.isError };
3323
- var msgs = existing.messages.slice();
3324
- var idx = -1;
3325
- for (var i = msgs.length - 1; i >= 0; i--) {
3326
- var m = msgs[i];
3327
- if (m && m.isPending && m.role === "assistant" && !m.isBackgroundTask) {
3328
- idx = i;
3329
- break;
3330
- }
3331
- }
3332
- if (idx !== -1) {
3333
- reply._serverItemId = msgs[idx]._serverItemId;
3334
- msgs[idx] = reply;
3335
- } else {
3336
- msgs.push(reply);
3337
- }
3338
- self.aiChatHistoryCache[params.key] = {
3339
- messages: msgs,
3340
- endOfList: existing.endOfList,
3341
- startKeyHistory: existing.startKeyHistory
3342
- };
3823
+ if (dispatchItemId) reply._serverItemId = dispatchItemId;
3824
+ self._applyReplyToCache(params.key, reply, dispatchItemId);
3343
3825
  return result;
3344
3826
  });
3345
3827
  this.pendingAgentRequests[params.key] = run;
@@ -3728,8 +4210,9 @@ var ChatSession = class {
3728
4210
  }
3729
4211
  this.host.notify();
3730
4212
  this.updateHistoryCache();
3731
- this.host.scrollToBottom(true);
4213
+ this.scrollForDispatch(stageId);
3732
4214
  var capturedComposed = composed, capturedPlatform = aiPlatform, capturedKey = key;
4215
+ var capturedQueuedLid = queuedBubble._localId;
3733
4216
  Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls, id.projectId, id.owner)).then(function(result) {
3734
4217
  var sendingIdx = self.getHistoryCacheKey() !== capturedKey ? -1 : self.state.messages.findIndex(function(m) {
3735
4218
  return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user" && !m._stageId && (m._ownerKey === void 0 || m._ownerKey === capturedKey);
@@ -3741,8 +4224,9 @@ var ChatSession = class {
3741
4224
  self.state.messages[sendingIdx] = upd;
3742
4225
  self.host.notify();
3743
4226
  }
4227
+ if (serverId) self._stampTurnWithItemId(capturedKey, capturedQueuedLid, void 0, serverId);
3744
4228
  if (result && result.poll && (result.status === "pending" || result.status === "running")) {
3745
- var qp = result.poll({ latency: POLL_INTERVAL });
4229
+ var qp = self._fgPollWithEarlyProbe(result, serverId);
3746
4230
  if (serverId) self._trackPoll(serverId, "fg", qp);
3747
4231
  return qp.then(function(res) {
3748
4232
  if (isPollStopped(res)) return;
@@ -3758,7 +4242,7 @@ var ChatSession = class {
3758
4242
  return;
3759
4243
  }
3760
4244
  var immediateUser = { role: "user", content: composed, _localId: this._newLocalId(), _ts: wallClockNow(), ...key ? { _ownerKey: key } : {} };
3761
- var immediatePlaceholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} };
4245
+ var immediatePlaceholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, _localId: this._newLocalId(), ...key ? { _ownerKey: key } : {} };
3762
4246
  var iStage = this._stageIndex(this.state.messages, stageId);
3763
4247
  if (iStage !== -1) {
3764
4248
  var iEx = this.state.messages[iStage];
@@ -3772,7 +4256,7 @@ var ChatSession = class {
3772
4256
  this.host.notify();
3773
4257
  this.updateHistoryCache();
3774
4258
  this.state.sending = true;
3775
- this.host.scrollToBottom(true);
4259
+ this.scrollForDispatch(stageId);
3776
4260
  var historyForLlm = this.state.messages.filter(function(m) {
3777
4261
  if (m === immediateUser) return false;
3778
4262
  return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask && !m.isError;
@@ -3785,6 +4269,7 @@ var ChatSession = class {
3785
4269
  projectId: id.projectId,
3786
4270
  history: historyForLlm
3787
4271
  });
4272
+ var immediateUserLid = immediateUser._localId, immediatePlaceholderLid = immediatePlaceholder._localId;
3788
4273
  var run = this.dispatchAgentRequest({
3789
4274
  key,
3790
4275
  projectId: id.projectId,
@@ -3796,7 +4281,15 @@ var ChatSession = class {
3796
4281
  boundedMessages: bounded.messages,
3797
4282
  userId: chatQueue,
3798
4283
  extractContent,
3799
- fileUrls
4284
+ fileUrls,
4285
+ // THE fix for a turn rendering twice after navigate-away-and-back. Until
4286
+ // this existed the immediate-send pair carried no server id for its whole
4287
+ // life (only the QUEUED path stamped one, off its ack), so nothing could
4288
+ // tell the local copy and the server's copy of the same turn apart. See
4289
+ // _stampTurnWithItemId.
4290
+ onItemId: function(itemId) {
4291
+ self._stampTurnWithItemId(key, immediateUserLid, immediatePlaceholderLid, itemId);
4292
+ }
3800
4293
  });
3801
4294
  Promise.resolve(run).catch(function() {
3802
4295
  }).then(function() {
@@ -3807,6 +4300,28 @@ var ChatSession = class {
3807
4300
  });
3808
4301
  });
3809
4302
  }
4303
+ /**
4304
+ * Scroll for a dispatch that is going out NOW, but never for one arriving late.
4305
+ *
4306
+ * A turn with attachments does not dispatch when the user hits Send: it waits out
4307
+ * its uploads and then its whole indexing chain, which is minutes
4308
+ * (awaitIndexingDrained). By then the reader has very often scrolled up into
4309
+ * history to pass the time, and the forcing scrollToBottom yanked them out of it
4310
+ * — and worse, force-pinned stickToBottom, which no-ops every method on the
4311
+ * scroll anchor and re-arms the queue-detect poll whose only bail is
4312
+ * !stickToBottom.
4313
+ *
4314
+ * `stageId` is the exact marker for that case: only the attachment path ever
4315
+ * produces one. The gesture itself was already paid for at stage time, where
4316
+ * stageOutgoingMessage forces the scroll while the user is still looking at the
4317
+ * composer. Deliberately NOT gated on "did we find the staged bubble" — a remount
4318
+ * rebuilds from the cache and the turn is appended rather than replaced, which is
4319
+ * just as late and just as unrequested.
4320
+ */
4321
+ scrollForDispatch(stageId) {
4322
+ if (stageId) this.host.scrollToBottomIfSticky(true);
4323
+ else this.host.scrollToBottom(true);
4324
+ }
3810
4325
  promoteNextBgQueuedToRunning() {
3811
4326
  if (this.state.messages.some(function(m) {
3812
4327
  return m.isPending && m.role === "assistant" && m.isBackgroundTask;
@@ -3932,6 +4447,29 @@ var ChatSession = class {
3932
4447
  else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
3933
4448
  else this.state.messages.push(msg);
3934
4449
  }
4450
+ /**
4451
+ * The server's OWN copy of this turn is already on screen.
4452
+ *
4453
+ * A first-page fetch can land between the server settling the item and this
4454
+ * poll's tick, and now that the local bubbles carry the item id
4455
+ * (_stampTurnWithItemId) the rescue correctly drops them and renders the
4456
+ * server's settled pair instead. There is then nothing left to resolve: the
4457
+ * -1 fallback would push the answer in a SECOND time at the bottom of the list,
4458
+ * and the positional fallbacks would hijack some other turn's bubble.
4459
+ *
4460
+ * The USER bubble counts, not just a settled assistant: an item whose answer is
4461
+ * empty produces no assistant bubble at all in the mapper, and that variant
4462
+ * would otherwise still bottom-push "No text response received...". While a turn
4463
+ * is genuinely live its user bubble is always pending (the queued branch sets
4464
+ * isPendingQueued, promoteNextQueuedToRunning sets isPendingInProcess), so this
4465
+ * cannot fire early.
4466
+ */
4467
+ _turnAlreadyRendered(serverId) {
4468
+ if (!serverId) return false;
4469
+ return this.state.messages.some(function(m) {
4470
+ return m._serverItemId === serverId && !m.isPending && !m.isPendingQueued && !m.isPendingInProcess;
4471
+ });
4472
+ }
3935
4473
  onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
3936
4474
  if (serverId) this.historyItemPolls.delete(serverId);
3937
4475
  if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
@@ -3940,6 +4478,12 @@ var ChatSession = class {
3940
4478
  if (serverId) this.cancelledServerIds.delete(serverId);
3941
4479
  return;
3942
4480
  }
4481
+ if (this._turnAlreadyRendered(serverId)) {
4482
+ if (serverId) this.cancelledServerIds.delete(serverId);
4483
+ this.host.notify();
4484
+ this.updateHistoryCache();
4485
+ return;
4486
+ }
3943
4487
  var targetIdx = this.resolveQueuedUserBubble(serverId);
3944
4488
  if (targetIdx === void 0) {
3945
4489
  this.host.notify();
@@ -3981,6 +4525,12 @@ var ChatSession = class {
3981
4525
  if (serverId) this.cancelledServerIds.delete(serverId);
3982
4526
  return;
3983
4527
  }
4528
+ if (this._turnAlreadyRendered(serverId)) {
4529
+ if (serverId) this.cancelledServerIds.delete(serverId);
4530
+ this.host.notify();
4531
+ this.updateHistoryCache();
4532
+ return;
4533
+ }
3984
4534
  var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
3985
4535
  if (isNotExists) {
3986
4536
  var userIdx = serverId ? this.state.messages.findIndex(function(m) {
@@ -5242,21 +5792,15 @@ var ChatSession = class {
5242
5792
  var rescued = [];
5243
5793
  for (var ri = 0; ri < self.state.messages.length; ri++) {
5244
5794
  var mm = self.state.messages[ri];
5245
- if (mm.isBackgroundTask) continue;
5246
- if (mm._ownerKey !== void 0 && mm._ownerKey !== loadKey) continue;
5247
- if (mm._serverItemId && serverIds[mm._serverItemId]) continue;
5248
- if (!mm._serverItemId) {
5249
- if (mm._stageId) {
5250
- rescued.push(mm);
5251
- continue;
5252
- }
5253
- if (mappedHasPendingAssistant) continue;
5254
- if (mm.isSendingToServer || mm.isPendingQueued || mm.isPendingInProcess || mm.isPending) rescued.push(mm);
5255
- else if (self.state.sending && mm.role === "user") {
5256
- var next = self.state.messages[ri + 1];
5257
- if (next && !next.isBackgroundTask && next.isPending && !next._serverItemId) rescued.push(mm);
5258
- }
5259
- }
5795
+ if (shouldRescueInFlightMessage(mm, {
5796
+ hasServerId: function(sid) {
5797
+ return !!serverIds[sid];
5798
+ },
5799
+ pageHasPendingAssistant: mappedHasPendingAssistant,
5800
+ sending: !!self.state.sending,
5801
+ next: self.state.messages[ri + 1],
5802
+ loadKey
5803
+ })) rescued.push(mm);
5260
5804
  }
5261
5805
  var oldestInPage1 = void 0;
5262
5806
  mapped.forEach(function(m) {
@@ -5307,6 +5851,9 @@ var ChatSession = class {
5307
5851
  keptOlderPages = prependOlder.length > 0 || interleave.length > 0;
5308
5852
  self.state.messages = prependOlder.length ? prependOlder.concat(page1) : page1;
5309
5853
  rescued.forEach(function(m) {
5854
+ if (m._serverItemId && self.state.messages.some(function(x) {
5855
+ return x._serverItemId === m._serverItemId && x.role === m.role;
5856
+ })) return;
5310
5857
  self.state.messages.push(m);
5311
5858
  });
5312
5859
  if (Object.keys(locallyCancelled).length) {
@@ -5431,6 +5978,7 @@ var ChatSession = class {
5431
5978
  releaseBgFlag();
5432
5979
  self.updateHistoryCache();
5433
5980
  self.host.notify();
5981
+ if (self.host.settleScroll) self.host.settleScroll();
5434
5982
  }, function() {
5435
5983
  releaseBgFlag();
5436
5984
  self.host.notify();
@@ -5458,8 +6006,8 @@ var ChatSession = class {
5458
6006
  if ((item._isBgTask || item._isOnBgQueue) && self.isPollingPaused()) return;
5459
6007
  if ((item._isBgTask || item._isOnBgQueue) && !bgAllow[item.id]) return;
5460
6008
  var capturedId = item.id;
5461
- var pp = item.poll({
5462
- latency: POLL_INTERVAL,
6009
+ var isBg = !!(item._isBgTask || item._isOnBgQueue);
6010
+ var pollOpts = {
5463
6011
  onResponse: function(response) {
5464
6012
  if (isPollStopped(response)) return;
5465
6013
  self.handleHistoryItemResolution(capturedId, response, platform);
@@ -5474,9 +6022,9 @@ var ChatSession = class {
5474
6022
  var uIdx = self.state.messages.findIndex(function(m) {
5475
6023
  return m.role === "user" && m._serverItemId === capturedId && !m.isCancelled;
5476
6024
  });
5477
- var isBg = aIdx !== -1 && !!self.state.messages[aIdx].isBackgroundTask || uIdx !== -1 && !!self.state.messages[uIdx].isBackgroundTask;
6025
+ var isBg2 = aIdx !== -1 && !!self.state.messages[aIdx].isBackgroundTask || uIdx !== -1 && !!self.state.messages[uIdx].isBackgroundTask;
5478
6026
  if (aIdx !== -1) self.state.messages.splice(aIdx, 1);
5479
- if (!isBg) {
6027
+ if (!isBg2) {
5480
6028
  if (uIdx !== -1) {
5481
6029
  var ex = self.state.messages[uIdx];
5482
6030
  self.state.messages[uIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId };
@@ -5503,7 +6051,8 @@ var ChatSession = class {
5503
6051
  self.updateHistoryCache();
5504
6052
  }
5505
6053
  }
5506
- });
6054
+ };
6055
+ var pp = isBg ? item.poll(Object.assign({ latency: POLL_INTERVAL }, pollOpts)) : self._fgPollWithEarlyProbe(item, capturedId, pollOpts);
5507
6056
  self._trackPoll(capturedId, item._isBgTask || item._isOnBgQueue ? "bg" : "fg", pp);
5508
6057
  if (pp && pp.catch) pp.catch(function() {
5509
6058
  });
@@ -5511,7 +6060,7 @@ var ChatSession = class {
5511
6060
  self.drainBgTaskQueue();
5512
6061
  }
5513
6062
  if (!fetchMore) self.refreshLiveIndexState();
5514
- if (!fetchMore) return self.host.scrollToBottomIfSticky();
6063
+ if (!fetchMore) return self.host.settleScroll ? self.host.settleScroll() : self.host.scrollToBottomIfSticky();
5515
6064
  }).catch(function(err) {
5516
6065
  console.warn("[chat-engine] getChatHistory failed", err);
5517
6066
  }).then(function() {
@@ -6134,6 +6683,6 @@ function buildChatDisplayList(messages, opts) {
6134
6683
  return out;
6135
6684
  }
6136
6685
 
6137
- export { BG_INDEXING_QUEUE_SUFFIX, BOM, BOM_EXTS, CLAUDE_INPUT_CAP_RATIO, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_CONTEXT_WINDOW, DEFAULT_OPENAI_MODEL, EMPTY_INDEXING_REPLY, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, EXT_CONTENT_TYPES, 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, 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, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, PRESIGN_SAFETY_MARGIN_MS, PREVIEWABLE_IMAGE_CONTENT_TYPES, PREVIEW_BROWSER_CACHE_SECONDS, PREVIEW_URL_EXPIRES_SECONDS, RENDER_FROM_TOKEN, RTF_EXTS, RUN_RECORD_WORKING_STALE_MS, TOOL_AND_RESPONSE_BUFFER, 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 };
6686
+ export { BG_INDEXING_QUEUE_SUFFIX, BOM, BOM_EXTS, CLAUDE_INPUT_CAP_RATIO, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_CONTEXT_WINDOW, DEFAULT_OPENAI_MODEL, EMPTY_INDEXING_REPLY, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, EXT_CONTENT_TYPES, 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, 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, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, PRESIGN_SAFETY_MARGIN_MS, PREVIEWABLE_IMAGE_CONTENT_TYPES, PREVIEW_BROWSER_CACHE_SECONDS, PREVIEW_LAYOUT_BOX_SELECTOR, PREVIEW_URL_EXPIRES_SECONDS, RENDER_FROM_TOKEN, RTF_EXTS, RUN_RECORD_WORKING_STALE_MS, TOOL_AND_RESPONSE_BUFFER, 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 };
6138
6687
  //# sourceMappingURL=engine.mjs.map
6139
6688
  //# sourceMappingURL=engine.mjs.map