bunnyquery 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bunnyquery.js +273 -32
- package/dist/engine.cjs +288 -29
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +135 -2
- package/dist/engine.d.ts +135 -2
- package/dist/engine.mjs +280 -30
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/ai_agent.ts +80 -0
- package/src/engine/budget.ts +113 -7
- package/src/engine/history.ts +48 -18
- package/src/engine/index.ts +8 -0
- package/src/engine/requests.ts +11 -2
- package/src/engine/session.ts +202 -3
package/bunnyquery.js
CHANGED
|
@@ -751,24 +751,63 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
751
751
|
// src/engine/budget.ts
|
|
752
752
|
var CONTEXT_WINDOW_DEFAULT = { claude: 2e5, openai: 128e3 };
|
|
753
753
|
var CONTEXT_WINDOW_BY_MODEL = {
|
|
754
|
-
|
|
754
|
+
// exact ids
|
|
755
|
+
"claude-opus-5": 1e6,
|
|
756
|
+
"claude-opus-4-8": 1e6,
|
|
757
|
+
"claude-opus-4-7": 1e6,
|
|
758
|
+
"claude-sonnet-5": 1e6,
|
|
759
|
+
"claude-sonnet-4-6": 1e6,
|
|
755
760
|
"claude-sonnet-4": 2e5,
|
|
756
|
-
"
|
|
761
|
+
"claude-haiku-4-5": 2e5,
|
|
762
|
+
"gpt-5.4": 128e3,
|
|
763
|
+
"gpt-5.6-luna": 128e3,
|
|
764
|
+
// family keys
|
|
765
|
+
"claude-opus": 1e6,
|
|
766
|
+
"claude-sonnet": 1e6,
|
|
767
|
+
"claude-haiku": 2e5,
|
|
768
|
+
"gpt-5.6": 128e3,
|
|
769
|
+
"gpt-5": 128e3
|
|
757
770
|
};
|
|
771
|
+
var apiReportedContextWindows = {};
|
|
772
|
+
var projectContextWindows = {};
|
|
773
|
+
function setProjectContextWindow(serviceId, tokens) {
|
|
774
|
+
var key = (serviceId || "").trim();
|
|
775
|
+
if (!key) return;
|
|
776
|
+
var n = Number(tokens);
|
|
777
|
+
if (Number.isFinite(n) && n > 0) projectContextWindows[key] = Math.floor(n);
|
|
778
|
+
else delete projectContextWindows[key];
|
|
779
|
+
}
|
|
780
|
+
function getProjectContextWindow(serviceId) {
|
|
781
|
+
var key = (serviceId || "").trim();
|
|
782
|
+
return key && projectContextWindows[key] ? projectContextWindows[key] : null;
|
|
783
|
+
}
|
|
758
784
|
var OUTPUT_TOKEN_RESERVE = 22e3;
|
|
759
785
|
var TOOL_AND_RESPONSE_BUFFER = 4e3;
|
|
760
786
|
var MIN_INPUT_TOKEN_BUDGET = 8e3;
|
|
761
787
|
var CLAUDE_PER_REQUEST_INPUT_CAP = 28e3;
|
|
788
|
+
var MAX_HISTORY_MESSAGES = 20;
|
|
762
789
|
var HISTORY_TOKEN_BUDGET = 8e3;
|
|
790
|
+
var CLAUDE_INPUT_CAP_RATIO = 0.16;
|
|
791
|
+
var HISTORY_BUDGET_RATIO = 0.08;
|
|
763
792
|
function estimateTextTokens(text) {
|
|
764
793
|
return Math.ceil((text || "").length / 3);
|
|
765
794
|
}
|
|
766
795
|
function estimateMessageTokens(msg) {
|
|
767
796
|
return estimateTextTokens(msg.content) + estimateTextTokens(msg.role) + 6;
|
|
768
797
|
}
|
|
769
|
-
function getContextWindow(platform, model) {
|
|
798
|
+
function getContextWindow(platform, model, serviceId) {
|
|
799
|
+
var override = serviceId ? getProjectContextWindow(serviceId) : null;
|
|
800
|
+
if (override) return override;
|
|
770
801
|
var normalized = (model || "").trim().toLowerCase();
|
|
771
|
-
if (normalized
|
|
802
|
+
if (normalized) {
|
|
803
|
+
if (apiReportedContextWindows[normalized]) return apiReportedContextWindows[normalized];
|
|
804
|
+
if (CONTEXT_WINDOW_BY_MODEL[normalized]) return CONTEXT_WINDOW_BY_MODEL[normalized];
|
|
805
|
+
var parts = normalized.split("-");
|
|
806
|
+
for (var end = parts.length - 1; end > 0; end--) {
|
|
807
|
+
var family = parts.slice(0, end).join("-");
|
|
808
|
+
if (CONTEXT_WINDOW_BY_MODEL[family]) return CONTEXT_WINDOW_BY_MODEL[family];
|
|
809
|
+
}
|
|
810
|
+
}
|
|
772
811
|
return CONTEXT_WINDOW_DEFAULT[platform];
|
|
773
812
|
}
|
|
774
813
|
function stripFileBlocksFromHistory(content) {
|
|
@@ -776,15 +815,19 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
776
815
|
return content.replace(/```([^\n`]+?\.[^\s.`]+)\n[\s\S]*?```/g, "[file previously attached: $1]");
|
|
777
816
|
}
|
|
778
817
|
function buildBoundedChatMessages(options) {
|
|
779
|
-
var contextWindow = getContextWindow(options.platform, options.model);
|
|
818
|
+
var contextWindow = getContextWindow(options.platform, options.model, options.serviceId);
|
|
780
819
|
var contextBasedBudget = Math.max(
|
|
781
820
|
MIN_INPUT_TOKEN_BUDGET,
|
|
782
821
|
contextWindow - OUTPUT_TOKEN_RESERVE - TOOL_AND_RESPONSE_BUFFER
|
|
783
822
|
);
|
|
784
|
-
var
|
|
823
|
+
var scaled = !!(options.serviceId && getProjectContextWindow(options.serviceId));
|
|
824
|
+
var claudeInputCap = scaled ? Math.max(CLAUDE_PER_REQUEST_INPUT_CAP, Math.round(contextBasedBudget * CLAUDE_INPUT_CAP_RATIO)) : CLAUDE_PER_REQUEST_INPUT_CAP;
|
|
825
|
+
var availableInputBudget = options.platform === "claude" ? Math.min(contextBasedBudget, claudeInputCap) : contextBasedBudget;
|
|
785
826
|
var systemCost = estimateTextTokens(options.systemPrompt) + 12;
|
|
786
|
-
var
|
|
787
|
-
var
|
|
827
|
+
var historyAllowance = scaled ? Math.max(HISTORY_TOKEN_BUDGET, Math.round(contextBasedBudget * HISTORY_BUDGET_RATIO)) : HISTORY_TOKEN_BUDGET;
|
|
828
|
+
var budgetForHistory = Math.max(1e3, Math.min(historyAllowance, availableInputBudget - systemCost));
|
|
829
|
+
var maxHistoryMessages = scaled ? Math.max(MAX_HISTORY_MESSAGES, Math.round(MAX_HISTORY_MESSAGES * (budgetForHistory / HISTORY_TOKEN_BUDGET))) : MAX_HISTORY_MESSAGES;
|
|
830
|
+
var windowed = options.history.slice(-maxHistoryMessages);
|
|
788
831
|
var latestIndex = windowed.length - 1;
|
|
789
832
|
var trimmed = windowed.map(function(m, i2) {
|
|
790
833
|
if (i2 === latestIndex) return m;
|
|
@@ -829,6 +872,31 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
829
872
|
}
|
|
830
873
|
}
|
|
831
874
|
|
|
875
|
+
// src/engine/ai_agent.ts
|
|
876
|
+
function normalizePlatform(raw) {
|
|
877
|
+
var p = (raw || "").trim().toLowerCase();
|
|
878
|
+
return p === "claude" || p === "openai" ? p : null;
|
|
879
|
+
}
|
|
880
|
+
function parseAiAgentValue(value) {
|
|
881
|
+
var raw = (value || "").trim();
|
|
882
|
+
if (!raw || raw.toLowerCase() === "none") {
|
|
883
|
+
return { platform: null, model: "", contextWindow: null, hasPlatform: false };
|
|
884
|
+
}
|
|
885
|
+
var firstHash = raw.indexOf("#");
|
|
886
|
+
if (firstHash === -1) {
|
|
887
|
+
var only = normalizePlatform(raw);
|
|
888
|
+
return { platform: only, model: "", contextWindow: null, hasPlatform: !!only };
|
|
889
|
+
}
|
|
890
|
+
var platform = normalizePlatform(raw.slice(0, firstHash));
|
|
891
|
+
var rest = raw.slice(firstHash + 1);
|
|
892
|
+
var secondHash = rest.indexOf("#");
|
|
893
|
+
var model = (secondHash === -1 ? rest : rest.slice(0, secondHash)).trim();
|
|
894
|
+
var windowRaw = secondHash === -1 ? "" : rest.slice(secondHash + 1).trim();
|
|
895
|
+
var parsedWindow = windowRaw ? Number(windowRaw) : NaN;
|
|
896
|
+
var contextWindow = Number.isFinite(parsedWindow) && parsedWindow > 0 ? Math.floor(parsedWindow) : null;
|
|
897
|
+
return { platform, model, contextWindow, hasPlatform: !!platform };
|
|
898
|
+
}
|
|
899
|
+
|
|
832
900
|
// src/engine/requests.ts
|
|
833
901
|
var ANTHROPIC_MESSAGES_API_URL = "https://api.anthropic.com/v1/messages";
|
|
834
902
|
var ANTHROPIC_VERSION = "2023-06-01";
|
|
@@ -1310,7 +1378,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1310
1378
|
method: "POST"
|
|
1311
1379
|
},
|
|
1312
1380
|
{ service: params.service, owner: params.owner },
|
|
1313
|
-
params.queue ? { queue: params.queue } : {}
|
|
1381
|
+
params.queue ? { queue: params.queue } : {},
|
|
1382
|
+
params.status ? { status: params.status } : {}
|
|
1314
1383
|
);
|
|
1315
1384
|
return chatEngineConfig().clientSecretRequestHistory(
|
|
1316
1385
|
p,
|
|
@@ -1347,6 +1416,25 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1347
1416
|
}
|
|
1348
1417
|
return "";
|
|
1349
1418
|
}
|
|
1419
|
+
function isIndexingRequestText(userText) {
|
|
1420
|
+
if (typeof userText !== "string") return false;
|
|
1421
|
+
return userText.indexOf("A new file has just been uploaded") === 0 || userText.indexOf("CONTINUE indexing") === 0;
|
|
1422
|
+
}
|
|
1423
|
+
function parseIndexingRequestText(userText) {
|
|
1424
|
+
if (typeof userText !== "string" || !userText) return null;
|
|
1425
|
+
var nameMatch = userText.match(/^- name: (.+)$/m);
|
|
1426
|
+
if (!nameMatch) return null;
|
|
1427
|
+
var mimeMatch = userText.match(/^- mime type: (.+)$/m);
|
|
1428
|
+
var sizeMatch = userText.match(/^- size \(bytes\): (\d+)$/m);
|
|
1429
|
+
var pathMatch = userText.match(/^- storage path: (.+)$/m);
|
|
1430
|
+
return {
|
|
1431
|
+
name: nameMatch[1].trim(),
|
|
1432
|
+
path: pathMatch ? pathMatch[1].trim() : void 0,
|
|
1433
|
+
mime: mimeMatch ? mimeMatch[1].trim() : void 0,
|
|
1434
|
+
size: sizeMatch ? Number(sizeMatch[1]) : void 0,
|
|
1435
|
+
continued: userText.indexOf("CONTINUE indexing") === 0
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1350
1438
|
function mapHistoryListToMessages(list, platform, opts) {
|
|
1351
1439
|
var mapped = [], runningItemIds = [];
|
|
1352
1440
|
var extractAssistantText = platform === "openai" ? extractOpenAIText : extractClaudeText;
|
|
@@ -1371,27 +1459,17 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1371
1459
|
var displayContent;
|
|
1372
1460
|
var indexFile = void 0;
|
|
1373
1461
|
if (item._isBgTask) {
|
|
1374
|
-
var
|
|
1375
|
-
if (
|
|
1376
|
-
var mimeMatch = userText.match(/^- mime type: (.+)$/m);
|
|
1377
|
-
var sizeMatch = userText.match(/^- size \(bytes\): (\d+)$/m);
|
|
1378
|
-
var pathMatch = userText.match(/^- storage path: (.+)$/m);
|
|
1379
|
-
var isContinuePass = userText.indexOf("CONTINUE indexing") === 0;
|
|
1462
|
+
var ref = parseIndexingRequestText(userText);
|
|
1463
|
+
if (ref) {
|
|
1380
1464
|
displayContent = opts.formatIndexingLabel(
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1465
|
+
ref.name,
|
|
1466
|
+
ref.mime || "",
|
|
1467
|
+
typeof ref.size === "number" ? ref.size : null,
|
|
1468
|
+
ref.path,
|
|
1385
1469
|
false,
|
|
1386
|
-
|
|
1470
|
+
ref.continued
|
|
1387
1471
|
);
|
|
1388
|
-
indexFile =
|
|
1389
|
-
name: nameMatch[1].trim(),
|
|
1390
|
-
path: pathMatch ? pathMatch[1].trim() : void 0,
|
|
1391
|
-
mime: mimeMatch ? mimeMatch[1].trim() : void 0,
|
|
1392
|
-
size: sizeMatch ? Number(sizeMatch[1]) : void 0,
|
|
1393
|
-
continued: isContinuePass
|
|
1394
|
-
};
|
|
1472
|
+
indexFile = ref;
|
|
1395
1473
|
} else {
|
|
1396
1474
|
displayContent = userText;
|
|
1397
1475
|
}
|
|
@@ -1520,6 +1598,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1520
1598
|
}
|
|
1521
1599
|
|
|
1522
1600
|
// src/engine/session.ts
|
|
1601
|
+
var WORKER_PASS_ADOPT_LIMIT = 20;
|
|
1602
|
+
var WORKER_PASS_ADOPT_ATTEMPTS = [0, 2e3, 6e3];
|
|
1523
1603
|
var _g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
1524
1604
|
function nowMs() {
|
|
1525
1605
|
return _g.performance && typeof _g.performance.now === "function" ? _g.performance.now() : Date.now();
|
|
@@ -1539,6 +1619,35 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1539
1619
|
var ChatSession = class {
|
|
1540
1620
|
constructor(host) {
|
|
1541
1621
|
this.typewriterQueue = Promise.resolve();
|
|
1622
|
+
/**
|
|
1623
|
+
* Pick up indexing passes the WORKER minted, which no client ever dispatched.
|
|
1624
|
+
*
|
|
1625
|
+
* For a PDF (and for text/grid when windowed indexing is on) the worker writes
|
|
1626
|
+
* pass N+1's row itself, inside pass N's invocation, right after saving pass
|
|
1627
|
+
* N's result. That row reaches this client through nothing at all: it is not in
|
|
1628
|
+
* bgTaskQueue (the client never asked for it) and it is not in state.messages
|
|
1629
|
+
* (only a first-page history load maps it in, which happens on mount, project
|
|
1630
|
+
* switch or tab return). So between two worker passes every pass the client
|
|
1631
|
+
* knows about is settled, and the collapsed row renders "Indexed N passes"
|
|
1632
|
+
* with no spinner and no Stop, for a file that is still being read. A user who
|
|
1633
|
+
* believes that then asks questions against a half-indexed file — which is what
|
|
1634
|
+
* this exists to prevent.
|
|
1635
|
+
*
|
|
1636
|
+
* So when a background indexing pass settles, ask the bg queue what is still
|
|
1637
|
+
* unresolved on it and adopt anything unknown as an ordinary BgTaskEntry.
|
|
1638
|
+
* drainBgTaskQueue then treats it exactly like a pass this client dispatched:
|
|
1639
|
+
* same bubble, same `_indexFile` (so it joins the file's collapsed row), same
|
|
1640
|
+
* poll — and that poll settling runs this again, so the chain is followed to
|
|
1641
|
+
* its end. `status`-scoped so the reply carries the live items only, never a
|
|
1642
|
+
* page of finished ones with their bodies.
|
|
1643
|
+
*
|
|
1644
|
+
* Termination: the only trigger is a pass SETTLING, which happens once per
|
|
1645
|
+
* pass. An adopted item that is still running is not re-adopted (its id is
|
|
1646
|
+
* already polled), and when the queue holds nothing unknown the chain stops on
|
|
1647
|
+
* its own. Nothing here is periodic — a timer that re-reads history is the
|
|
1648
|
+
* shape that previously looped fetchHistoryPage after an already-DONE index.
|
|
1649
|
+
*/
|
|
1650
|
+
this._adoptingWorkerPasses = false;
|
|
1542
1651
|
this.host = host;
|
|
1543
1652
|
this.state = {
|
|
1544
1653
|
messages: [],
|
|
@@ -2484,8 +2593,21 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2484
2593
|
}
|
|
2485
2594
|
// --- background-task resolution + drain -------------------------------
|
|
2486
2595
|
handleHistoryItemResolution(itemId, response, platform) {
|
|
2596
|
+
var indexRef = this._indexRefOfItem(itemId);
|
|
2487
2597
|
this.applyHistoryItemResolution(itemId, response, platform);
|
|
2488
2598
|
this.promoteNextBgQueuedToRunning();
|
|
2599
|
+
if (indexRef) this._followWorkerIndexingChain(indexRef.name, indexRef.mime);
|
|
2600
|
+
}
|
|
2601
|
+
/** The file an already-rendered background pass is about, off its request
|
|
2602
|
+
* bubble. Null for an ordinary turn, which is most of them. */
|
|
2603
|
+
_indexRefOfItem(itemId) {
|
|
2604
|
+
if (!itemId) return null;
|
|
2605
|
+
for (var i = 0; i < this.state.messages.length; i++) {
|
|
2606
|
+
var m = this.state.messages[i];
|
|
2607
|
+
if (m._serverItemId !== itemId || m.role !== "user" || !m.isBackgroundTask) continue;
|
|
2608
|
+
return m._indexFile || null;
|
|
2609
|
+
}
|
|
2610
|
+
return null;
|
|
2489
2611
|
}
|
|
2490
2612
|
applyHistoryItemResolution(itemId, response, platform) {
|
|
2491
2613
|
this.historyItemPolls.delete(itemId);
|
|
@@ -2619,6 +2741,116 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2619
2741
|
self.cancelQueuedMessage(t.msg, idx === -1 ? t.idx : idx);
|
|
2620
2742
|
});
|
|
2621
2743
|
}
|
|
2744
|
+
/**
|
|
2745
|
+
* True when the WORKER, not this client, drives the rest of this file's chain.
|
|
2746
|
+
* The mirror image of the early returns in maybeResumeIndexing: whatever that
|
|
2747
|
+
* refuses to continue is exactly what nothing client-side is tracking.
|
|
2748
|
+
*/
|
|
2749
|
+
_isWorkerDrivenIndexing(filename, mime) {
|
|
2750
|
+
if (!isPagedReadFile(filename, mime)) return false;
|
|
2751
|
+
if (isImageVisionFile(filename, mime)) return true;
|
|
2752
|
+
return windowedIndexingEnabled() && isWindowedReadFile(filename, mime);
|
|
2753
|
+
}
|
|
2754
|
+
_adoptWorkerIndexingPasses(attempt) {
|
|
2755
|
+
var self = this;
|
|
2756
|
+
if (this._adoptingWorkerPasses) return;
|
|
2757
|
+
var id = this.host.getIdentity();
|
|
2758
|
+
var platform = id.platform;
|
|
2759
|
+
if (!id.serviceId || platform !== "claude" && platform !== "openai") return;
|
|
2760
|
+
if (this.isPollingPaused() || !this.host.isViewMounted()) return;
|
|
2761
|
+
var svcId = id.serviceId, owner = id.owner;
|
|
2762
|
+
var queue = (id.userId || id.serviceId) + BG_INDEXING_QUEUE_SUFFIX;
|
|
2763
|
+
var ask = function(status) {
|
|
2764
|
+
return Promise.resolve(getChatHistory(
|
|
2765
|
+
{ service: svcId, owner, platform, queue, status },
|
|
2766
|
+
{ limit: WORKER_PASS_ADOPT_LIMIT }
|
|
2767
|
+
)).catch(function() {
|
|
2768
|
+
return null;
|
|
2769
|
+
});
|
|
2770
|
+
};
|
|
2771
|
+
this._adoptingWorkerPasses = true;
|
|
2772
|
+
Promise.all([ask("running"), ask("pending")]).then(function(results) {
|
|
2773
|
+
self._adoptingWorkerPasses = false;
|
|
2774
|
+
var now = self.host.getIdentity();
|
|
2775
|
+
if (now.serviceId !== svcId || now.platform !== platform) return;
|
|
2776
|
+
if (!self.host.isViewMounted()) return;
|
|
2777
|
+
var adoptedIds = [];
|
|
2778
|
+
for (var ri = 0; ri < results.length; ri++) {
|
|
2779
|
+
var list = results[ri] && Array.isArray(results[ri].list) ? results[ri].list : [];
|
|
2780
|
+
for (var i = 0; i < list.length; i++) {
|
|
2781
|
+
if (self._adoptWorkerIndexingItem(list[i], svcId, platform)) adoptedIds.push(list[i].id);
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
if (adoptedIds.length) {
|
|
2785
|
+
self.drainBgTaskQueue();
|
|
2786
|
+
if (self._isTrackingAny(adoptedIds)) return;
|
|
2787
|
+
}
|
|
2788
|
+
if (attempt + 1 >= WORKER_PASS_ADOPT_ATTEMPTS.length) return;
|
|
2789
|
+
setTimeout(function() {
|
|
2790
|
+
var later = self.host.getIdentity();
|
|
2791
|
+
if (later.serviceId !== svcId || later.platform !== platform) return;
|
|
2792
|
+
if (self.isPollingPaused() || !self.host.isViewMounted()) return;
|
|
2793
|
+
self._adoptWorkerIndexingPasses(attempt + 1);
|
|
2794
|
+
}, WORKER_PASS_ADOPT_ATTEMPTS[attempt + 1]);
|
|
2795
|
+
}, function() {
|
|
2796
|
+
self._adoptingWorkerPasses = false;
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2799
|
+
/** Any of these ids still queued or still polled, i.e. surviving work. */
|
|
2800
|
+
_isTrackingAny(ids) {
|
|
2801
|
+
for (var i = 0; i < ids.length; i++) {
|
|
2802
|
+
if (this.historyItemPolls.has(ids[i])) return true;
|
|
2803
|
+
for (var q = 0; q < this.bgTaskQueue.length; q++) {
|
|
2804
|
+
if (this.bgTaskQueue[q].id === ids[i]) return true;
|
|
2805
|
+
}
|
|
2806
|
+
}
|
|
2807
|
+
return false;
|
|
2808
|
+
}
|
|
2809
|
+
/** One live bg-queue item -> a BgTaskEntry, if it is an indexing pass this
|
|
2810
|
+
* client is not already tracking. Returns whether it was adopted. */
|
|
2811
|
+
_adoptWorkerIndexingItem(item, svcId, platform) {
|
|
2812
|
+
if (!item || typeof item.id !== "string" || !item.id) return false;
|
|
2813
|
+
if (item.status !== "pending" && item.status !== "running") return false;
|
|
2814
|
+
if (typeof item.poll !== "function") return false;
|
|
2815
|
+
if (this.historyItemPolls.has(item.id)) return false;
|
|
2816
|
+
for (var q = 0; q < this.bgTaskQueue.length; q++) {
|
|
2817
|
+
if (this.bgTaskQueue[q].id === item.id) return false;
|
|
2818
|
+
}
|
|
2819
|
+
for (var m = 0; m < this.state.messages.length; m++) {
|
|
2820
|
+
var msg = this.state.messages[m];
|
|
2821
|
+
if (msg._serverItemId !== item.id) continue;
|
|
2822
|
+
if (!(msg.isPending || msg.isPendingInProcess || msg.isPendingQueued)) return false;
|
|
2823
|
+
}
|
|
2824
|
+
var body = item.request_body;
|
|
2825
|
+
if (!body || typeof body !== "object") return false;
|
|
2826
|
+
if (platform === "claude" ? !Array.isArray(body.messages) : !Array.isArray(body.input)) return false;
|
|
2827
|
+
var userText = extractLastUserTextFromRequest(body);
|
|
2828
|
+
if (!isIndexingRequestText(userText)) return false;
|
|
2829
|
+
var ref = parseIndexingRequestText(userText);
|
|
2830
|
+
if (!ref || !ref.name) return false;
|
|
2831
|
+
if (!this._isWorkerDrivenIndexing(ref.name, ref.mime)) return false;
|
|
2832
|
+
this.bgTaskQueue.push({
|
|
2833
|
+
serviceId: svcId,
|
|
2834
|
+
platform,
|
|
2835
|
+
id: item.id,
|
|
2836
|
+
filename: ref.name,
|
|
2837
|
+
storagePath: ref.path,
|
|
2838
|
+
mime: ref.mime,
|
|
2839
|
+
size: ref.size,
|
|
2840
|
+
status: item.status === "running" ? "running" : "pending",
|
|
2841
|
+
poll: item.poll,
|
|
2842
|
+
// Drives the "(continuing)" label and marks this as a continuation for
|
|
2843
|
+
// _applyIndexCancellations, which must not read a CONTINUE pass as the
|
|
2844
|
+
// fresh first pass that lifts a stop.
|
|
2845
|
+
resumePass: ref.continued ? 1 : 0
|
|
2846
|
+
});
|
|
2847
|
+
return true;
|
|
2848
|
+
}
|
|
2849
|
+
/** Follow the chain on from a background indexing pass that just settled. */
|
|
2850
|
+
_followWorkerIndexingChain(filename, mime) {
|
|
2851
|
+
if (!this._isWorkerDrivenIndexing(filename, mime)) return;
|
|
2852
|
+
this._adoptWorkerIndexingPasses(0);
|
|
2853
|
+
}
|
|
2622
2854
|
/** Best-effort server-side cancel of a bg-queue item that has no bubble (so
|
|
2623
2855
|
* cancelQueuedMessage, which drives one, has nothing to act on). */
|
|
2624
2856
|
_cancelServerItem(serverId) {
|
|
@@ -2829,8 +3061,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2829
3061
|
var chatList = history && Array.isArray(history.list) ? history.list : [];
|
|
2830
3062
|
chatList.forEach(function(item) {
|
|
2831
3063
|
if (isBgIndexingQueue(item.queue_name)) {
|
|
2832
|
-
|
|
2833
|
-
if (typeof userText === "string" && (userText.indexOf("A new file has just been uploaded") === 0 || userText.indexOf("CONTINUE indexing") === 0)) item._isBgTask = true;
|
|
3064
|
+
if (isIndexingRequestText(extractLastUserTextFromRequest(item.request_body))) item._isBgTask = true;
|
|
2834
3065
|
else item._isOnBgQueue = true;
|
|
2835
3066
|
}
|
|
2836
3067
|
});
|
|
@@ -3401,7 +3632,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3401
3632
|
(function() {
|
|
3402
3633
|
var MCP_PROD = "https://mcp.broadwayinc.computer";
|
|
3403
3634
|
var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
|
|
3404
|
-
var BQ_VERSION = "1.8.
|
|
3635
|
+
var BQ_VERSION = "1.8.1" ;
|
|
3405
3636
|
var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
|
|
3406
3637
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
3407
3638
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -6703,7 +6934,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6703
6934
|
if (S.aiPlatform === "none") return "No agent configured";
|
|
6704
6935
|
return S.serviceName || "BunnyQuery";
|
|
6705
6936
|
}
|
|
6706
|
-
function
|
|
6937
|
+
function parseAiAgentValue2(value) {
|
|
6707
6938
|
var raw = (value || "").trim();
|
|
6708
6939
|
var platform = raw, model = "";
|
|
6709
6940
|
if (raw.indexOf("#") !== -1) {
|
|
@@ -6717,9 +6948,10 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6717
6948
|
function applyAgentConfig() {
|
|
6718
6949
|
var conn = S.service || {};
|
|
6719
6950
|
var raw = conn.ai_agent || "";
|
|
6720
|
-
var parsed =
|
|
6951
|
+
var parsed = parseAiAgentValue2(raw);
|
|
6721
6952
|
S.aiPlatform = parsed.platform;
|
|
6722
6953
|
S.aiModel = parsed.model;
|
|
6954
|
+
setProjectContextWindow(S.serviceId, parseAiAgentValue(raw).contextWindow);
|
|
6723
6955
|
S.serviceName = conn.service_name || "";
|
|
6724
6956
|
S.serviceDescription = conn.service_description || "";
|
|
6725
6957
|
}
|
|
@@ -6861,6 +7093,15 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6861
7093
|
},
|
|
6862
7094
|
mcpBaseUrl: mcpBaseUrl(),
|
|
6863
7095
|
poll: 0,
|
|
7096
|
+
// Server-driven windowed indexing. Off by default in the engine because the
|
|
7097
|
+
// worker must strip `_skapi_window` first, or the provider rejects the whole
|
|
7098
|
+
// call with no retry. `apply_file_windows` is deployed in every region
|
|
7099
|
+
// (verified 2026-07-27 against the deployed ClientSecretKeyPollingWorker in
|
|
7100
|
+
// all 7), so the widget now opts in like agent.vue does. Without it the
|
|
7101
|
+
// widget fell back to the client-driven CONTINUE loop capped at
|
|
7102
|
+
// MAX_INDEXING_RESUME_PASSES, which needs the tab kept open and stops early
|
|
7103
|
+
// on a big file. Pass windowedIndexing: false in init opts to opt back out.
|
|
7104
|
+
windowedIndexing: S.opts.windowedIndexing !== false,
|
|
6864
7105
|
// Client-side attachment parsers (e.g. an .hwp parser) passed via init opts.
|
|
6865
7106
|
attachmentParsers: S.opts.attachmentParsers || void 0
|
|
6866
7107
|
});
|