bunnyquery 1.6.0 → 1.6.2
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 +406 -83
- package/dist/engine.cjs +398 -81
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +132 -9
- package/dist/engine.d.ts +132 -9
- package/dist/engine.mjs +395 -82
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/config.ts +19 -0
- package/src/engine/host.ts +19 -0
- package/src/engine/index.ts +2 -1
- package/src/engine/office.ts +53 -5
- package/src/engine/prompts/index.ts +3 -0
- package/src/engine/prompts/indexing_user_message.ts +106 -29
- package/src/engine/requests.ts +106 -19
- package/src/engine/session.ts +350 -55
package/dist/engine.cjs
CHANGED
|
@@ -61,6 +61,9 @@ function chatEngineConfig() {
|
|
|
61
61
|
}
|
|
62
62
|
return _config;
|
|
63
63
|
}
|
|
64
|
+
function windowedIndexingEnabled() {
|
|
65
|
+
return _config?.windowedIndexing === true;
|
|
66
|
+
}
|
|
64
67
|
function pollOpt() {
|
|
65
68
|
const p = _config?.poll;
|
|
66
69
|
return p === void 0 ? {} : { poll: p };
|
|
@@ -155,7 +158,32 @@ function isServerExtractable(name, mime) {
|
|
|
155
158
|
return m.includes("officedocument") || m.includes("opendocument") || m.includes("hwp") || m.includes("epub") || m === "application/msword" || m === "application/vnd.ms-excel" || m === "application/vnd.ms-powerpoint";
|
|
156
159
|
}
|
|
157
160
|
var isOfficeFile = isServerExtractable;
|
|
158
|
-
var PAGED_READ_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
161
|
+
var PAGED_READ_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
162
|
+
// grids
|
|
163
|
+
"xls",
|
|
164
|
+
"xlsx",
|
|
165
|
+
"xlsm",
|
|
166
|
+
"ods",
|
|
167
|
+
// delimited text (row-windowed by the layer)
|
|
168
|
+
"csv",
|
|
169
|
+
"tsv",
|
|
170
|
+
"tab",
|
|
171
|
+
// documents
|
|
172
|
+
"pdf",
|
|
173
|
+
"docx",
|
|
174
|
+
"pptx",
|
|
175
|
+
// plain text / data / markup
|
|
176
|
+
"txt",
|
|
177
|
+
"md",
|
|
178
|
+
"markdown",
|
|
179
|
+
"log",
|
|
180
|
+
"json",
|
|
181
|
+
"jsonl",
|
|
182
|
+
"ndjson",
|
|
183
|
+
"xml",
|
|
184
|
+
"yaml",
|
|
185
|
+
"yml"
|
|
186
|
+
]);
|
|
159
187
|
function isPagedReadFile(name, mime) {
|
|
160
188
|
const ext = (name || "").split(".").pop()?.toLowerCase() || "";
|
|
161
189
|
if (PAGED_READ_EXTENSIONS.has(ext)) return true;
|
|
@@ -179,6 +207,16 @@ function makeRenderPlaceholder(seed) {
|
|
|
179
207
|
return `{{SKAPI_RENDER::${slug}-${_renderPlaceholderSeq}}}`;
|
|
180
208
|
}
|
|
181
209
|
var RENDER_PAGES_PER_WINDOW = 5;
|
|
210
|
+
var _windowPlaceholderSeq = 0;
|
|
211
|
+
function makeWindowPlaceholder(seed) {
|
|
212
|
+
_windowPlaceholderSeq += 1;
|
|
213
|
+
const slug = (seed || "file").replace(/[^a-zA-Z0-9]+/g, "_").slice(-48);
|
|
214
|
+
return `{{SKAPI_WINDOW::${slug}-${_windowPlaceholderSeq}}}`;
|
|
215
|
+
}
|
|
216
|
+
function isWindowedReadFile(name, mime) {
|
|
217
|
+
if (isImageVisionFile(name, mime)) return false;
|
|
218
|
+
return isPagedReadFile(name, mime);
|
|
219
|
+
}
|
|
182
220
|
function composeUserMessage(text, attachmentUrls) {
|
|
183
221
|
let composed = text;
|
|
184
222
|
if (attachmentUrls.length > 0) {
|
|
@@ -326,32 +364,58 @@ Read this file with the readFileContent tool, using the storage path above - do
|
|
|
326
364
|
}
|
|
327
365
|
return head + `- temporary URL (fetch this to read the file contents): ${attachment.url}`;
|
|
328
366
|
}
|
|
367
|
+
var RENDER_FROM_TOKEN = "{{RENDER_FROM}}";
|
|
368
|
+
var WINDOW_CURSOR_TOKEN = RENDER_FROM_TOKEN;
|
|
329
369
|
function buildIndexingRenderMessage(attachment, placeholder, renderFrom) {
|
|
330
370
|
const from = Math.max(0, renderFrom || 0);
|
|
371
|
+
if (from > 0) return buildIndexingRenderContinueTemplate(attachment, placeholder, String(from + 1));
|
|
372
|
+
return `A new file has just been uploaded. Index it now.
|
|
373
|
+
|
|
374
|
+
` + buildRenderMeta(attachment) + `
|
|
375
|
+
This is a PDF. Its pages are delivered to you as RENDERED PAGE IMAGES embedded directly in this message (you do NOT need any tool, URL, or web_fetch to see them). You are shown a WINDOW of pages at a time, starting at page ${from + 1}.
|
|
376
|
+
` + buildRenderDatafy(placeholder);
|
|
377
|
+
}
|
|
378
|
+
function buildIndexingRenderContinueTemplate(attachment, placeholder, pageLabel = RENDER_FROM_TOKEN) {
|
|
331
379
|
const src = `src::${attachment.storagePath}`;
|
|
332
|
-
|
|
380
|
+
return `CONTINUE indexing a PDF whose previous pass did not finish.
|
|
381
|
+
|
|
382
|
+
` + buildRenderMeta(attachment) + `
|
|
383
|
+
Records for the earlier pages are ALREADY saved (they reference "${src}"). The NEXT window of rendered page images (starting at page ${pageLabel}) is embedded in this message. Datafy each page as before and do NOT re-save pages that are already saved.
|
|
384
|
+
` + buildRenderDatafy(placeholder);
|
|
385
|
+
}
|
|
386
|
+
function buildRenderMeta(attachment) {
|
|
387
|
+
return `File metadata:
|
|
333
388
|
- name: ${attachment.name}
|
|
334
389
|
- storage path: ${attachment.storagePath}
|
|
335
390
|
` + (attachment.mime ? `- mime type: ${attachment.mime}
|
|
336
391
|
` : "");
|
|
337
|
-
|
|
392
|
+
}
|
|
393
|
+
function buildRenderDatafy(placeholder) {
|
|
394
|
+
return `
|
|
338
395
|
${placeholder}
|
|
339
396
|
|
|
340
397
|
LOOK at each rendered page image in this message and DATAFY what it shows: for EVERY page call postRecords and save records - one record per row / table entry / line item visible on the page (or one record for the page if it is prose), capturing every value you can read (OCR the text, read tables cell by cell, describe any photos/diagrams). Use the storage path above for the "src::" unique_id.
|
|
341
398
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
399
|
+
Save records for THIS window of pages only, then stop and report what you saved. Do NOT try to read the rest of the file and do NOT worry about the pages after this window: if any remain, the next window is rendered and sent to you automatically. Report only the pages you were actually shown - never imply you have seen the whole document.`;
|
|
400
|
+
}
|
|
401
|
+
function buildIndexingWindowMessage(attachment, placeholder, isContinuation, positionLabel) {
|
|
402
|
+
const src = `src::${attachment.storagePath}`;
|
|
403
|
+
const head = isContinuation ? `CONTINUE indexing a file whose previous pass did not finish.
|
|
345
404
|
|
|
346
|
-
`
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
405
|
+
` : `A new file has just been uploaded. Index it now.
|
|
406
|
+
|
|
407
|
+
`;
|
|
408
|
+
const where = isContinuation ? `
|
|
409
|
+
Records for the earlier windows are ALREADY saved (they reference "${src}"). The NEXT window (starting at ${positionLabel || WINDOW_CURSOR_TOKEN}) is embedded below. Do NOT re-save windows that are already saved.
|
|
410
|
+
` : `
|
|
411
|
+
This file is delivered to you ONE WINDOW at a time, embedded directly in this message. You do NOT need any tool, URL, or web_fetch to read it.
|
|
412
|
+
`;
|
|
413
|
+
return head + buildRenderMeta(attachment) + where + `
|
|
414
|
+
${placeholder}
|
|
351
415
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
416
|
+
DATAFY this window: call postRecords and save records for everything in it - ONE RECORD PER ROW for tabular data (keyed by the column headers), or one record per section for prose. Capture every value you can read. Use the storage path above for the "src::" unique_id on the file-level record, and link every row/section record to it by reference.
|
|
417
|
+
|
|
418
|
+
Save records for THIS window only, then stop and report what you saved. Do NOT try to read the rest of the file, and do NOT call readFileContent - if more remains, the next window is read and sent to you automatically. Report only what you were actually shown, and never imply you have seen the whole file when the note beside the window says more remains.`;
|
|
355
419
|
}
|
|
356
420
|
function buildIndexingContinueMessage(attachment) {
|
|
357
421
|
const src = `src::${attachment.storagePath}`;
|
|
@@ -624,19 +688,22 @@ var mcpUrl = () => chatEngineConfig().mcpBaseUrl;
|
|
|
624
688
|
var clientSecretRequest = (opts) => chatEngineConfig().clientSecretRequest(opts);
|
|
625
689
|
var getOpenAIImageDetail = (model) => {
|
|
626
690
|
const normalized = (model || DEFAULT_OPENAI_MODEL).trim().toLowerCase();
|
|
627
|
-
const match = normalized.match(/^gpt-(\d+)(?:\.(\d+))?$/);
|
|
691
|
+
const match = normalized.match(/^gpt-(\d+)(?:\.(\d+))?(-[a-z0-9.\-]+)?$/);
|
|
628
692
|
if (!match) {
|
|
629
693
|
return DEFAULT_OPENAI_IMAGE_DETAIL;
|
|
630
694
|
}
|
|
631
695
|
const major = Number(match[1]);
|
|
632
696
|
const minor = match[2] === void 0 ? null : Number(match[2]);
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
return "original";
|
|
697
|
+
const isVariant = !!match[3];
|
|
698
|
+
const supportsOriginal = major > 5 || major === 5 && minor !== null && minor >= 4;
|
|
699
|
+
if (!supportsOriginal) {
|
|
700
|
+
return DEFAULT_OPENAI_IMAGE_DETAIL;
|
|
638
701
|
}
|
|
639
|
-
return
|
|
702
|
+
return isVariant ? "high" : "original";
|
|
703
|
+
};
|
|
704
|
+
var getRenderImageDetail = (model) => {
|
|
705
|
+
const detail = getOpenAIImageDetail(model);
|
|
706
|
+
return detail === DEFAULT_OPENAI_IMAGE_DETAIL ? "high" : detail;
|
|
640
707
|
};
|
|
641
708
|
var IMAGE_URL_REGEX = /\bhttps?:\/\/[^\s<>"'()\[\]]+?\.(?:jpg|jpeg|png|gif|webp)(?:\?[^\s<>"'()\[\]]*)?/gi;
|
|
642
709
|
function transformContentWithImages(content) {
|
|
@@ -881,17 +948,44 @@ async function notifyAgentSaveAttachment(info) {
|
|
|
881
948
|
const visionFile = !parsedContent && isImageVisionFile(attachment.name, attachment.mime);
|
|
882
949
|
const renderFrom = Math.max(0, info.renderFrom || 0);
|
|
883
950
|
const renderPlaceholder = visionFile ? makeRenderPlaceholder(attachment.storagePath) : void 0;
|
|
951
|
+
const renderDetail = platform === "openai" ? getRenderImageDetail(info.model || DEFAULT_OPENAI_MODEL) : void 0;
|
|
884
952
|
const skapiRender = visionFile && renderPlaceholder ? {
|
|
885
953
|
_skapi_render: [
|
|
886
|
-
{
|
|
954
|
+
{
|
|
955
|
+
path: attachment.storagePath,
|
|
956
|
+
from: renderFrom,
|
|
957
|
+
count: RENDER_PAGES_PER_WINDOW,
|
|
958
|
+
placeholder: renderPlaceholder,
|
|
959
|
+
name: attachment.name,
|
|
960
|
+
mime: attachment.mime,
|
|
961
|
+
detail: renderDetail,
|
|
962
|
+
auto_continue: true,
|
|
963
|
+
continue_text: buildIndexingRenderContinueTemplate(attachment, renderPlaceholder)
|
|
964
|
+
}
|
|
965
|
+
]
|
|
966
|
+
} : {};
|
|
967
|
+
const windowedRead = !visionFile && !parsedContent && windowedIndexingEnabled() && isWindowedReadFile(attachment.name, attachment.mime);
|
|
968
|
+
const windowPlaceholder = windowedRead ? makeWindowPlaceholder(attachment.storagePath) : void 0;
|
|
969
|
+
const skapiWindow = windowedRead && windowPlaceholder ? {
|
|
970
|
+
_skapi_window: [
|
|
971
|
+
{
|
|
972
|
+
path: attachment.storagePath,
|
|
973
|
+
cursor: null,
|
|
974
|
+
placeholder: windowPlaceholder,
|
|
975
|
+
name: attachment.name,
|
|
976
|
+
mime: attachment.mime,
|
|
977
|
+
kind: "window",
|
|
978
|
+
auto_continue: true,
|
|
979
|
+
continue_text: buildIndexingWindowMessage(attachment, windowPlaceholder, true)
|
|
980
|
+
}
|
|
887
981
|
]
|
|
888
982
|
} : {};
|
|
889
|
-
const pagedRead = !visionFile && (continuing || !parsedContent && isPagedReadFile(attachment.name, attachment.mime));
|
|
890
|
-
const serverExtract = !visionFile && !continuing && !parsedContent && !pagedRead && isServerExtractable(attachment.name, attachment.mime);
|
|
983
|
+
const pagedRead = !visionFile && !windowedRead && (continuing || !parsedContent && isPagedReadFile(attachment.name, attachment.mime));
|
|
984
|
+
const serverExtract = !visionFile && !windowedRead && !continuing && !parsedContent && !pagedRead && isServerExtractable(attachment.name, attachment.mime);
|
|
891
985
|
const placeholder = serverExtract ? makeExtractPlaceholder(attachment.storagePath) : void 0;
|
|
892
986
|
const extractContent = serverExtract && placeholder ? [{ path: attachment.storagePath, placeholder, name: attachment.name, mime: attachment.mime }] : void 0;
|
|
893
987
|
const skapiExtract = extractContent && extractContent.length ? { _skapi_extract: extractContent } : {};
|
|
894
|
-
const userMessage = visionFile && renderPlaceholder ? buildIndexingRenderMessage(attachment, renderPlaceholder, renderFrom) : continuing ? buildIndexingContinueMessage(attachment) : buildIndexingUserMessage(
|
|
988
|
+
const userMessage = visionFile && renderPlaceholder ? buildIndexingRenderMessage(attachment, renderPlaceholder, renderFrom) : windowedRead && windowPlaceholder ? buildIndexingWindowMessage(attachment, windowPlaceholder, false) : continuing ? buildIndexingContinueMessage(attachment) : buildIndexingUserMessage(
|
|
895
989
|
attachment,
|
|
896
990
|
parsedContent ? { inlineContent: parsedContent } : placeholder ? { inlineContentPlaceholder: placeholder } : pagedRead ? { pagedRead: true } : void 0
|
|
897
991
|
);
|
|
@@ -920,6 +1014,7 @@ async function notifyAgentSaveAttachment(info) {
|
|
|
920
1014
|
max_output_tokens: MAX_TOKENS,
|
|
921
1015
|
...skapiExtract,
|
|
922
1016
|
...skapiRender,
|
|
1017
|
+
...skapiWindow,
|
|
923
1018
|
input: [
|
|
924
1019
|
{ role: "system", content: systemPrompt },
|
|
925
1020
|
{
|
|
@@ -965,6 +1060,7 @@ async function notifyAgentSaveAttachment(info) {
|
|
|
965
1060
|
max_tokens: MAX_TOKENS,
|
|
966
1061
|
...skapiExtract,
|
|
967
1062
|
...skapiRender,
|
|
1063
|
+
...skapiWindow,
|
|
968
1064
|
system: [
|
|
969
1065
|
{
|
|
970
1066
|
type: "text",
|
|
@@ -1061,9 +1157,15 @@ async function listOpenAIModels(service, owner) {
|
|
|
1061
1157
|
});
|
|
1062
1158
|
}
|
|
1063
1159
|
var BG_INDEXING_QUEUE_SUFFIX = "-bg";
|
|
1160
|
+
function isBgIndexingQueue(queueName) {
|
|
1161
|
+
if (typeof queueName !== "string" || !queueName) return false;
|
|
1162
|
+
const prefix = queueName.split("|")[0];
|
|
1163
|
+
const idx = prefix.lastIndexOf(":");
|
|
1164
|
+
const name = idx === -1 ? prefix : prefix.slice(idx + 1);
|
|
1165
|
+
return name.slice(-BG_INDEXING_QUEUE_SUFFIX.length) === BG_INDEXING_QUEUE_SUFFIX;
|
|
1166
|
+
}
|
|
1064
1167
|
var INDEXING_COMPLETE_MARKER = "INDEXING_COMPLETE";
|
|
1065
1168
|
var MAX_INDEXING_RESUME_PASSES = 6;
|
|
1066
|
-
var MAX_VISION_RESUME_PASSES = 40;
|
|
1067
1169
|
async function getChatHistory(params, fetchOptions) {
|
|
1068
1170
|
const url = params.platform === "claude" ? ANTHROPIC_MESSAGES_API_URL : OPENAI_RESPONSES_API_URL;
|
|
1069
1171
|
const p = Object.assign(
|
|
@@ -1191,6 +1293,9 @@ function nextFrame(cb) {
|
|
|
1191
1293
|
cb(nowMs());
|
|
1192
1294
|
}, 16);
|
|
1193
1295
|
}
|
|
1296
|
+
function isPollStopped(res) {
|
|
1297
|
+
return !!res && typeof res === "object" && res.status === "stopped";
|
|
1298
|
+
}
|
|
1194
1299
|
var ChatSession = class {
|
|
1195
1300
|
constructor(host) {
|
|
1196
1301
|
this.typewriterQueue = Promise.resolve();
|
|
@@ -1214,8 +1319,83 @@ var ChatSession = class {
|
|
|
1214
1319
|
this.pendingAgentRequests = {};
|
|
1215
1320
|
this.aiChatHistoryCache = {};
|
|
1216
1321
|
this.historyItemPolls = /* @__PURE__ */ new Map();
|
|
1322
|
+
this._pauseReasons = /* @__PURE__ */ new Set();
|
|
1323
|
+
this._resuming = false;
|
|
1217
1324
|
this._lidSeq = 0;
|
|
1218
1325
|
}
|
|
1326
|
+
/**
|
|
1327
|
+
* Register a live poll so (a) a remount dedupes against it instead of stacking a
|
|
1328
|
+
* SECOND poll on the same item, and (b) pausePolling can stop it.
|
|
1329
|
+
*
|
|
1330
|
+
* `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
|
|
1331
|
+
* poll simply cannot be stopped and is left running — see pausePolling.
|
|
1332
|
+
*/
|
|
1333
|
+
_trackPoll(id, kind, p) {
|
|
1334
|
+
var stop = p && typeof p.stop === "function" ? p.stop.bind(p) : void 0;
|
|
1335
|
+
if (!stop) {
|
|
1336
|
+
console.debug("[chat-engine] poll has no stop handle", { id, kind });
|
|
1337
|
+
}
|
|
1338
|
+
this.historyItemPolls.set(id, { kind, stop });
|
|
1339
|
+
return p;
|
|
1340
|
+
}
|
|
1341
|
+
/** True while any pause reason is active. */
|
|
1342
|
+
isPollingPaused() {
|
|
1343
|
+
return this._pauseReasons.size > 0;
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Stop BACKGROUND polling until resumePolling. Foreground polls (a reply the user is
|
|
1347
|
+
* waiting on) keep running deliberately: their results must still land in the history
|
|
1348
|
+
* cache so resumePendingRequest can render them on return, otherwise a user who sends
|
|
1349
|
+
* a message then navigates away comes back to a permanently stuck "Thinking...".
|
|
1350
|
+
*
|
|
1351
|
+
* Server-side work is untouched; this only stops asking about it. That is safe for
|
|
1352
|
+
* document indexing because the worker drives that loop itself.
|
|
1353
|
+
*/
|
|
1354
|
+
pausePolling(reason) {
|
|
1355
|
+
this._pauseReasons.add(reason || "paused");
|
|
1356
|
+
var self = this;
|
|
1357
|
+
var stopped = [];
|
|
1358
|
+
this.historyItemPolls.forEach(function(handle, id) {
|
|
1359
|
+
if (!handle || handle.kind !== "bg") return;
|
|
1360
|
+
if (typeof handle.stop !== "function") return;
|
|
1361
|
+
try {
|
|
1362
|
+
handle.stop();
|
|
1363
|
+
} catch (e) {
|
|
1364
|
+
}
|
|
1365
|
+
stopped.push(id);
|
|
1366
|
+
});
|
|
1367
|
+
stopped.forEach(function(id) {
|
|
1368
|
+
self.historyItemPolls.delete(id);
|
|
1369
|
+
});
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Lift a pause reason WITHOUT running the reconcile. For a caller that is about to
|
|
1373
|
+
* reload history anyway (a view remounting), letting resumePolling also reconcile
|
|
1374
|
+
* would race that load and can double-attach.
|
|
1375
|
+
*/
|
|
1376
|
+
clearPauseReason(reason) {
|
|
1377
|
+
this._pauseReasons.delete(reason || "paused");
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Clear a pause reason and, once none remain, re-attach polling and reconcile.
|
|
1381
|
+
* Deliberately does NOT touch gateRefreshToken: bumping it would silently discard
|
|
1382
|
+
* the results of anything still in flight across the pause.
|
|
1383
|
+
*/
|
|
1384
|
+
resumePolling(reason) {
|
|
1385
|
+
this._pauseReasons.delete(reason || "paused");
|
|
1386
|
+
if (this._pauseReasons.size > 0 || this._resuming) return Promise.resolve();
|
|
1387
|
+
if (!this.host.isViewMounted || !this.host.isViewMounted()) return Promise.resolve();
|
|
1388
|
+
var self = this;
|
|
1389
|
+
this._resuming = true;
|
|
1390
|
+
return Promise.resolve().then(function() {
|
|
1391
|
+
self.drainBgTaskQueue();
|
|
1392
|
+
return self.loadHistory(false, self.state.gateRefreshToken);
|
|
1393
|
+
}).catch(function(e) {
|
|
1394
|
+
console.error("[chat-engine] resume polling failed", e);
|
|
1395
|
+
}).then(function() {
|
|
1396
|
+
self._resuming = false;
|
|
1397
|
+
});
|
|
1398
|
+
}
|
|
1219
1399
|
_newLocalId() {
|
|
1220
1400
|
this._lidSeq += 1;
|
|
1221
1401
|
return "lid_" + this._lidSeq;
|
|
@@ -1229,29 +1409,86 @@ var ChatSession = class {
|
|
|
1229
1409
|
var key = this.getHistoryCacheKey();
|
|
1230
1410
|
if (!key) return;
|
|
1231
1411
|
this.aiChatHistoryCache[key] = {
|
|
1232
|
-
messages: this.state.messages.
|
|
1412
|
+
messages: this.state.messages.filter(function(m) {
|
|
1413
|
+
return m._ownerKey === void 0 || m._ownerKey === key;
|
|
1414
|
+
}),
|
|
1233
1415
|
endOfList: this.state.historyEndOfList,
|
|
1234
1416
|
startKeyHistory: this.state.historyStartKeyHistory.slice()
|
|
1235
1417
|
};
|
|
1236
1418
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1419
|
+
/**
|
|
1420
|
+
* Land a resolved reply in the history cache of a chat that is NOT currently
|
|
1421
|
+
* visible, without touching state.messages. Mirrors the cache-only path in
|
|
1422
|
+
* dispatchAgentRequest: REPLACE the trailing pending "Thinking..." bubble
|
|
1423
|
+
* (append only when there is none), and settle the matching pending user
|
|
1424
|
+
* bubble, so the cached copy never keeps a stuck "Thinking..." that a later
|
|
1425
|
+
* cache-first load would re-render forever.
|
|
1426
|
+
*/
|
|
1427
|
+
_applyReplyToCache(key, reply, serverId) {
|
|
1428
|
+
if (!key) return;
|
|
1429
|
+
var existing = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1430
|
+
var msgs = existing.messages.slice();
|
|
1431
|
+
var thIdx = -1;
|
|
1432
|
+
for (var i = msgs.length - 1; i >= 0; i--) {
|
|
1433
|
+
var m = msgs[i];
|
|
1434
|
+
if (!m || !m.isPending || m.role !== "assistant" || m.isBackgroundTask) continue;
|
|
1435
|
+
if (serverId && m._serverItemId && m._serverItemId !== serverId) continue;
|
|
1436
|
+
thIdx = i;
|
|
1437
|
+
break;
|
|
1438
|
+
}
|
|
1439
|
+
if (thIdx !== -1) {
|
|
1440
|
+
if (reply._serverItemId === void 0 && msgs[thIdx]._serverItemId !== void 0) reply._serverItemId = msgs[thIdx]._serverItemId;
|
|
1441
|
+
msgs[thIdx] = reply;
|
|
1442
|
+
} else {
|
|
1443
|
+
msgs.push(reply);
|
|
1444
|
+
}
|
|
1445
|
+
for (var j = 0; j < msgs.length; j++) {
|
|
1446
|
+
var u = msgs[j];
|
|
1447
|
+
if (!u || u.role !== "user" || u.isBackgroundTask) continue;
|
|
1448
|
+
if (!(u.isPendingQueued || u.isPendingInProcess || u.isSendingToServer)) continue;
|
|
1449
|
+
if (serverId && u._serverItemId && u._serverItemId !== serverId) continue;
|
|
1450
|
+
var settled = { role: "user", content: u.content };
|
|
1451
|
+
if (u._serverItemId !== void 0) settled._serverItemId = u._serverItemId;
|
|
1452
|
+
if (u._ownerKey !== void 0) settled._ownerKey = u._ownerKey;
|
|
1453
|
+
msgs[j] = settled;
|
|
1454
|
+
break;
|
|
1455
|
+
}
|
|
1456
|
+
this.aiChatHistoryCache[key] = {
|
|
1457
|
+
messages: msgs,
|
|
1458
|
+
endOfList: existing.endOfList,
|
|
1459
|
+
startKeyHistory: existing.startKeyHistory
|
|
1460
|
+
};
|
|
1461
|
+
}
|
|
1462
|
+
/**
|
|
1463
|
+
* serviceId/owner are passed explicitly by every caller: a request can be
|
|
1464
|
+
* dispatched after the user moved to another project, and re-reading the live
|
|
1465
|
+
* identity here would silently send the turn to THAT project instead of the
|
|
1466
|
+
* one it was composed for. Falls back to the live read only when a caller
|
|
1467
|
+
* omits them.
|
|
1468
|
+
*/
|
|
1469
|
+
_callProviderFor(platform, prompt, messages, system, model, userId, extractContent, fileUrls, serviceId, owner) {
|
|
1470
|
+
if (serviceId === void 0 || owner === void 0) {
|
|
1471
|
+
var id = this.host.getIdentity();
|
|
1472
|
+
if (serviceId === void 0) serviceId = id.serviceId;
|
|
1473
|
+
if (owner === void 0) owner = id.owner;
|
|
1474
|
+
}
|
|
1475
|
+
return platform === "openai" ? callOpenAIWithPublicMcp(prompt, serviceId, owner, messages, system, model, userId, extractContent, fileUrls) : callClaudeWithPublicMcp(prompt, serviceId, owner, messages, system, model, userId, extractContent, fileUrls);
|
|
1240
1476
|
}
|
|
1241
1477
|
dispatchAgentRequest(params) {
|
|
1242
1478
|
var self = this;
|
|
1243
1479
|
var dispatchItemId;
|
|
1244
1480
|
var sendAndPoll = function() {
|
|
1245
1481
|
return Promise.resolve(
|
|
1246
|
-
self._callProviderFor(params.aiPlatform, params.text, params.boundedMessages, params.systemPrompt, params.aiModel, params.userId, params.extractContent, params.fileUrls)
|
|
1482
|
+
self._callProviderFor(params.aiPlatform, params.text, params.boundedMessages, params.systemPrompt, params.aiModel, params.userId, params.extractContent, params.fileUrls, params.serviceId, params.owner)
|
|
1247
1483
|
).then(function(initial) {
|
|
1248
1484
|
if (initial && initial.poll && (initial.status === "pending" || initial.status === "running")) {
|
|
1249
1485
|
if (initial.id) {
|
|
1250
1486
|
if (dispatchItemId && dispatchItemId !== initial.id) self.historyItemPolls.delete(dispatchItemId);
|
|
1251
1487
|
dispatchItemId = initial.id;
|
|
1252
|
-
self.historyItemPolls.set(initial.id, true);
|
|
1253
1488
|
}
|
|
1254
|
-
|
|
1489
|
+
var dp = initial.poll({ latency: POLL_INTERVAL });
|
|
1490
|
+
if (initial.id) self._trackPoll(initial.id, "fg", dp);
|
|
1491
|
+
return dp;
|
|
1255
1492
|
}
|
|
1256
1493
|
return initial;
|
|
1257
1494
|
});
|
|
@@ -1304,20 +1541,57 @@ var ChatSession = class {
|
|
|
1304
1541
|
// composed = clean display text; composedForLlm carries office-extraction
|
|
1305
1542
|
// placeholders for the provider only. useBgQueue routes a post-attachment turn
|
|
1306
1543
|
// onto the "-bg" queue so it runs after indexing.
|
|
1307
|
-
dispatchComposedMessage(composed, useBgQueue, composedForLlm, extractContent, fileUrls) {
|
|
1544
|
+
dispatchComposedMessage(composed, useBgQueue, composedForLlm, extractContent, fileUrls, pinned) {
|
|
1308
1545
|
var self = this;
|
|
1309
1546
|
if (!composed) return;
|
|
1310
|
-
var id = this.host.getIdentity();
|
|
1547
|
+
var id = pinned ? pinned.identity : this.host.getIdentity();
|
|
1311
1548
|
if (id.platform === "none") return;
|
|
1312
1549
|
var llmComposed = composedForLlm || composed;
|
|
1313
|
-
var
|
|
1550
|
+
var key = !id.serviceId ? "" : id.serviceId + "#" + id.platform;
|
|
1551
|
+
var offChat = !!key && key !== this.getHistoryCacheKey();
|
|
1552
|
+
var isQueuedSend = !offChat && (useBgQueue || this.state.sending || this.state.messages.some(function(m) {
|
|
1314
1553
|
return (m.isPending || m.isPendingQueued) && !m.isBackgroundTask && !m._useBgQueue;
|
|
1315
|
-
});
|
|
1554
|
+
}));
|
|
1316
1555
|
var aiPlatform = id.platform;
|
|
1317
1556
|
var aiModel = id.model || void 0;
|
|
1318
|
-
var systemPrompt = this.host.buildSystemPrompt();
|
|
1557
|
+
var systemPrompt = pinned ? pinned.systemPrompt : this.host.buildSystemPrompt();
|
|
1319
1558
|
var userId = id.userId || id.serviceId;
|
|
1320
1559
|
var chatQueue = useBgQueue ? userId + BG_INDEXING_QUEUE_SUFFIX : userId;
|
|
1560
|
+
if (offChat) {
|
|
1561
|
+
var offHistory = (this.aiChatHistoryCache[key] ? this.aiChatHistoryCache[key].messages : []).filter(function(m) {
|
|
1562
|
+
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask;
|
|
1563
|
+
});
|
|
1564
|
+
var offBounded = buildBoundedChatMessages({
|
|
1565
|
+
platform: aiPlatform,
|
|
1566
|
+
model: aiModel,
|
|
1567
|
+
systemPrompt,
|
|
1568
|
+
serviceId: id.serviceId,
|
|
1569
|
+
history: offHistory.concat([{ role: "user", content: llmComposed }])
|
|
1570
|
+
});
|
|
1571
|
+
var offExisting = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1572
|
+
this.aiChatHistoryCache[key] = {
|
|
1573
|
+
messages: offExisting.messages.concat([
|
|
1574
|
+
{ role: "user", content: composed, _ownerKey: key },
|
|
1575
|
+
{ role: "assistant", content: "", isPending: true, isPendingInProcess: true, _ownerKey: key }
|
|
1576
|
+
]),
|
|
1577
|
+
endOfList: offExisting.endOfList,
|
|
1578
|
+
startKeyHistory: offExisting.startKeyHistory
|
|
1579
|
+
};
|
|
1580
|
+
this.dispatchAgentRequest({
|
|
1581
|
+
key,
|
|
1582
|
+
serviceId: id.serviceId,
|
|
1583
|
+
owner: id.owner,
|
|
1584
|
+
aiPlatform,
|
|
1585
|
+
aiModel,
|
|
1586
|
+
systemPrompt,
|
|
1587
|
+
text: composed,
|
|
1588
|
+
boundedMessages: offBounded.messages,
|
|
1589
|
+
userId: chatQueue,
|
|
1590
|
+
extractContent,
|
|
1591
|
+
fileUrls
|
|
1592
|
+
});
|
|
1593
|
+
return;
|
|
1594
|
+
}
|
|
1321
1595
|
if (isQueuedSend) {
|
|
1322
1596
|
var resolvedHistory = this.state.messages.filter(function(m) {
|
|
1323
1597
|
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask;
|
|
@@ -1330,15 +1604,16 @@ var ChatSession = class {
|
|
|
1330
1604
|
history: resolvedHistory.concat([{ role: "user", content: llmComposed }])
|
|
1331
1605
|
});
|
|
1332
1606
|
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true };
|
|
1607
|
+
if (key) queuedBubble._ownerKey = key;
|
|
1333
1608
|
if (useBgQueue) queuedBubble._useBgQueue = true;
|
|
1334
1609
|
this.state.messages.push(queuedBubble);
|
|
1335
1610
|
this.host.notify();
|
|
1336
1611
|
this.updateHistoryCache();
|
|
1337
1612
|
this.host.scrollToBottom(true);
|
|
1338
|
-
var capturedComposed = composed, capturedPlatform = aiPlatform;
|
|
1339
|
-
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls)).then(function(result) {
|
|
1340
|
-
var sendingIdx = self.state.messages.findIndex(function(m) {
|
|
1341
|
-
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user";
|
|
1613
|
+
var capturedComposed = composed, capturedPlatform = aiPlatform, capturedKey = key;
|
|
1614
|
+
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls, id.serviceId, id.owner)).then(function(result) {
|
|
1615
|
+
var sendingIdx = self.getHistoryCacheKey() !== capturedKey ? -1 : self.state.messages.findIndex(function(m) {
|
|
1616
|
+
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user" && (m._ownerKey === void 0 || m._ownerKey === capturedKey);
|
|
1342
1617
|
});
|
|
1343
1618
|
var serverId = result && typeof result.id === "string" ? result.id : void 0;
|
|
1344
1619
|
if (sendingIdx >= 0) {
|
|
@@ -1348,26 +1623,27 @@ var ChatSession = class {
|
|
|
1348
1623
|
self.host.notify();
|
|
1349
1624
|
}
|
|
1350
1625
|
if (result && result.poll && (result.status === "pending" || result.status === "running")) {
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1626
|
+
var qp = result.poll({ latency: POLL_INTERVAL });
|
|
1627
|
+
if (serverId) self._trackPoll(serverId, "fg", qp);
|
|
1628
|
+
return qp.then(function(res) {
|
|
1629
|
+
if (isPollStopped(res)) return;
|
|
1630
|
+
return self.onQueuedSendResponse(capturedComposed, res, capturedPlatform, serverId, capturedKey);
|
|
1354
1631
|
}).catch(function(err) {
|
|
1355
|
-
return self.onQueuedSendError(capturedComposed, err, serverId);
|
|
1632
|
+
return self.onQueuedSendError(capturedComposed, err, serverId, capturedKey);
|
|
1356
1633
|
});
|
|
1357
1634
|
}
|
|
1358
|
-
return self.onQueuedSendResponse(capturedComposed, result, capturedPlatform, serverId);
|
|
1635
|
+
return self.onQueuedSendResponse(capturedComposed, result, capturedPlatform, serverId, capturedKey);
|
|
1359
1636
|
}).catch(function(err) {
|
|
1360
|
-
return self.onQueuedSendError(capturedComposed, err, void 0);
|
|
1637
|
+
return self.onQueuedSendError(capturedComposed, err, void 0, capturedKey);
|
|
1361
1638
|
});
|
|
1362
1639
|
return;
|
|
1363
1640
|
}
|
|
1364
|
-
this.state.messages.push({ role: "user", content: composed });
|
|
1365
|
-
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true });
|
|
1641
|
+
this.state.messages.push({ role: "user", content: composed, ...key ? { _ownerKey: key } : {} });
|
|
1642
|
+
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} });
|
|
1366
1643
|
this.host.notify();
|
|
1367
1644
|
this.updateHistoryCache();
|
|
1368
1645
|
this.state.sending = true;
|
|
1369
1646
|
this.host.scrollToBottom(true);
|
|
1370
|
-
var key = this.getHistoryCacheKey();
|
|
1371
1647
|
var historyForLlm = this.state.messages.filter(function(m) {
|
|
1372
1648
|
return !m.isCancelled && !m.isBackgroundTask;
|
|
1373
1649
|
});
|
|
@@ -1401,8 +1677,8 @@ var ChatSession = class {
|
|
|
1401
1677
|
});
|
|
1402
1678
|
Promise.resolve(run).catch(function() {
|
|
1403
1679
|
}).then(function() {
|
|
1404
|
-
if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
|
|
1405
1680
|
self.state.sending = false;
|
|
1681
|
+
if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
|
|
1406
1682
|
return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
|
|
1407
1683
|
self.host.scrollToBottom(true);
|
|
1408
1684
|
});
|
|
@@ -1419,9 +1695,11 @@ var ChatSession = class {
|
|
|
1419
1695
|
var existing = this.state.messages[nextIdx];
|
|
1420
1696
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true, isBackgroundTask: true };
|
|
1421
1697
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1698
|
+
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1422
1699
|
this.state.messages[nextIdx] = promoted;
|
|
1423
1700
|
var placeholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, isBackgroundTask: true };
|
|
1424
1701
|
if (existing._serverItemId !== void 0) placeholder._serverItemId = existing._serverItemId;
|
|
1702
|
+
if (existing._ownerKey !== void 0) placeholder._ownerKey = existing._ownerKey;
|
|
1425
1703
|
this.state.messages.splice(nextIdx + 1, 0, placeholder);
|
|
1426
1704
|
this.host.notify();
|
|
1427
1705
|
}
|
|
@@ -1437,14 +1715,20 @@ var ChatSession = class {
|
|
|
1437
1715
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true };
|
|
1438
1716
|
if (existing.isBackgroundTask) promoted.isBackgroundTask = true;
|
|
1439
1717
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1718
|
+
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1440
1719
|
if (existing.isSendingToServer) promoted.isSendingToServer = true;
|
|
1441
1720
|
this.state.messages[nextIdx] = promoted;
|
|
1442
1721
|
var placeholder = { role: "assistant", content: "", isPending: true };
|
|
1443
1722
|
if (existing._serverItemId !== void 0) placeholder._serverItemId = existing._serverItemId;
|
|
1723
|
+
if (existing._ownerKey !== void 0) placeholder._ownerKey = existing._ownerKey;
|
|
1444
1724
|
this.state.messages.splice(nextIdx + 1, 0, placeholder);
|
|
1445
1725
|
this.host.notify();
|
|
1446
1726
|
}
|
|
1447
1727
|
resolveQueuedUserBubble(serverId) {
|
|
1728
|
+
var liveKey = this.getHistoryCacheKey();
|
|
1729
|
+
var isLocal = function(m) {
|
|
1730
|
+
return m._ownerKey === void 0 || m._ownerKey === liveKey;
|
|
1731
|
+
};
|
|
1448
1732
|
var userIdx = -1;
|
|
1449
1733
|
if (serverId) {
|
|
1450
1734
|
userIdx = this.state.messages.findIndex(function(m) {
|
|
@@ -1453,19 +1737,19 @@ var ChatSession = class {
|
|
|
1453
1737
|
}
|
|
1454
1738
|
if (userIdx === -1) {
|
|
1455
1739
|
userIdx = this.state.messages.findIndex(function(m) {
|
|
1456
|
-
return m.isPendingInProcess && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1740
|
+
return m.isPendingInProcess && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue && isLocal(m);
|
|
1457
1741
|
});
|
|
1458
1742
|
}
|
|
1459
1743
|
if (userIdx === -1) {
|
|
1460
1744
|
userIdx = this.state.messages.findIndex(function(m) {
|
|
1461
|
-
return m.isPendingQueued && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1745
|
+
return m.isPendingQueued && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue && isLocal(m);
|
|
1462
1746
|
});
|
|
1463
1747
|
}
|
|
1464
1748
|
if (serverId && this.cancelledServerIds.has(serverId)) {
|
|
1465
1749
|
this.cancelledServerIds.delete(serverId);
|
|
1466
1750
|
if (userIdx >= 0) {
|
|
1467
1751
|
var ex = this.state.messages[userIdx];
|
|
1468
|
-
this.state.messages[userIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId };
|
|
1752
|
+
this.state.messages[userIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId, ...ex._ownerKey !== void 0 ? { _ownerKey: ex._ownerKey } : {} };
|
|
1469
1753
|
var thIdx = this.state.messages.findIndex(function(m, i) {
|
|
1470
1754
|
return i > userIdx && m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1471
1755
|
});
|
|
@@ -1478,6 +1762,7 @@ var ChatSession = class {
|
|
|
1478
1762
|
var exist = this.state.messages[userIdx];
|
|
1479
1763
|
var repl = { role: "user", content: exist.content };
|
|
1480
1764
|
if (exist._serverItemId !== void 0) repl._serverItemId = exist._serverItemId;
|
|
1765
|
+
if (exist._ownerKey !== void 0) repl._ownerKey = exist._ownerKey;
|
|
1481
1766
|
this.state.messages[userIdx] = repl;
|
|
1482
1767
|
}
|
|
1483
1768
|
var thinkingIdx = userIdx >= 0 ? this.state.messages.findIndex(function(m, i) {
|
|
@@ -1490,8 +1775,14 @@ var ChatSession = class {
|
|
|
1490
1775
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
1491
1776
|
else this.state.messages.push(msg);
|
|
1492
1777
|
}
|
|
1493
|
-
onQueuedSendResponse(_composed, response, platform, serverId) {
|
|
1778
|
+
onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
|
|
1494
1779
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
1780
|
+
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
1781
|
+
var offReply = isErrorResponseBody(response) ? { role: "assistant", content: getErrorMessage(response), isError: true } : { role: "assistant", content: ((platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "").trim() || "No text response received from AI provider." };
|
|
1782
|
+
this._applyReplyToCache(ownerKey, offReply, serverId);
|
|
1783
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1495
1786
|
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
1496
1787
|
if (targetIdx === void 0) {
|
|
1497
1788
|
this.host.notify();
|
|
@@ -1525,8 +1816,14 @@ var ChatSession = class {
|
|
|
1525
1816
|
this.host.notify();
|
|
1526
1817
|
this.host.scrollToBottom(true);
|
|
1527
1818
|
}
|
|
1528
|
-
onQueuedSendError(_composed, err, serverId) {
|
|
1819
|
+
onQueuedSendError(_composed, err, serverId, ownerKey) {
|
|
1529
1820
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
1821
|
+
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
1822
|
+
var isGone = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
1823
|
+
this._applyReplyToCache(ownerKey, isGone ? { role: "assistant", content: "Request was cancelled.", isError: true } : { role: "assistant", content: getErrorMessage(err), isError: true }, serverId);
|
|
1824
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
1825
|
+
return;
|
|
1826
|
+
}
|
|
1530
1827
|
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
1531
1828
|
if (isNotExists) {
|
|
1532
1829
|
var userIdx = serverId ? this.state.messages.findIndex(function(m) {
|
|
@@ -1930,24 +2227,31 @@ var ChatSession = class {
|
|
|
1930
2227
|
}
|
|
1931
2228
|
this.bgTaskQueue.forEach(function(entry) {
|
|
1932
2229
|
if (entry.serviceId !== svcId || entry.platform !== plat) return;
|
|
1933
|
-
if (presentIds[entry.id])
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
2230
|
+
if (!presentIds[entry.id]) {
|
|
2231
|
+
var isRunning = entry.status === "running";
|
|
2232
|
+
var userBubble = { role: "user", content: self.host.formatIndexingLabel(entry.filename, entry.mime, entry.size, entry.storagePath, entry.isReindex), isBackgroundTask: true, _serverItemId: entry.id };
|
|
2233
|
+
if (isRunning) userBubble.isPendingInProcess = true;
|
|
2234
|
+
else userBubble.isPendingQueued = true;
|
|
2235
|
+
self.state.messages.push(userBubble);
|
|
2236
|
+
if (isRunning) {
|
|
2237
|
+
self.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, isBackgroundTask: true, _serverItemId: entry.id });
|
|
2238
|
+
}
|
|
2239
|
+
presentIds[entry.id] = true;
|
|
2240
|
+
self.host.notify();
|
|
2241
|
+
self.updateHistoryCache();
|
|
2242
|
+
self.host.scrollToBottom(false);
|
|
1941
2243
|
}
|
|
1942
|
-
|
|
1943
|
-
self.host.notify();
|
|
1944
|
-
self.updateHistoryCache();
|
|
1945
|
-
self.host.scrollToBottom(false);
|
|
1946
|
-
if (!self.historyItemPolls.has(entry.id) && typeof entry.poll === "function") {
|
|
1947
|
-
self.historyItemPolls.set(entry.id, true);
|
|
2244
|
+
if (!self.isPollingPaused() && !self.historyItemPolls.has(entry.id) && typeof entry.poll === "function") {
|
|
1948
2245
|
var capturedId = entry.id, capturedPlat = plat;
|
|
1949
2246
|
var capturedEntry = entry;
|
|
1950
|
-
|
|
2247
|
+
var wasStopped = false;
|
|
2248
|
+
var bp = entry.poll({ latency: POLL_INTERVAL });
|
|
2249
|
+
self._trackPoll(entry.id, "bg", bp);
|
|
2250
|
+
bp.then(function(response) {
|
|
2251
|
+
if (isPollStopped(response)) {
|
|
2252
|
+
wasStopped = true;
|
|
2253
|
+
return;
|
|
2254
|
+
}
|
|
1951
2255
|
self.handleHistoryItemResolution(capturedId, response, capturedPlat);
|
|
1952
2256
|
self.maybeResumeIndexing(capturedEntry, response, capturedPlat);
|
|
1953
2257
|
}).catch(function(err) {
|
|
@@ -1963,6 +2267,7 @@ var ChatSession = class {
|
|
|
1963
2267
|
self.updateHistoryCache();
|
|
1964
2268
|
}
|
|
1965
2269
|
}).then(function() {
|
|
2270
|
+
if (wasStopped) return;
|
|
1966
2271
|
var qi = self.bgTaskQueue.findIndex(function(q) {
|
|
1967
2272
|
return q.id === capturedId;
|
|
1968
2273
|
});
|
|
@@ -1973,25 +2278,30 @@ var ChatSession = class {
|
|
|
1973
2278
|
this.promoteNextBgQueuedToRunning();
|
|
1974
2279
|
}
|
|
1975
2280
|
// Resume-across-passes: if a background INDEXING task for a paged file (spreadsheet or
|
|
1976
|
-
//
|
|
2281
|
+
// text) finished WITHOUT the completion marker, the agent ran out of room before reading
|
|
1977
2282
|
// the whole file - dispatch a CONTINUE pass (up to a cap) that resumes from where the
|
|
1978
2283
|
// already-saved records leave off. Additive + guarded so it never loops forever and
|
|
1979
2284
|
// never breaks the resolution path.
|
|
2285
|
+
//
|
|
2286
|
+
// VISION files (PDFs rendered to page images) are NOT resumed here: the proxy worker
|
|
2287
|
+
// advances their page window itself, off the renderer's true page count. Driving them
|
|
2288
|
+
// from the browser is what used to lose pages on long documents - the chain lived in tab
|
|
2289
|
+
// memory (a reload or a closed tab ended it), and it stopped whenever the model claimed
|
|
2290
|
+
// completion, which on an 88-page file happened at page 15. Continuing to dispatch here
|
|
2291
|
+
// as well would now double-index every window.
|
|
1980
2292
|
maybeResumeIndexing(entry, response, platform) {
|
|
1981
2293
|
var self = this;
|
|
1982
2294
|
try {
|
|
1983
2295
|
if (!entry || !entry.storagePath) return;
|
|
1984
2296
|
if (!isPagedReadFile(entry.filename, entry.mime)) return;
|
|
2297
|
+
if (isImageVisionFile(entry.filename, entry.mime)) return;
|
|
1985
2298
|
if (isErrorResponseBody(response)) return;
|
|
1986
2299
|
var answer = (platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "";
|
|
1987
2300
|
if (answer.indexOf(INDEXING_COMPLETE_MARKER) !== -1) return;
|
|
1988
2301
|
var pass = (entry.resumePass || 0) + 1;
|
|
1989
|
-
|
|
1990
|
-
var maxPasses = isVision ? MAX_VISION_RESUME_PASSES : MAX_INDEXING_RESUME_PASSES;
|
|
1991
|
-
if (pass > maxPasses) return;
|
|
2302
|
+
if (pass > MAX_INDEXING_RESUME_PASSES) return;
|
|
1992
2303
|
var id = this.host.getIdentity();
|
|
1993
2304
|
if (!id || id.platform === "none" || id.serviceId !== entry.serviceId) return;
|
|
1994
|
-
var renderFrom = isVision ? pass * RENDER_PAGES_PER_WINDOW : void 0;
|
|
1995
2305
|
notifyAgentContinueIndexing({
|
|
1996
2306
|
platform: id.platform,
|
|
1997
2307
|
model: id.model,
|
|
@@ -2000,7 +2310,6 @@ var ChatSession = class {
|
|
|
2000
2310
|
userId: id.userId || id.serviceId,
|
|
2001
2311
|
serviceName: id.serviceName,
|
|
2002
2312
|
serviceDescription: id.serviceDescription,
|
|
2003
|
-
renderFrom,
|
|
2004
2313
|
attachment: {
|
|
2005
2314
|
name: entry.filename,
|
|
2006
2315
|
storagePath: entry.storagePath,
|
|
@@ -2040,6 +2349,7 @@ var ChatSession = class {
|
|
|
2040
2349
|
loadHistory(fetchMore, token) {
|
|
2041
2350
|
var self = this;
|
|
2042
2351
|
var id = this.host.getIdentity();
|
|
2352
|
+
var loadKey = !id.serviceId || id.platform === "none" ? "" : id.serviceId + "#" + id.platform;
|
|
2043
2353
|
if (token === void 0) token = this.state.gateRefreshToken;
|
|
2044
2354
|
if (this.state.loadingHistory && this.state.historyRequestToken === token || id.platform === "none" || !id.serviceId) {
|
|
2045
2355
|
return Promise.resolve();
|
|
@@ -2062,9 +2372,9 @@ var ChatSession = class {
|
|
|
2062
2372
|
if (token !== self.state.gateRefreshToken) return;
|
|
2063
2373
|
var chatList = history && Array.isArray(history.list) ? history.list : [];
|
|
2064
2374
|
chatList.forEach(function(item) {
|
|
2065
|
-
if (
|
|
2375
|
+
if (isBgIndexingQueue(item.queue_name)) {
|
|
2066
2376
|
var userText = extractLastUserTextFromRequest(item.request_body);
|
|
2067
|
-
if (typeof userText === "string" && userText.indexOf("A new file has just been uploaded") === 0) item._isBgTask = true;
|
|
2377
|
+
if (typeof userText === "string" && (userText.indexOf("A new file has just been uploaded") === 0 || userText.indexOf("CONTINUE indexing") === 0)) item._isBgTask = true;
|
|
2068
2378
|
else item._isOnBgQueue = true;
|
|
2069
2379
|
}
|
|
2070
2380
|
});
|
|
@@ -2096,6 +2406,7 @@ var ChatSession = class {
|
|
|
2096
2406
|
for (var ri = 0; ri < self.state.messages.length; ri++) {
|
|
2097
2407
|
var mm = self.state.messages[ri];
|
|
2098
2408
|
if (mm.isBackgroundTask) continue;
|
|
2409
|
+
if (mm._ownerKey !== void 0 && mm._ownerKey !== loadKey) continue;
|
|
2099
2410
|
if (mm._serverItemId && serverIds[mm._serverItemId]) continue;
|
|
2100
2411
|
if (!mm._serverItemId) {
|
|
2101
2412
|
if (mappedHasPendingAssistant) continue;
|
|
@@ -2140,11 +2451,12 @@ var ChatSession = class {
|
|
|
2140
2451
|
if (!item.poll || !item.id) return;
|
|
2141
2452
|
if (self.historyItemPolls.has(item.id)) return;
|
|
2142
2453
|
if (self.pendingAgentRequests[self.getHistoryCacheKey()] && !item._isBgTask && !item._isOnBgQueue) return;
|
|
2143
|
-
|
|
2454
|
+
if ((item._isBgTask || item._isOnBgQueue) && self.isPollingPaused()) return;
|
|
2144
2455
|
var capturedId = item.id;
|
|
2145
2456
|
var pp = item.poll({
|
|
2146
2457
|
latency: POLL_INTERVAL,
|
|
2147
2458
|
onResponse: function(response) {
|
|
2459
|
+
if (isPollStopped(response)) return;
|
|
2148
2460
|
self.handleHistoryItemResolution(capturedId, response, platform);
|
|
2149
2461
|
},
|
|
2150
2462
|
onError: function(err) {
|
|
@@ -2180,6 +2492,7 @@ var ChatSession = class {
|
|
|
2180
2492
|
}
|
|
2181
2493
|
}
|
|
2182
2494
|
});
|
|
2495
|
+
self._trackPoll(capturedId, item._isBgTask || item._isOnBgQueue ? "bg" : "fg", pp);
|
|
2183
2496
|
if (pp && pp.catch) pp.catch(function() {
|
|
2184
2497
|
});
|
|
2185
2498
|
});
|
|
@@ -2409,14 +2722,17 @@ exports.MCP_NAME = MCP_NAME;
|
|
|
2409
2722
|
exports.MIN_INPUT_TOKEN_BUDGET = MIN_INPUT_TOKEN_BUDGET;
|
|
2410
2723
|
exports.OUTPUT_TOKEN_RESERVE = OUTPUT_TOKEN_RESERVE;
|
|
2411
2724
|
exports.POLL_INTERVAL = POLL_INTERVAL;
|
|
2725
|
+
exports.RENDER_FROM_TOKEN = RENDER_FROM_TOKEN;
|
|
2412
2726
|
exports.TOOL_AND_RESPONSE_BUFFER = TOOL_AND_RESPONSE_BUFFER;
|
|
2413
2727
|
exports.buildBoundedChatMessages = buildBoundedChatMessages;
|
|
2414
2728
|
exports.buildChatSystemPrompt = buildChatSystemPrompt;
|
|
2415
2729
|
exports.buildDisplayExpiredAttachmentHref = buildDisplayExpiredAttachmentHref;
|
|
2416
2730
|
exports.buildIndexingContinueMessage = buildIndexingContinueMessage;
|
|
2731
|
+
exports.buildIndexingRenderContinueTemplate = buildIndexingRenderContinueTemplate;
|
|
2417
2732
|
exports.buildIndexingRenderMessage = buildIndexingRenderMessage;
|
|
2418
2733
|
exports.buildIndexingSystemPrompt = buildIndexingSystemPrompt;
|
|
2419
2734
|
exports.buildIndexingUserMessage = buildIndexingUserMessage;
|
|
2735
|
+
exports.buildIndexingWindowMessage = buildIndexingWindowMessage;
|
|
2420
2736
|
exports.callClaudeWithMcp = callClaudeWithMcp;
|
|
2421
2737
|
exports.callClaudeWithPublicMcp = callClaudeWithPublicMcp;
|
|
2422
2738
|
exports.callOpenAIWithPublicMcp = callOpenAIWithPublicMcp;
|
|
@@ -2441,6 +2757,7 @@ exports.getErrorMessage = getErrorMessage;
|
|
|
2441
2757
|
exports.getExpiredAttachmentVisiblePath = getExpiredAttachmentVisiblePath;
|
|
2442
2758
|
exports.groupAttachmentFailures = groupAttachmentFailures;
|
|
2443
2759
|
exports.isAuthExpiredError = isAuthExpiredError;
|
|
2760
|
+
exports.isBgIndexingQueue = isBgIndexingQueue;
|
|
2444
2761
|
exports.isErrorResponseBody = isErrorResponseBody;
|
|
2445
2762
|
exports.isNonRetryableRequestError = isNonRetryableRequestError;
|
|
2446
2763
|
exports.isOfficeFile = isOfficeFile;
|