bunnyquery 1.7.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.css +12 -0
- package/bunnyquery.js +337 -41
- package/dist/engine.cjs +351 -35
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +214 -12
- package/dist/engine.d.ts +214 -12
- package/dist/engine.mjs +340 -36
- 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 +58 -18
- package/src/engine/host.ts +6 -0
- package/src/engine/index.ts +9 -0
- package/src/engine/indexing_groups.ts +50 -11
- package/src/engine/links.ts +35 -1
- package/src/engine/prompts/chat_system_prompt.ts +3 -0
- package/src/engine/requests.ts +11 -2
- package/src/engine/session.ts +220 -6
- package/src/engine/time.ts +34 -0
- package/styles/chat.css +12 -0
package/bunnyquery.js
CHANGED
|
@@ -280,6 +280,9 @@ Extracted content of attached office files (read inline below; do NOT fetch thei
|
|
|
280
280
|
You are a dedicated assistant for the project ID: "${formattedServiceId}".
|
|
281
281
|
Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage.
|
|
282
282
|
Knowledge lookup: Before saying you don't know or that something isn't in the chat history, ALWAYS query this project's database through the available MCP tools to look for the answer. The user's data is the source of truth - the chat transcript is not. Only respond with "I don't know" or "I couldn't find that" after you have actually searched the project's data and come back empty.
|
|
283
|
+
Complete answers over stored data: The database holds one record per spreadsheet row, and each uploaded file becomes many records that usually share a table. So a question about the data almost always spans many records across several files. For any request that counts, sums, totals, lists every match, compares across records, finds which one, or asks whether something is present or ABSENT (for example "how many", "total spent", "which card", "is there any", "\uC5C6\uC5B4?", "\uD558\uB098\uB3C4 \uC5C6\uB098?"), you MUST read the COMPLETE matching set before answering. Query with fetch_all set to true, or page through getToolResponsePage until pagination.complete is true, across EVERY relevant table and EVERY relevant file. A single default query returns only the first page (about 50 records). That is a SAMPLE. Never treat it as the whole dataset.
|
|
284
|
+
Never assert absence from a partial read. Do not say "there is no X", "none", "not found", or "\uC544\uB2C8\uC694, \uC5C6\uC2B5\uB2C8\uB2E4" until a complete scan has come back empty. If you have not finished scanning every relevant table and file, keep querying instead of guessing. A confident "no" that later turns out wrong is worse than telling the user you are still checking.
|
|
285
|
+
Embedded values: a search term is often stored inside a larger string. A merchant "GODADDY" appears as "DNH*GODADDY#4070277042", and a card as "4140****2941". Server-side index and tag filters match only exact values or leading prefixes, not substrings, so filtering on such a field silently drops rows. When the value you are looking for may be embedded, do not trust a narrow filter to be complete. Fetch the full set with fetch_all and match the substring yourself.
|
|
283
286
|
File attachments: When a user message contains an "Attached files:" section with markdown links, those links point to short-lived signed URLs in this project's db storage and will expire.
|
|
284
287
|
- Image files (.jpg, .jpeg, .png, .gif, .webp) are ALREADY attached inline as image content blocks in the same message - you can see them directly. Do NOT call web_fetch on image URLs; that will fail or return garbage. Just look at the image block and answer.
|
|
285
288
|
- Most attached files (office documents like .docx/.xlsx/.pptx/.hwp/.hwpx/.ods, and text/data/code files like .csv/.tsv/.json/.xml/.txt/.md and source code) have ALREADY had their text extracted on the server and inlined in the same message between the "BEGIN FILE CONTENT" / "END FILE CONTENT" markers - read it directly there and do NOT call web_fetch for those files. A "[skapi: ...]" note in that block means the file could not be extracted.
|
|
@@ -614,6 +617,15 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
614
617
|
if (/^https?:\/\/[^/\s]+\/download\/[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)?$/i.test(stripped)) return stripped;
|
|
615
618
|
return href.trim().replace(/\s/g, "%20");
|
|
616
619
|
}
|
|
620
|
+
function repairUrlEntities(href) {
|
|
621
|
+
if (!href || href.indexOf("&") === -1) return href;
|
|
622
|
+
var out = href, prev = "";
|
|
623
|
+
while (out !== prev) {
|
|
624
|
+
prev = out;
|
|
625
|
+
out = out.replace(/&/gi, "&").replace(/�*38;/g, "&").replace(/�*26;/gi, "&");
|
|
626
|
+
}
|
|
627
|
+
return out;
|
|
628
|
+
}
|
|
617
629
|
function normalizeTrailingInlineToken(value) {
|
|
618
630
|
if (!value) return value;
|
|
619
631
|
var out = value.replace(/[.,;:!?]+$/, "");
|
|
@@ -661,8 +673,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
661
673
|
var tail = full.slice(("src::" + rawPath).length);
|
|
662
674
|
var srcIsUrl = isHttpUrlLike(rawPath);
|
|
663
675
|
if (srcIsUrl && !isDbHost(rawPath) && !readExpiredAttachmentHref(rawPath)) {
|
|
676
|
+
var srcUrl = repairUrlEntities(rawPath);
|
|
664
677
|
return {
|
|
665
|
-
part: { type: "link", label: truncateLabelForDisplay(
|
|
678
|
+
part: { type: "link", label: truncateLabelForDisplay(srcUrl), fullLabel: srcUrl, href: srcUrl, expired: false },
|
|
666
679
|
tail
|
|
667
680
|
};
|
|
668
681
|
}
|
|
@@ -696,6 +709,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
696
709
|
}
|
|
697
710
|
var originalHref = g3 || g6 || "";
|
|
698
711
|
if (!originalHref) return null;
|
|
712
|
+
originalHref = repairUrlEntities(originalHref);
|
|
699
713
|
var urlTail;
|
|
700
714
|
if (!g3 && g6) {
|
|
701
715
|
var trimmedUrl = normalizeTrailingInlineToken(originalHref);
|
|
@@ -737,24 +751,63 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
737
751
|
// src/engine/budget.ts
|
|
738
752
|
var CONTEXT_WINDOW_DEFAULT = { claude: 2e5, openai: 128e3 };
|
|
739
753
|
var CONTEXT_WINDOW_BY_MODEL = {
|
|
740
|
-
|
|
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,
|
|
741
760
|
"claude-sonnet-4": 2e5,
|
|
742
|
-
"
|
|
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
|
|
743
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
|
+
}
|
|
744
784
|
var OUTPUT_TOKEN_RESERVE = 22e3;
|
|
745
785
|
var TOOL_AND_RESPONSE_BUFFER = 4e3;
|
|
746
786
|
var MIN_INPUT_TOKEN_BUDGET = 8e3;
|
|
747
787
|
var CLAUDE_PER_REQUEST_INPUT_CAP = 28e3;
|
|
788
|
+
var MAX_HISTORY_MESSAGES = 20;
|
|
748
789
|
var HISTORY_TOKEN_BUDGET = 8e3;
|
|
790
|
+
var CLAUDE_INPUT_CAP_RATIO = 0.16;
|
|
791
|
+
var HISTORY_BUDGET_RATIO = 0.08;
|
|
749
792
|
function estimateTextTokens(text) {
|
|
750
793
|
return Math.ceil((text || "").length / 3);
|
|
751
794
|
}
|
|
752
795
|
function estimateMessageTokens(msg) {
|
|
753
796
|
return estimateTextTokens(msg.content) + estimateTextTokens(msg.role) + 6;
|
|
754
797
|
}
|
|
755
|
-
function getContextWindow(platform, model) {
|
|
798
|
+
function getContextWindow(platform, model, serviceId) {
|
|
799
|
+
var override = serviceId ? getProjectContextWindow(serviceId) : null;
|
|
800
|
+
if (override) return override;
|
|
756
801
|
var normalized = (model || "").trim().toLowerCase();
|
|
757
|
-
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
|
+
}
|
|
758
811
|
return CONTEXT_WINDOW_DEFAULT[platform];
|
|
759
812
|
}
|
|
760
813
|
function stripFileBlocksFromHistory(content) {
|
|
@@ -762,15 +815,19 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
762
815
|
return content.replace(/```([^\n`]+?\.[^\s.`]+)\n[\s\S]*?```/g, "[file previously attached: $1]");
|
|
763
816
|
}
|
|
764
817
|
function buildBoundedChatMessages(options) {
|
|
765
|
-
var contextWindow = getContextWindow(options.platform, options.model);
|
|
818
|
+
var contextWindow = getContextWindow(options.platform, options.model, options.serviceId);
|
|
766
819
|
var contextBasedBudget = Math.max(
|
|
767
820
|
MIN_INPUT_TOKEN_BUDGET,
|
|
768
821
|
contextWindow - OUTPUT_TOKEN_RESERVE - TOOL_AND_RESPONSE_BUFFER
|
|
769
822
|
);
|
|
770
|
-
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;
|
|
771
826
|
var systemCost = estimateTextTokens(options.systemPrompt) + 12;
|
|
772
|
-
var
|
|
773
|
-
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);
|
|
774
831
|
var latestIndex = windowed.length - 1;
|
|
775
832
|
var trimmed = windowed.map(function(m, i2) {
|
|
776
833
|
if (i2 === latestIndex) return m;
|
|
@@ -795,6 +852,51 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
795
852
|
};
|
|
796
853
|
}
|
|
797
854
|
|
|
855
|
+
// src/engine/time.ts
|
|
856
|
+
function wallClockNow() {
|
|
857
|
+
return Date.now();
|
|
858
|
+
}
|
|
859
|
+
function formatChatTimestamp(ms) {
|
|
860
|
+
if (typeof ms !== "number" || !isFinite(ms) || ms <= 0) return "";
|
|
861
|
+
try {
|
|
862
|
+
return new Date(ms).toLocaleString(void 0, {
|
|
863
|
+
year: "numeric",
|
|
864
|
+
month: "short",
|
|
865
|
+
day: "numeric",
|
|
866
|
+
hour: "numeric",
|
|
867
|
+
minute: "2-digit",
|
|
868
|
+
second: "2-digit"
|
|
869
|
+
});
|
|
870
|
+
} catch (e) {
|
|
871
|
+
return "";
|
|
872
|
+
}
|
|
873
|
+
}
|
|
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
|
+
|
|
798
900
|
// src/engine/requests.ts
|
|
799
901
|
var ANTHROPIC_MESSAGES_API_URL = "https://api.anthropic.com/v1/messages";
|
|
800
902
|
var ANTHROPIC_VERSION = "2023-06-01";
|
|
@@ -1276,7 +1378,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1276
1378
|
method: "POST"
|
|
1277
1379
|
},
|
|
1278
1380
|
{ service: params.service, owner: params.owner },
|
|
1279
|
-
params.queue ? { queue: params.queue } : {}
|
|
1381
|
+
params.queue ? { queue: params.queue } : {},
|
|
1382
|
+
params.status ? { status: params.status } : {}
|
|
1280
1383
|
);
|
|
1281
1384
|
return chatEngineConfig().clientSecretRequestHistory(
|
|
1282
1385
|
p,
|
|
@@ -1313,6 +1416,25 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1313
1416
|
}
|
|
1314
1417
|
return "";
|
|
1315
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
|
+
}
|
|
1316
1438
|
function mapHistoryListToMessages(list, platform, opts) {
|
|
1317
1439
|
var mapped = [], runningItemIds = [];
|
|
1318
1440
|
var extractAssistantText = platform === "openai" ? extractOpenAIText : extractClaudeText;
|
|
@@ -1329,31 +1451,25 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1329
1451
|
var assistantText = isPending ? "" : (extractAssistantText(response) || "").trim() || "";
|
|
1330
1452
|
var isErrorResponse = !isPending && (isFailed || isErrorResponseBody(response));
|
|
1331
1453
|
var serverItemId = item && typeof item.id === "string" && item.id ? item.id : void 0;
|
|
1454
|
+
var createdTs = Number(item && item.created);
|
|
1455
|
+
var updatedTs = Number(item && item.updated);
|
|
1456
|
+
var userTs = isFinite(createdTs) && createdTs > 0 ? createdTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : void 0;
|
|
1457
|
+
var replyTs = isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
1332
1458
|
if (userText) {
|
|
1333
1459
|
var displayContent;
|
|
1334
1460
|
var indexFile = void 0;
|
|
1335
1461
|
if (item._isBgTask) {
|
|
1336
|
-
var
|
|
1337
|
-
if (
|
|
1338
|
-
var mimeMatch = userText.match(/^- mime type: (.+)$/m);
|
|
1339
|
-
var sizeMatch = userText.match(/^- size \(bytes\): (\d+)$/m);
|
|
1340
|
-
var pathMatch = userText.match(/^- storage path: (.+)$/m);
|
|
1341
|
-
var isContinuePass = userText.indexOf("CONTINUE indexing") === 0;
|
|
1462
|
+
var ref = parseIndexingRequestText(userText);
|
|
1463
|
+
if (ref) {
|
|
1342
1464
|
displayContent = opts.formatIndexingLabel(
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1465
|
+
ref.name,
|
|
1466
|
+
ref.mime || "",
|
|
1467
|
+
typeof ref.size === "number" ? ref.size : null,
|
|
1468
|
+
ref.path,
|
|
1347
1469
|
false,
|
|
1348
|
-
|
|
1470
|
+
ref.continued
|
|
1349
1471
|
);
|
|
1350
|
-
indexFile =
|
|
1351
|
-
name: nameMatch[1].trim(),
|
|
1352
|
-
path: pathMatch ? pathMatch[1].trim() : void 0,
|
|
1353
|
-
mime: mimeMatch ? mimeMatch[1].trim() : void 0,
|
|
1354
|
-
size: sizeMatch ? Number(sizeMatch[1]) : void 0,
|
|
1355
|
-
continued: isContinuePass
|
|
1356
|
-
};
|
|
1472
|
+
indexFile = ref;
|
|
1357
1473
|
} else {
|
|
1358
1474
|
displayContent = userText;
|
|
1359
1475
|
}
|
|
@@ -1368,6 +1484,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1368
1484
|
if (indexFile) userMsg._indexFile = indexFile;
|
|
1369
1485
|
if (item._isOnBgQueue) userMsg._useBgQueue = true;
|
|
1370
1486
|
if (serverItemId !== void 0) userMsg._serverItemId = serverItemId;
|
|
1487
|
+
if (userTs !== void 0) userMsg._ts = userTs;
|
|
1371
1488
|
mapped.push(userMsg);
|
|
1372
1489
|
}
|
|
1373
1490
|
if (isCancelledItem) ; else if (isInProcess) {
|
|
@@ -1382,11 +1499,13 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1382
1499
|
var em = { role: "assistant", content: getErrorMessage(response), isError: true };
|
|
1383
1500
|
if (item._isBgTask) em.isBackgroundTask = true;
|
|
1384
1501
|
if (serverItemId !== void 0) em._serverItemId = serverItemId;
|
|
1502
|
+
if (replyTs !== void 0) em._ts = replyTs;
|
|
1385
1503
|
mapped.push(em);
|
|
1386
1504
|
} else if (assistantText) {
|
|
1387
1505
|
var okm = { role: "assistant", content: sanitizeAttachmentLinksForHistory(assistantText, opts.serviceId, true) };
|
|
1388
1506
|
if (item._isBgTask) okm.isBackgroundTask = true;
|
|
1389
1507
|
if (serverItemId !== void 0) okm._serverItemId = serverItemId;
|
|
1508
|
+
if (replyTs !== void 0) okm._ts = replyTs;
|
|
1390
1509
|
mapped.push(okm);
|
|
1391
1510
|
}
|
|
1392
1511
|
});
|
|
@@ -1479,6 +1598,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1479
1598
|
}
|
|
1480
1599
|
|
|
1481
1600
|
// src/engine/session.ts
|
|
1601
|
+
var WORKER_PASS_ADOPT_LIMIT = 20;
|
|
1602
|
+
var WORKER_PASS_ADOPT_ATTEMPTS = [0, 2e3, 6e3];
|
|
1482
1603
|
var _g = typeof globalThis !== "undefined" ? globalThis : {};
|
|
1483
1604
|
function nowMs() {
|
|
1484
1605
|
return _g.performance && typeof _g.performance.now === "function" ? _g.performance.now() : Date.now();
|
|
@@ -1498,6 +1619,35 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1498
1619
|
var ChatSession = class {
|
|
1499
1620
|
constructor(host) {
|
|
1500
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;
|
|
1501
1651
|
this.host = host;
|
|
1502
1652
|
this.state = {
|
|
1503
1653
|
messages: [],
|
|
@@ -1799,7 +1949,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1799
1949
|
var offExisting = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1800
1950
|
this.aiChatHistoryCache[key] = {
|
|
1801
1951
|
messages: offExisting.messages.concat([
|
|
1802
|
-
{ role: "user", content: composed, _ownerKey: key },
|
|
1952
|
+
{ role: "user", content: composed, _ownerKey: key, _ts: wallClockNow() },
|
|
1803
1953
|
{ role: "assistant", content: "", isPending: true, isPendingInProcess: true, _ownerKey: key }
|
|
1804
1954
|
]),
|
|
1805
1955
|
endOfList: offExisting.endOfList,
|
|
@@ -1831,7 +1981,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1831
1981
|
serviceId: id.serviceId,
|
|
1832
1982
|
history: resolvedHistory.concat([{ role: "user", content: llmComposed }])
|
|
1833
1983
|
});
|
|
1834
|
-
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true };
|
|
1984
|
+
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true, _ts: wallClockNow() };
|
|
1835
1985
|
if (key) queuedBubble._ownerKey = key;
|
|
1836
1986
|
if (useBgQueue) queuedBubble._useBgQueue = true;
|
|
1837
1987
|
this.state.messages.push(queuedBubble);
|
|
@@ -1866,7 +2016,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1866
2016
|
});
|
|
1867
2017
|
return;
|
|
1868
2018
|
}
|
|
1869
|
-
this.state.messages.push({ role: "user", content: composed, ...key ? { _ownerKey: key } : {} });
|
|
2019
|
+
this.state.messages.push({ role: "user", content: composed, _ts: wallClockNow(), ...key ? { _ownerKey: key } : {} });
|
|
1870
2020
|
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} });
|
|
1871
2021
|
this.host.notify();
|
|
1872
2022
|
this.updateHistoryCache();
|
|
@@ -1923,6 +2073,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1923
2073
|
var existing = this.state.messages[nextIdx];
|
|
1924
2074
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true, isBackgroundTask: true };
|
|
1925
2075
|
if (existing._indexFile) promoted._indexFile = existing._indexFile;
|
|
2076
|
+
if (existing._ts !== void 0) promoted._ts = existing._ts;
|
|
1926
2077
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1927
2078
|
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1928
2079
|
this.state.messages[nextIdx] = promoted;
|
|
@@ -1944,6 +2095,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1944
2095
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true };
|
|
1945
2096
|
if (existing.isBackgroundTask) promoted.isBackgroundTask = true;
|
|
1946
2097
|
if (existing._indexFile) promoted._indexFile = existing._indexFile;
|
|
2098
|
+
if (existing._ts !== void 0) promoted._ts = existing._ts;
|
|
1947
2099
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1948
2100
|
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1949
2101
|
if (existing.isSendingToServer) promoted.isSendingToServer = true;
|
|
@@ -1993,6 +2145,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1993
2145
|
var repl = { role: "user", content: exist.content };
|
|
1994
2146
|
if (exist._serverItemId !== void 0) repl._serverItemId = exist._serverItemId;
|
|
1995
2147
|
if (exist._ownerKey !== void 0) repl._ownerKey = exist._ownerKey;
|
|
2148
|
+
if (exist._ts !== void 0) repl._ts = exist._ts;
|
|
1996
2149
|
this.state.messages[userIdx] = repl;
|
|
1997
2150
|
}
|
|
1998
2151
|
var thinkingIdx = userIdx >= 0 ? this.state.messages.findIndex(function(m, i) {
|
|
@@ -2001,6 +2154,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2001
2154
|
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
2002
2155
|
}
|
|
2003
2156
|
insertAtTarget(msg, targetIdx) {
|
|
2157
|
+
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts = wallClockNow();
|
|
2004
2158
|
if (targetIdx >= 0 && this.state.messages[targetIdx] && this.state.messages[targetIdx].isPending) this.state.messages[targetIdx] = msg;
|
|
2005
2159
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
2006
2160
|
else this.state.messages.push(msg);
|
|
@@ -2337,6 +2491,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2337
2491
|
}
|
|
2338
2492
|
enqueueTypewrite(idx, fullText, localId) {
|
|
2339
2493
|
var self = this;
|
|
2494
|
+
var target = this.state.messages[idx];
|
|
2495
|
+
if (target && target._ts === void 0) target._ts = wallClockNow();
|
|
2340
2496
|
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
2341
2497
|
return self.typewriteIntoIndex(idx, fullText, localId);
|
|
2342
2498
|
});
|
|
@@ -2406,6 +2562,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2406
2562
|
var u = this.state.messages[uIdx];
|
|
2407
2563
|
var cleaned = { role: "user", content: u.content, _serverItemId: itemId };
|
|
2408
2564
|
if (u.isBackgroundTask) cleaned.isBackgroundTask = true;
|
|
2565
|
+
if (u._ts !== void 0) cleaned._ts = u._ts;
|
|
2409
2566
|
if (u._indexFile) cleaned._indexFile = u._indexFile;
|
|
2410
2567
|
this.state.messages[uIdx] = cleaned;
|
|
2411
2568
|
}
|
|
@@ -2436,8 +2593,21 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2436
2593
|
}
|
|
2437
2594
|
// --- background-task resolution + drain -------------------------------
|
|
2438
2595
|
handleHistoryItemResolution(itemId, response, platform) {
|
|
2596
|
+
var indexRef = this._indexRefOfItem(itemId);
|
|
2439
2597
|
this.applyHistoryItemResolution(itemId, response, platform);
|
|
2440
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;
|
|
2441
2611
|
}
|
|
2442
2612
|
applyHistoryItemResolution(itemId, response, platform) {
|
|
2443
2613
|
this.historyItemPolls.delete(itemId);
|
|
@@ -2478,6 +2648,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2478
2648
|
var ex = this.state.messages[userIdx];
|
|
2479
2649
|
var settledUser = { role: "user", content: ex.content, _serverItemId: itemId };
|
|
2480
2650
|
if (ex.isBackgroundTask) settledUser.isBackgroundTask = true;
|
|
2651
|
+
if (ex._ts !== void 0) settledUser._ts = ex._ts;
|
|
2481
2652
|
if (ex._indexFile) settledUser._indexFile = ex._indexFile;
|
|
2482
2653
|
if (ex._useBgQueue) settledUser._useBgQueue = true;
|
|
2483
2654
|
this.state.messages[userIdx] = settledUser;
|
|
@@ -2570,6 +2741,116 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2570
2741
|
self.cancelQueuedMessage(t.msg, idx === -1 ? t.idx : idx);
|
|
2571
2742
|
});
|
|
2572
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
|
+
}
|
|
2573
2854
|
/** Best-effort server-side cancel of a bg-queue item that has no bubble (so
|
|
2574
2855
|
* cancelQueuedMessage, which drives one, has nothing to act on). */
|
|
2575
2856
|
_cancelServerItem(serverId) {
|
|
@@ -2780,8 +3061,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2780
3061
|
var chatList = history && Array.isArray(history.list) ? history.list : [];
|
|
2781
3062
|
chatList.forEach(function(item) {
|
|
2782
3063
|
if (isBgIndexingQueue(item.queue_name)) {
|
|
2783
|
-
|
|
2784
|
-
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;
|
|
2785
3065
|
else item._isOnBgQueue = true;
|
|
2786
3066
|
}
|
|
2787
3067
|
});
|
|
@@ -3254,7 +3534,10 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3254
3534
|
cancellableIds: [],
|
|
3255
3535
|
cancelling: false,
|
|
3256
3536
|
mayHaveOlder: false,
|
|
3257
|
-
|
|
3537
|
+
// The run's first loaded pass, and never re-stamped: see the file
|
|
3538
|
+
// docstring. `anchorId` is filled in once every member is known.
|
|
3539
|
+
anchorIndex: i,
|
|
3540
|
+
anchorId: ""
|
|
3258
3541
|
};
|
|
3259
3542
|
order.push(runId);
|
|
3260
3543
|
}
|
|
@@ -3268,7 +3551,6 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3268
3551
|
g.passCount++;
|
|
3269
3552
|
}
|
|
3270
3553
|
g.members.push({ msg, index: i });
|
|
3271
|
-
g.anchorIndex = i;
|
|
3272
3554
|
runOfIndex[i] = runId;
|
|
3273
3555
|
if (msg._serverItemId) runByItemId[msg._serverItemId] = runId;
|
|
3274
3556
|
if (ref && ref.name) keyByName[ref.name] = g.key;
|
|
@@ -3330,6 +3612,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3330
3612
|
}
|
|
3331
3613
|
}
|
|
3332
3614
|
grp.mayHaveOlder = !sawFirstPass && hasMoreHistory;
|
|
3615
|
+
var anchor = grp.members[0];
|
|
3616
|
+
grp.anchorIndex = anchor.index;
|
|
3617
|
+
grp.anchorId = anchor.msg._serverItemId || anchor.msg._localId || "";
|
|
3333
3618
|
}
|
|
3334
3619
|
var out = [];
|
|
3335
3620
|
for (var j = 0; j < list.length; j++) {
|
|
@@ -3347,7 +3632,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3347
3632
|
(function() {
|
|
3348
3633
|
var MCP_PROD = "https://mcp.broadwayinc.computer";
|
|
3349
3634
|
var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
|
|
3350
|
-
var BQ_VERSION = "1.
|
|
3635
|
+
var BQ_VERSION = "1.8.1" ;
|
|
3351
3636
|
var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
|
|
3352
3637
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
3353
3638
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -6170,12 +6455,14 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6170
6455
|
});
|
|
6171
6456
|
bubble.appendChild(cancelBtn);
|
|
6172
6457
|
}
|
|
6173
|
-
var md = h("div", { class: "bq-md", html: parseMsgPartsHtml(msg.content) });
|
|
6458
|
+
var md = h("div", { class: "bq-md", translate: "no", html: parseMsgPartsHtml(msg.content) });
|
|
6174
6459
|
md.addEventListener("click", onBubbleLinkClick);
|
|
6175
6460
|
bubble.appendChild(md);
|
|
6176
6461
|
if (msg.isPendingQueued) bubble.appendChild(h("span", { class: "bq-pending-note", text: "(In queue)" }));
|
|
6177
6462
|
if (msg.isCancelled) bubble.appendChild(h("span", { class: "bq-cancel-error", text: "(cancelled)" }));
|
|
6178
6463
|
if (msg._cancelError) bubble.appendChild(h("span", { class: "bq-cancel-error", text: msg._cancelError }));
|
|
6464
|
+
var ts = formatChatTimestamp(msg._ts);
|
|
6465
|
+
if (ts) bubble.appendChild(h("time", { class: "bq-msg-time", text: ts }));
|
|
6179
6466
|
}
|
|
6180
6467
|
return h("div", { class: cls.join(" "), dataset: { msgIndex: String(idx) } }, bubble);
|
|
6181
6468
|
}
|
|
@@ -6296,8 +6583,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6296
6583
|
return id ? "s" + id + ":" + msg.role : "i" + index;
|
|
6297
6584
|
}
|
|
6298
6585
|
function indexGroupAnchorId(group) {
|
|
6299
|
-
|
|
6300
|
-
return last && last.msg && last.msg._serverItemId || "";
|
|
6586
|
+
return group.anchorId || "";
|
|
6301
6587
|
}
|
|
6302
6588
|
function captureScrollAnchor() {
|
|
6303
6589
|
var box = CS.messagesBox;
|
|
@@ -6648,7 +6934,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6648
6934
|
if (S.aiPlatform === "none") return "No agent configured";
|
|
6649
6935
|
return S.serviceName || "BunnyQuery";
|
|
6650
6936
|
}
|
|
6651
|
-
function
|
|
6937
|
+
function parseAiAgentValue2(value) {
|
|
6652
6938
|
var raw = (value || "").trim();
|
|
6653
6939
|
var platform = raw, model = "";
|
|
6654
6940
|
if (raw.indexOf("#") !== -1) {
|
|
@@ -6662,9 +6948,10 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6662
6948
|
function applyAgentConfig() {
|
|
6663
6949
|
var conn = S.service || {};
|
|
6664
6950
|
var raw = conn.ai_agent || "";
|
|
6665
|
-
var parsed =
|
|
6951
|
+
var parsed = parseAiAgentValue2(raw);
|
|
6666
6952
|
S.aiPlatform = parsed.platform;
|
|
6667
6953
|
S.aiModel = parsed.model;
|
|
6954
|
+
setProjectContextWindow(S.serviceId, parseAiAgentValue(raw).contextWindow);
|
|
6668
6955
|
S.serviceName = conn.service_name || "";
|
|
6669
6956
|
S.serviceDescription = conn.service_description || "";
|
|
6670
6957
|
}
|
|
@@ -6806,6 +7093,15 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6806
7093
|
},
|
|
6807
7094
|
mcpBaseUrl: mcpBaseUrl(),
|
|
6808
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,
|
|
6809
7105
|
// Client-side attachment parsers (e.g. an .hwp parser) passed via init opts.
|
|
6810
7106
|
attachmentParsers: S.opts.attachmentParsers || void 0
|
|
6811
7107
|
});
|