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/bunnyquery.js
CHANGED
|
@@ -56,6 +56,9 @@
|
|
|
56
56
|
}
|
|
57
57
|
return _config;
|
|
58
58
|
}
|
|
59
|
+
function windowedIndexingEnabled() {
|
|
60
|
+
return _config?.windowedIndexing === true;
|
|
61
|
+
}
|
|
59
62
|
function pollOpt() {
|
|
60
63
|
const p = _config?.poll;
|
|
61
64
|
return p === void 0 ? {} : { poll: p };
|
|
@@ -149,7 +152,32 @@
|
|
|
149
152
|
if (isTextMime(m)) return true;
|
|
150
153
|
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";
|
|
151
154
|
}
|
|
152
|
-
var PAGED_READ_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
155
|
+
var PAGED_READ_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
156
|
+
// grids
|
|
157
|
+
"xls",
|
|
158
|
+
"xlsx",
|
|
159
|
+
"xlsm",
|
|
160
|
+
"ods",
|
|
161
|
+
// delimited text (row-windowed by the layer)
|
|
162
|
+
"csv",
|
|
163
|
+
"tsv",
|
|
164
|
+
"tab",
|
|
165
|
+
// documents
|
|
166
|
+
"pdf",
|
|
167
|
+
"docx",
|
|
168
|
+
"pptx",
|
|
169
|
+
// plain text / data / markup
|
|
170
|
+
"txt",
|
|
171
|
+
"md",
|
|
172
|
+
"markdown",
|
|
173
|
+
"log",
|
|
174
|
+
"json",
|
|
175
|
+
"jsonl",
|
|
176
|
+
"ndjson",
|
|
177
|
+
"xml",
|
|
178
|
+
"yaml",
|
|
179
|
+
"yml"
|
|
180
|
+
]);
|
|
153
181
|
function isPagedReadFile(name, mime) {
|
|
154
182
|
const ext = (name || "").split(".").pop()?.toLowerCase() || "";
|
|
155
183
|
if (PAGED_READ_EXTENSIONS.has(ext)) return true;
|
|
@@ -173,6 +201,16 @@
|
|
|
173
201
|
return `{{SKAPI_RENDER::${slug}-${_renderPlaceholderSeq}}}`;
|
|
174
202
|
}
|
|
175
203
|
var RENDER_PAGES_PER_WINDOW = 5;
|
|
204
|
+
var _windowPlaceholderSeq = 0;
|
|
205
|
+
function makeWindowPlaceholder(seed) {
|
|
206
|
+
_windowPlaceholderSeq += 1;
|
|
207
|
+
const slug = (seed || "file").replace(/[^a-zA-Z0-9]+/g, "_").slice(-48);
|
|
208
|
+
return `{{SKAPI_WINDOW::${slug}-${_windowPlaceholderSeq}}}`;
|
|
209
|
+
}
|
|
210
|
+
function isWindowedReadFile(name, mime) {
|
|
211
|
+
if (isImageVisionFile(name, mime)) return false;
|
|
212
|
+
return isPagedReadFile(name, mime);
|
|
213
|
+
}
|
|
176
214
|
function composeUserMessage(text, attachmentUrls) {
|
|
177
215
|
let composed = text;
|
|
178
216
|
if (attachmentUrls.length > 0) {
|
|
@@ -320,32 +358,58 @@ Read this file with the readFileContent tool, using the storage path above - do
|
|
|
320
358
|
}
|
|
321
359
|
return head + `- temporary URL (fetch this to read the file contents): ${attachment.url}`;
|
|
322
360
|
}
|
|
361
|
+
var RENDER_FROM_TOKEN = "{{RENDER_FROM}}";
|
|
362
|
+
var WINDOW_CURSOR_TOKEN = RENDER_FROM_TOKEN;
|
|
323
363
|
function buildIndexingRenderMessage(attachment, placeholder, renderFrom) {
|
|
324
364
|
const from = Math.max(0, renderFrom || 0);
|
|
365
|
+
if (from > 0) return buildIndexingRenderContinueTemplate(attachment, placeholder, String(from + 1));
|
|
366
|
+
return `A new file has just been uploaded. Index it now.
|
|
367
|
+
|
|
368
|
+
` + buildRenderMeta(attachment) + `
|
|
369
|
+
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}.
|
|
370
|
+
` + buildRenderDatafy(placeholder);
|
|
371
|
+
}
|
|
372
|
+
function buildIndexingRenderContinueTemplate(attachment, placeholder, pageLabel = RENDER_FROM_TOKEN) {
|
|
325
373
|
const src = `src::${attachment.storagePath}`;
|
|
326
|
-
|
|
374
|
+
return `CONTINUE indexing a PDF whose previous pass did not finish.
|
|
375
|
+
|
|
376
|
+
` + buildRenderMeta(attachment) + `
|
|
377
|
+
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.
|
|
378
|
+
` + buildRenderDatafy(placeholder);
|
|
379
|
+
}
|
|
380
|
+
function buildRenderMeta(attachment) {
|
|
381
|
+
return `File metadata:
|
|
327
382
|
- name: ${attachment.name}
|
|
328
383
|
- storage path: ${attachment.storagePath}
|
|
329
384
|
` + (attachment.mime ? `- mime type: ${attachment.mime}
|
|
330
385
|
` : "");
|
|
331
|
-
|
|
386
|
+
}
|
|
387
|
+
function buildRenderDatafy(placeholder) {
|
|
388
|
+
return `
|
|
332
389
|
${placeholder}
|
|
333
390
|
|
|
334
391
|
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.
|
|
335
392
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
393
|
+
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.`;
|
|
394
|
+
}
|
|
395
|
+
function buildIndexingWindowMessage(attachment, placeholder, isContinuation, positionLabel) {
|
|
396
|
+
const src = `src::${attachment.storagePath}`;
|
|
397
|
+
const head = isContinuation ? `CONTINUE indexing a file whose previous pass did not finish.
|
|
339
398
|
|
|
340
|
-
`
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
399
|
+
` : `A new file has just been uploaded. Index it now.
|
|
400
|
+
|
|
401
|
+
`;
|
|
402
|
+
const where = isContinuation ? `
|
|
403
|
+
Records for the earlier windows are ALREADY saved (they reference "${src}"). The NEXT window (starting at ${WINDOW_CURSOR_TOKEN}) is embedded below. Do NOT re-save windows that are already saved.
|
|
404
|
+
` : `
|
|
405
|
+
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.
|
|
406
|
+
`;
|
|
407
|
+
return head + buildRenderMeta(attachment) + where + `
|
|
408
|
+
${placeholder}
|
|
409
|
+
|
|
410
|
+
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.
|
|
345
411
|
|
|
346
|
-
|
|
347
|
-
Records for the earlier pages are ALREADY saved (they reference "${src}"). The NEXT window of rendered page images (starting at page ${from + 1}) is embedded in this message. Datafy each page as before and do NOT re-save pages that are already saved.
|
|
348
|
-
` + datafy;
|
|
412
|
+
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.`;
|
|
349
413
|
}
|
|
350
414
|
function buildIndexingContinueMessage(attachment) {
|
|
351
415
|
const src = `src::${attachment.storagePath}`;
|
|
@@ -615,19 +679,22 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
615
679
|
var clientSecretRequest = (opts) => chatEngineConfig().clientSecretRequest(opts);
|
|
616
680
|
var getOpenAIImageDetail = (model) => {
|
|
617
681
|
const normalized = (model || DEFAULT_OPENAI_MODEL).trim().toLowerCase();
|
|
618
|
-
const match = normalized.match(/^gpt-(\d+)(?:\.(\d+))?$/);
|
|
682
|
+
const match = normalized.match(/^gpt-(\d+)(?:\.(\d+))?(-[a-z0-9.\-]+)?$/);
|
|
619
683
|
if (!match) {
|
|
620
684
|
return DEFAULT_OPENAI_IMAGE_DETAIL;
|
|
621
685
|
}
|
|
622
686
|
const major = Number(match[1]);
|
|
623
687
|
const minor = match[2] === void 0 ? null : Number(match[2]);
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
return "original";
|
|
688
|
+
const isVariant = !!match[3];
|
|
689
|
+
const supportsOriginal = major > 5 || major === 5 && minor !== null && minor >= 4;
|
|
690
|
+
if (!supportsOriginal) {
|
|
691
|
+
return DEFAULT_OPENAI_IMAGE_DETAIL;
|
|
629
692
|
}
|
|
630
|
-
return
|
|
693
|
+
return isVariant ? "high" : "original";
|
|
694
|
+
};
|
|
695
|
+
var getRenderImageDetail = (model) => {
|
|
696
|
+
const detail = getOpenAIImageDetail(model);
|
|
697
|
+
return detail === DEFAULT_OPENAI_IMAGE_DETAIL ? "high" : detail;
|
|
631
698
|
};
|
|
632
699
|
var IMAGE_URL_REGEX = /\bhttps?:\/\/[^\s<>"'()\[\]]+?\.(?:jpg|jpeg|png|gif|webp)(?:\?[^\s<>"'()\[\]]*)?/gi;
|
|
633
700
|
function transformContentWithImages(content) {
|
|
@@ -872,17 +939,44 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
872
939
|
const visionFile = !parsedContent && isImageVisionFile(attachment.name, attachment.mime);
|
|
873
940
|
const renderFrom = Math.max(0, info.renderFrom || 0);
|
|
874
941
|
const renderPlaceholder = visionFile ? makeRenderPlaceholder(attachment.storagePath) : void 0;
|
|
942
|
+
const renderDetail = platform === "openai" ? getRenderImageDetail(info.model || DEFAULT_OPENAI_MODEL) : void 0;
|
|
875
943
|
const skapiRender = visionFile && renderPlaceholder ? {
|
|
876
944
|
_skapi_render: [
|
|
877
|
-
{
|
|
945
|
+
{
|
|
946
|
+
path: attachment.storagePath,
|
|
947
|
+
from: renderFrom,
|
|
948
|
+
count: RENDER_PAGES_PER_WINDOW,
|
|
949
|
+
placeholder: renderPlaceholder,
|
|
950
|
+
name: attachment.name,
|
|
951
|
+
mime: attachment.mime,
|
|
952
|
+
detail: renderDetail,
|
|
953
|
+
auto_continue: true,
|
|
954
|
+
continue_text: buildIndexingRenderContinueTemplate(attachment, renderPlaceholder)
|
|
955
|
+
}
|
|
878
956
|
]
|
|
879
957
|
} : {};
|
|
880
|
-
const
|
|
881
|
-
const
|
|
958
|
+
const windowedRead = !visionFile && !parsedContent && windowedIndexingEnabled() && isWindowedReadFile(attachment.name, attachment.mime);
|
|
959
|
+
const windowPlaceholder = windowedRead ? makeWindowPlaceholder(attachment.storagePath) : void 0;
|
|
960
|
+
const skapiWindow = windowedRead && windowPlaceholder ? {
|
|
961
|
+
_skapi_window: [
|
|
962
|
+
{
|
|
963
|
+
path: attachment.storagePath,
|
|
964
|
+
cursor: null,
|
|
965
|
+
placeholder: windowPlaceholder,
|
|
966
|
+
name: attachment.name,
|
|
967
|
+
mime: attachment.mime,
|
|
968
|
+
kind: "window",
|
|
969
|
+
auto_continue: true,
|
|
970
|
+
continue_text: buildIndexingWindowMessage(attachment, windowPlaceholder, true)
|
|
971
|
+
}
|
|
972
|
+
]
|
|
973
|
+
} : {};
|
|
974
|
+
const pagedRead = !visionFile && !windowedRead && (continuing || !parsedContent && isPagedReadFile(attachment.name, attachment.mime));
|
|
975
|
+
const serverExtract = !visionFile && !windowedRead && !continuing && !parsedContent && !pagedRead && isServerExtractable(attachment.name, attachment.mime);
|
|
882
976
|
const placeholder = serverExtract ? makeExtractPlaceholder(attachment.storagePath) : void 0;
|
|
883
977
|
const extractContent = serverExtract && placeholder ? [{ path: attachment.storagePath, placeholder, name: attachment.name, mime: attachment.mime }] : void 0;
|
|
884
978
|
const skapiExtract = extractContent && extractContent.length ? { _skapi_extract: extractContent } : {};
|
|
885
|
-
const userMessage = visionFile && renderPlaceholder ? buildIndexingRenderMessage(attachment, renderPlaceholder, renderFrom) : continuing ? buildIndexingContinueMessage(attachment) : buildIndexingUserMessage(
|
|
979
|
+
const userMessage = visionFile && renderPlaceholder ? buildIndexingRenderMessage(attachment, renderPlaceholder, renderFrom) : windowedRead && windowPlaceholder ? buildIndexingWindowMessage(attachment, windowPlaceholder, false) : continuing ? buildIndexingContinueMessage(attachment) : buildIndexingUserMessage(
|
|
886
980
|
attachment,
|
|
887
981
|
parsedContent ? { inlineContent: parsedContent } : placeholder ? { inlineContentPlaceholder: placeholder } : pagedRead ? { pagedRead: true } : void 0
|
|
888
982
|
);
|
|
@@ -911,6 +1005,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
911
1005
|
max_output_tokens: MAX_TOKENS,
|
|
912
1006
|
...skapiExtract,
|
|
913
1007
|
...skapiRender,
|
|
1008
|
+
...skapiWindow,
|
|
914
1009
|
input: [
|
|
915
1010
|
{ role: "system", content: systemPrompt },
|
|
916
1011
|
{
|
|
@@ -956,6 +1051,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
956
1051
|
max_tokens: MAX_TOKENS,
|
|
957
1052
|
...skapiExtract,
|
|
958
1053
|
...skapiRender,
|
|
1054
|
+
...skapiWindow,
|
|
959
1055
|
system: [
|
|
960
1056
|
{
|
|
961
1057
|
type: "text",
|
|
@@ -1027,9 +1123,15 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1027
1123
|
return "";
|
|
1028
1124
|
}
|
|
1029
1125
|
var BG_INDEXING_QUEUE_SUFFIX = "-bg";
|
|
1126
|
+
function isBgIndexingQueue(queueName) {
|
|
1127
|
+
if (typeof queueName !== "string" || !queueName) return false;
|
|
1128
|
+
const prefix = queueName.split("|")[0];
|
|
1129
|
+
const idx = prefix.lastIndexOf(":");
|
|
1130
|
+
const name = idx === -1 ? prefix : prefix.slice(idx + 1);
|
|
1131
|
+
return name.slice(-BG_INDEXING_QUEUE_SUFFIX.length) === BG_INDEXING_QUEUE_SUFFIX;
|
|
1132
|
+
}
|
|
1030
1133
|
var INDEXING_COMPLETE_MARKER = "INDEXING_COMPLETE";
|
|
1031
1134
|
var MAX_INDEXING_RESUME_PASSES = 6;
|
|
1032
|
-
var MAX_VISION_RESUME_PASSES = 40;
|
|
1033
1135
|
async function getChatHistory(params, fetchOptions) {
|
|
1034
1136
|
const url = params.platform === "claude" ? ANTHROPIC_MESSAGES_API_URL : OPENAI_RESPONSES_API_URL;
|
|
1035
1137
|
const p = Object.assign(
|
|
@@ -1157,6 +1259,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1157
1259
|
cb(nowMs());
|
|
1158
1260
|
}, 16);
|
|
1159
1261
|
}
|
|
1262
|
+
function isPollStopped(res) {
|
|
1263
|
+
return !!res && typeof res === "object" && res.status === "stopped";
|
|
1264
|
+
}
|
|
1160
1265
|
var ChatSession = class {
|
|
1161
1266
|
constructor(host) {
|
|
1162
1267
|
this.typewriterQueue = Promise.resolve();
|
|
@@ -1180,8 +1285,83 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1180
1285
|
this.pendingAgentRequests = {};
|
|
1181
1286
|
this.aiChatHistoryCache = {};
|
|
1182
1287
|
this.historyItemPolls = /* @__PURE__ */ new Map();
|
|
1288
|
+
this._pauseReasons = /* @__PURE__ */ new Set();
|
|
1289
|
+
this._resuming = false;
|
|
1183
1290
|
this._lidSeq = 0;
|
|
1184
1291
|
}
|
|
1292
|
+
/**
|
|
1293
|
+
* Register a live poll so (a) a remount dedupes against it instead of stacking a
|
|
1294
|
+
* SECOND poll on the same item, and (b) pausePolling can stop it.
|
|
1295
|
+
*
|
|
1296
|
+
* `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
|
|
1297
|
+
* poll simply cannot be stopped and is left running — see pausePolling.
|
|
1298
|
+
*/
|
|
1299
|
+
_trackPoll(id, kind, p) {
|
|
1300
|
+
var stop = p && typeof p.stop === "function" ? p.stop.bind(p) : void 0;
|
|
1301
|
+
if (!stop) {
|
|
1302
|
+
console.debug("[chat-engine] poll has no stop handle", { id, kind });
|
|
1303
|
+
}
|
|
1304
|
+
this.historyItemPolls.set(id, { kind, stop });
|
|
1305
|
+
return p;
|
|
1306
|
+
}
|
|
1307
|
+
/** True while any pause reason is active. */
|
|
1308
|
+
isPollingPaused() {
|
|
1309
|
+
return this._pauseReasons.size > 0;
|
|
1310
|
+
}
|
|
1311
|
+
/**
|
|
1312
|
+
* Stop BACKGROUND polling until resumePolling. Foreground polls (a reply the user is
|
|
1313
|
+
* waiting on) keep running deliberately: their results must still land in the history
|
|
1314
|
+
* cache so resumePendingRequest can render them on return, otherwise a user who sends
|
|
1315
|
+
* a message then navigates away comes back to a permanently stuck "Thinking...".
|
|
1316
|
+
*
|
|
1317
|
+
* Server-side work is untouched; this only stops asking about it. That is safe for
|
|
1318
|
+
* document indexing because the worker drives that loop itself.
|
|
1319
|
+
*/
|
|
1320
|
+
pausePolling(reason) {
|
|
1321
|
+
this._pauseReasons.add(reason || "paused");
|
|
1322
|
+
var self = this;
|
|
1323
|
+
var stopped = [];
|
|
1324
|
+
this.historyItemPolls.forEach(function(handle, id) {
|
|
1325
|
+
if (!handle || handle.kind !== "bg") return;
|
|
1326
|
+
if (typeof handle.stop !== "function") return;
|
|
1327
|
+
try {
|
|
1328
|
+
handle.stop();
|
|
1329
|
+
} catch (e) {
|
|
1330
|
+
}
|
|
1331
|
+
stopped.push(id);
|
|
1332
|
+
});
|
|
1333
|
+
stopped.forEach(function(id) {
|
|
1334
|
+
self.historyItemPolls.delete(id);
|
|
1335
|
+
});
|
|
1336
|
+
}
|
|
1337
|
+
/**
|
|
1338
|
+
* Lift a pause reason WITHOUT running the reconcile. For a caller that is about to
|
|
1339
|
+
* reload history anyway (a view remounting), letting resumePolling also reconcile
|
|
1340
|
+
* would race that load and can double-attach.
|
|
1341
|
+
*/
|
|
1342
|
+
clearPauseReason(reason) {
|
|
1343
|
+
this._pauseReasons.delete(reason || "paused");
|
|
1344
|
+
}
|
|
1345
|
+
/**
|
|
1346
|
+
* Clear a pause reason and, once none remain, re-attach polling and reconcile.
|
|
1347
|
+
* Deliberately does NOT touch gateRefreshToken: bumping it would silently discard
|
|
1348
|
+
* the results of anything still in flight across the pause.
|
|
1349
|
+
*/
|
|
1350
|
+
resumePolling(reason) {
|
|
1351
|
+
this._pauseReasons.delete(reason || "paused");
|
|
1352
|
+
if (this._pauseReasons.size > 0 || this._resuming) return Promise.resolve();
|
|
1353
|
+
if (!this.host.isViewMounted || !this.host.isViewMounted()) return Promise.resolve();
|
|
1354
|
+
var self = this;
|
|
1355
|
+
this._resuming = true;
|
|
1356
|
+
return Promise.resolve().then(function() {
|
|
1357
|
+
self.drainBgTaskQueue();
|
|
1358
|
+
return self.loadHistory(false, self.state.gateRefreshToken);
|
|
1359
|
+
}).catch(function(e) {
|
|
1360
|
+
console.error("[chat-engine] resume polling failed", e);
|
|
1361
|
+
}).then(function() {
|
|
1362
|
+
self._resuming = false;
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1185
1365
|
_newLocalId() {
|
|
1186
1366
|
this._lidSeq += 1;
|
|
1187
1367
|
return "lid_" + this._lidSeq;
|
|
@@ -1195,29 +1375,86 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1195
1375
|
var key = this.getHistoryCacheKey();
|
|
1196
1376
|
if (!key) return;
|
|
1197
1377
|
this.aiChatHistoryCache[key] = {
|
|
1198
|
-
messages: this.state.messages.
|
|
1378
|
+
messages: this.state.messages.filter(function(m) {
|
|
1379
|
+
return m._ownerKey === void 0 || m._ownerKey === key;
|
|
1380
|
+
}),
|
|
1199
1381
|
endOfList: this.state.historyEndOfList,
|
|
1200
1382
|
startKeyHistory: this.state.historyStartKeyHistory.slice()
|
|
1201
1383
|
};
|
|
1202
1384
|
}
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1385
|
+
/**
|
|
1386
|
+
* Land a resolved reply in the history cache of a chat that is NOT currently
|
|
1387
|
+
* visible, without touching state.messages. Mirrors the cache-only path in
|
|
1388
|
+
* dispatchAgentRequest: REPLACE the trailing pending "Thinking..." bubble
|
|
1389
|
+
* (append only when there is none), and settle the matching pending user
|
|
1390
|
+
* bubble, so the cached copy never keeps a stuck "Thinking..." that a later
|
|
1391
|
+
* cache-first load would re-render forever.
|
|
1392
|
+
*/
|
|
1393
|
+
_applyReplyToCache(key, reply, serverId) {
|
|
1394
|
+
if (!key) return;
|
|
1395
|
+
var existing = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1396
|
+
var msgs = existing.messages.slice();
|
|
1397
|
+
var thIdx = -1;
|
|
1398
|
+
for (var i = msgs.length - 1; i >= 0; i--) {
|
|
1399
|
+
var m = msgs[i];
|
|
1400
|
+
if (!m || !m.isPending || m.role !== "assistant" || m.isBackgroundTask) continue;
|
|
1401
|
+
if (serverId && m._serverItemId && m._serverItemId !== serverId) continue;
|
|
1402
|
+
thIdx = i;
|
|
1403
|
+
break;
|
|
1404
|
+
}
|
|
1405
|
+
if (thIdx !== -1) {
|
|
1406
|
+
if (reply._serverItemId === void 0 && msgs[thIdx]._serverItemId !== void 0) reply._serverItemId = msgs[thIdx]._serverItemId;
|
|
1407
|
+
msgs[thIdx] = reply;
|
|
1408
|
+
} else {
|
|
1409
|
+
msgs.push(reply);
|
|
1410
|
+
}
|
|
1411
|
+
for (var j = 0; j < msgs.length; j++) {
|
|
1412
|
+
var u = msgs[j];
|
|
1413
|
+
if (!u || u.role !== "user" || u.isBackgroundTask) continue;
|
|
1414
|
+
if (!(u.isPendingQueued || u.isPendingInProcess || u.isSendingToServer)) continue;
|
|
1415
|
+
if (serverId && u._serverItemId && u._serverItemId !== serverId) continue;
|
|
1416
|
+
var settled = { role: "user", content: u.content };
|
|
1417
|
+
if (u._serverItemId !== void 0) settled._serverItemId = u._serverItemId;
|
|
1418
|
+
if (u._ownerKey !== void 0) settled._ownerKey = u._ownerKey;
|
|
1419
|
+
msgs[j] = settled;
|
|
1420
|
+
break;
|
|
1421
|
+
}
|
|
1422
|
+
this.aiChatHistoryCache[key] = {
|
|
1423
|
+
messages: msgs,
|
|
1424
|
+
endOfList: existing.endOfList,
|
|
1425
|
+
startKeyHistory: existing.startKeyHistory
|
|
1426
|
+
};
|
|
1427
|
+
}
|
|
1428
|
+
/**
|
|
1429
|
+
* serviceId/owner are passed explicitly by every caller: a request can be
|
|
1430
|
+
* dispatched after the user moved to another project, and re-reading the live
|
|
1431
|
+
* identity here would silently send the turn to THAT project instead of the
|
|
1432
|
+
* one it was composed for. Falls back to the live read only when a caller
|
|
1433
|
+
* omits them.
|
|
1434
|
+
*/
|
|
1435
|
+
_callProviderFor(platform, prompt, messages, system, model, userId, extractContent, fileUrls, serviceId, owner) {
|
|
1436
|
+
if (serviceId === void 0 || owner === void 0) {
|
|
1437
|
+
var id = this.host.getIdentity();
|
|
1438
|
+
if (serviceId === void 0) serviceId = id.serviceId;
|
|
1439
|
+
if (owner === void 0) owner = id.owner;
|
|
1440
|
+
}
|
|
1441
|
+
return platform === "openai" ? callOpenAIWithPublicMcp(prompt, serviceId, owner, messages, system, model, userId, extractContent, fileUrls) : callClaudeWithPublicMcp(prompt, serviceId, owner, messages, system, model, userId, extractContent, fileUrls);
|
|
1206
1442
|
}
|
|
1207
1443
|
dispatchAgentRequest(params) {
|
|
1208
1444
|
var self = this;
|
|
1209
1445
|
var dispatchItemId;
|
|
1210
1446
|
var sendAndPoll = function() {
|
|
1211
1447
|
return Promise.resolve(
|
|
1212
|
-
self._callProviderFor(params.aiPlatform, params.text, params.boundedMessages, params.systemPrompt, params.aiModel, params.userId, params.extractContent, params.fileUrls)
|
|
1448
|
+
self._callProviderFor(params.aiPlatform, params.text, params.boundedMessages, params.systemPrompt, params.aiModel, params.userId, params.extractContent, params.fileUrls, params.serviceId, params.owner)
|
|
1213
1449
|
).then(function(initial) {
|
|
1214
1450
|
if (initial && initial.poll && (initial.status === "pending" || initial.status === "running")) {
|
|
1215
1451
|
if (initial.id) {
|
|
1216
1452
|
if (dispatchItemId && dispatchItemId !== initial.id) self.historyItemPolls.delete(dispatchItemId);
|
|
1217
1453
|
dispatchItemId = initial.id;
|
|
1218
|
-
self.historyItemPolls.set(initial.id, true);
|
|
1219
1454
|
}
|
|
1220
|
-
|
|
1455
|
+
var dp = initial.poll({ latency: POLL_INTERVAL });
|
|
1456
|
+
if (initial.id) self._trackPoll(initial.id, "fg", dp);
|
|
1457
|
+
return dp;
|
|
1221
1458
|
}
|
|
1222
1459
|
return initial;
|
|
1223
1460
|
});
|
|
@@ -1270,20 +1507,57 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1270
1507
|
// composed = clean display text; composedForLlm carries office-extraction
|
|
1271
1508
|
// placeholders for the provider only. useBgQueue routes a post-attachment turn
|
|
1272
1509
|
// onto the "-bg" queue so it runs after indexing.
|
|
1273
|
-
dispatchComposedMessage(composed, useBgQueue, composedForLlm, extractContent, fileUrls) {
|
|
1510
|
+
dispatchComposedMessage(composed, useBgQueue, composedForLlm, extractContent, fileUrls, pinned) {
|
|
1274
1511
|
var self = this;
|
|
1275
1512
|
if (!composed) return;
|
|
1276
|
-
var id = this.host.getIdentity();
|
|
1513
|
+
var id = pinned ? pinned.identity : this.host.getIdentity();
|
|
1277
1514
|
if (id.platform === "none") return;
|
|
1278
1515
|
var llmComposed = composedForLlm || composed;
|
|
1279
|
-
var
|
|
1516
|
+
var key = !id.serviceId ? "" : id.serviceId + "#" + id.platform;
|
|
1517
|
+
var offChat = !!key && key !== this.getHistoryCacheKey();
|
|
1518
|
+
var isQueuedSend = !offChat && (useBgQueue || this.state.sending || this.state.messages.some(function(m) {
|
|
1280
1519
|
return (m.isPending || m.isPendingQueued) && !m.isBackgroundTask && !m._useBgQueue;
|
|
1281
|
-
});
|
|
1520
|
+
}));
|
|
1282
1521
|
var aiPlatform = id.platform;
|
|
1283
1522
|
var aiModel = id.model || void 0;
|
|
1284
|
-
var systemPrompt = this.host.buildSystemPrompt();
|
|
1523
|
+
var systemPrompt = pinned ? pinned.systemPrompt : this.host.buildSystemPrompt();
|
|
1285
1524
|
var userId = id.userId || id.serviceId;
|
|
1286
1525
|
var chatQueue = useBgQueue ? userId + BG_INDEXING_QUEUE_SUFFIX : userId;
|
|
1526
|
+
if (offChat) {
|
|
1527
|
+
var offHistory = (this.aiChatHistoryCache[key] ? this.aiChatHistoryCache[key].messages : []).filter(function(m) {
|
|
1528
|
+
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask;
|
|
1529
|
+
});
|
|
1530
|
+
var offBounded = buildBoundedChatMessages({
|
|
1531
|
+
platform: aiPlatform,
|
|
1532
|
+
model: aiModel,
|
|
1533
|
+
systemPrompt,
|
|
1534
|
+
serviceId: id.serviceId,
|
|
1535
|
+
history: offHistory.concat([{ role: "user", content: llmComposed }])
|
|
1536
|
+
});
|
|
1537
|
+
var offExisting = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1538
|
+
this.aiChatHistoryCache[key] = {
|
|
1539
|
+
messages: offExisting.messages.concat([
|
|
1540
|
+
{ role: "user", content: composed, _ownerKey: key },
|
|
1541
|
+
{ role: "assistant", content: "", isPending: true, isPendingInProcess: true, _ownerKey: key }
|
|
1542
|
+
]),
|
|
1543
|
+
endOfList: offExisting.endOfList,
|
|
1544
|
+
startKeyHistory: offExisting.startKeyHistory
|
|
1545
|
+
};
|
|
1546
|
+
this.dispatchAgentRequest({
|
|
1547
|
+
key,
|
|
1548
|
+
serviceId: id.serviceId,
|
|
1549
|
+
owner: id.owner,
|
|
1550
|
+
aiPlatform,
|
|
1551
|
+
aiModel,
|
|
1552
|
+
systemPrompt,
|
|
1553
|
+
text: composed,
|
|
1554
|
+
boundedMessages: offBounded.messages,
|
|
1555
|
+
userId: chatQueue,
|
|
1556
|
+
extractContent,
|
|
1557
|
+
fileUrls
|
|
1558
|
+
});
|
|
1559
|
+
return;
|
|
1560
|
+
}
|
|
1287
1561
|
if (isQueuedSend) {
|
|
1288
1562
|
var resolvedHistory = this.state.messages.filter(function(m) {
|
|
1289
1563
|
return !m.isPending && !m.isPendingQueued && !m.isPendingInProcess && !m.isPendingOlder && !m.isCancelled && !m.isBackgroundTask;
|
|
@@ -1296,15 +1570,16 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1296
1570
|
history: resolvedHistory.concat([{ role: "user", content: llmComposed }])
|
|
1297
1571
|
});
|
|
1298
1572
|
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true };
|
|
1573
|
+
if (key) queuedBubble._ownerKey = key;
|
|
1299
1574
|
if (useBgQueue) queuedBubble._useBgQueue = true;
|
|
1300
1575
|
this.state.messages.push(queuedBubble);
|
|
1301
1576
|
this.host.notify();
|
|
1302
1577
|
this.updateHistoryCache();
|
|
1303
1578
|
this.host.scrollToBottom(true);
|
|
1304
|
-
var capturedComposed = composed, capturedPlatform = aiPlatform;
|
|
1305
|
-
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls)).then(function(result) {
|
|
1306
|
-
var sendingIdx = self.state.messages.findIndex(function(m) {
|
|
1307
|
-
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user";
|
|
1579
|
+
var capturedComposed = composed, capturedPlatform = aiPlatform, capturedKey = key;
|
|
1580
|
+
Promise.resolve(this._callProviderFor(aiPlatform, composed, boundedQ.messages, systemPrompt, aiModel, chatQueue, extractContent, fileUrls, id.serviceId, id.owner)).then(function(result) {
|
|
1581
|
+
var sendingIdx = self.getHistoryCacheKey() !== capturedKey ? -1 : self.state.messages.findIndex(function(m) {
|
|
1582
|
+
return m.isSendingToServer && (m.isPendingQueued || m.isPendingInProcess) && m.role === "user" && (m._ownerKey === void 0 || m._ownerKey === capturedKey);
|
|
1308
1583
|
});
|
|
1309
1584
|
var serverId = result && typeof result.id === "string" ? result.id : void 0;
|
|
1310
1585
|
if (sendingIdx >= 0) {
|
|
@@ -1314,26 +1589,27 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1314
1589
|
self.host.notify();
|
|
1315
1590
|
}
|
|
1316
1591
|
if (result && result.poll && (result.status === "pending" || result.status === "running")) {
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1592
|
+
var qp = result.poll({ latency: POLL_INTERVAL });
|
|
1593
|
+
if (serverId) self._trackPoll(serverId, "fg", qp);
|
|
1594
|
+
return qp.then(function(res) {
|
|
1595
|
+
if (isPollStopped(res)) return;
|
|
1596
|
+
return self.onQueuedSendResponse(capturedComposed, res, capturedPlatform, serverId, capturedKey);
|
|
1320
1597
|
}).catch(function(err) {
|
|
1321
|
-
return self.onQueuedSendError(capturedComposed, err, serverId);
|
|
1598
|
+
return self.onQueuedSendError(capturedComposed, err, serverId, capturedKey);
|
|
1322
1599
|
});
|
|
1323
1600
|
}
|
|
1324
|
-
return self.onQueuedSendResponse(capturedComposed, result, capturedPlatform, serverId);
|
|
1601
|
+
return self.onQueuedSendResponse(capturedComposed, result, capturedPlatform, serverId, capturedKey);
|
|
1325
1602
|
}).catch(function(err) {
|
|
1326
|
-
return self.onQueuedSendError(capturedComposed, err, void 0);
|
|
1603
|
+
return self.onQueuedSendError(capturedComposed, err, void 0, capturedKey);
|
|
1327
1604
|
});
|
|
1328
1605
|
return;
|
|
1329
1606
|
}
|
|
1330
|
-
this.state.messages.push({ role: "user", content: composed });
|
|
1331
|
-
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true });
|
|
1607
|
+
this.state.messages.push({ role: "user", content: composed, ...key ? { _ownerKey: key } : {} });
|
|
1608
|
+
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} });
|
|
1332
1609
|
this.host.notify();
|
|
1333
1610
|
this.updateHistoryCache();
|
|
1334
1611
|
this.state.sending = true;
|
|
1335
1612
|
this.host.scrollToBottom(true);
|
|
1336
|
-
var key = this.getHistoryCacheKey();
|
|
1337
1613
|
var historyForLlm = this.state.messages.filter(function(m) {
|
|
1338
1614
|
return !m.isCancelled && !m.isBackgroundTask;
|
|
1339
1615
|
});
|
|
@@ -1367,8 +1643,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1367
1643
|
});
|
|
1368
1644
|
Promise.resolve(run).catch(function() {
|
|
1369
1645
|
}).then(function() {
|
|
1370
|
-
if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
|
|
1371
1646
|
self.state.sending = false;
|
|
1647
|
+
if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
|
|
1372
1648
|
return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
|
|
1373
1649
|
self.host.scrollToBottom(true);
|
|
1374
1650
|
});
|
|
@@ -1385,9 +1661,11 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1385
1661
|
var existing = this.state.messages[nextIdx];
|
|
1386
1662
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true, isBackgroundTask: true };
|
|
1387
1663
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1664
|
+
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1388
1665
|
this.state.messages[nextIdx] = promoted;
|
|
1389
1666
|
var placeholder = { role: "assistant", content: "", isPending: true, isPendingInProcess: true, isBackgroundTask: true };
|
|
1390
1667
|
if (existing._serverItemId !== void 0) placeholder._serverItemId = existing._serverItemId;
|
|
1668
|
+
if (existing._ownerKey !== void 0) placeholder._ownerKey = existing._ownerKey;
|
|
1391
1669
|
this.state.messages.splice(nextIdx + 1, 0, placeholder);
|
|
1392
1670
|
this.host.notify();
|
|
1393
1671
|
}
|
|
@@ -1403,14 +1681,20 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1403
1681
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true };
|
|
1404
1682
|
if (existing.isBackgroundTask) promoted.isBackgroundTask = true;
|
|
1405
1683
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1684
|
+
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1406
1685
|
if (existing.isSendingToServer) promoted.isSendingToServer = true;
|
|
1407
1686
|
this.state.messages[nextIdx] = promoted;
|
|
1408
1687
|
var placeholder = { role: "assistant", content: "", isPending: true };
|
|
1409
1688
|
if (existing._serverItemId !== void 0) placeholder._serverItemId = existing._serverItemId;
|
|
1689
|
+
if (existing._ownerKey !== void 0) placeholder._ownerKey = existing._ownerKey;
|
|
1410
1690
|
this.state.messages.splice(nextIdx + 1, 0, placeholder);
|
|
1411
1691
|
this.host.notify();
|
|
1412
1692
|
}
|
|
1413
1693
|
resolveQueuedUserBubble(serverId) {
|
|
1694
|
+
var liveKey = this.getHistoryCacheKey();
|
|
1695
|
+
var isLocal = function(m) {
|
|
1696
|
+
return m._ownerKey === void 0 || m._ownerKey === liveKey;
|
|
1697
|
+
};
|
|
1414
1698
|
var userIdx = -1;
|
|
1415
1699
|
if (serverId) {
|
|
1416
1700
|
userIdx = this.state.messages.findIndex(function(m) {
|
|
@@ -1419,19 +1703,19 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1419
1703
|
}
|
|
1420
1704
|
if (userIdx === -1) {
|
|
1421
1705
|
userIdx = this.state.messages.findIndex(function(m) {
|
|
1422
|
-
return m.isPendingInProcess && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1706
|
+
return m.isPendingInProcess && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue && isLocal(m);
|
|
1423
1707
|
});
|
|
1424
1708
|
}
|
|
1425
1709
|
if (userIdx === -1) {
|
|
1426
1710
|
userIdx = this.state.messages.findIndex(function(m) {
|
|
1427
|
-
return m.isPendingQueued && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue;
|
|
1711
|
+
return m.isPendingQueued && m.role === "user" && !m.isBackgroundTask && !m._useBgQueue && isLocal(m);
|
|
1428
1712
|
});
|
|
1429
1713
|
}
|
|
1430
1714
|
if (serverId && this.cancelledServerIds.has(serverId)) {
|
|
1431
1715
|
this.cancelledServerIds.delete(serverId);
|
|
1432
1716
|
if (userIdx >= 0) {
|
|
1433
1717
|
var ex = this.state.messages[userIdx];
|
|
1434
|
-
this.state.messages[userIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId };
|
|
1718
|
+
this.state.messages[userIdx] = { role: "user", content: ex.content, isCancelled: true, _serverItemId: ex._serverItemId, ...ex._ownerKey !== void 0 ? { _ownerKey: ex._ownerKey } : {} };
|
|
1435
1719
|
var thIdx = this.state.messages.findIndex(function(m, i) {
|
|
1436
1720
|
return i > userIdx && m.isPending && m.role === "assistant" && !m.isBackgroundTask;
|
|
1437
1721
|
});
|
|
@@ -1444,6 +1728,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1444
1728
|
var exist = this.state.messages[userIdx];
|
|
1445
1729
|
var repl = { role: "user", content: exist.content };
|
|
1446
1730
|
if (exist._serverItemId !== void 0) repl._serverItemId = exist._serverItemId;
|
|
1731
|
+
if (exist._ownerKey !== void 0) repl._ownerKey = exist._ownerKey;
|
|
1447
1732
|
this.state.messages[userIdx] = repl;
|
|
1448
1733
|
}
|
|
1449
1734
|
var thinkingIdx = userIdx >= 0 ? this.state.messages.findIndex(function(m, i) {
|
|
@@ -1456,8 +1741,14 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1456
1741
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
1457
1742
|
else this.state.messages.push(msg);
|
|
1458
1743
|
}
|
|
1459
|
-
onQueuedSendResponse(_composed, response, platform, serverId) {
|
|
1744
|
+
onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
|
|
1460
1745
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
1746
|
+
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
1747
|
+
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." };
|
|
1748
|
+
this._applyReplyToCache(ownerKey, offReply, serverId);
|
|
1749
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
1750
|
+
return;
|
|
1751
|
+
}
|
|
1461
1752
|
var targetIdx = this.resolveQueuedUserBubble(serverId);
|
|
1462
1753
|
if (targetIdx === void 0) {
|
|
1463
1754
|
this.host.notify();
|
|
@@ -1491,8 +1782,14 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1491
1782
|
this.host.notify();
|
|
1492
1783
|
this.host.scrollToBottom(true);
|
|
1493
1784
|
}
|
|
1494
|
-
onQueuedSendError(_composed, err, serverId) {
|
|
1785
|
+
onQueuedSendError(_composed, err, serverId, ownerKey) {
|
|
1495
1786
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
1787
|
+
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
1788
|
+
var isGone = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
1789
|
+
this._applyReplyToCache(ownerKey, isGone ? { role: "assistant", content: "Request was cancelled.", isError: true } : { role: "assistant", content: getErrorMessage(err), isError: true }, serverId);
|
|
1790
|
+
if (serverId) this.cancelledServerIds.delete(serverId);
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1496
1793
|
var isNotExists = err && (err.code === "NOT_EXISTS" || err.body && err.body.code === "NOT_EXISTS");
|
|
1497
1794
|
if (isNotExists) {
|
|
1498
1795
|
var userIdx = serverId ? this.state.messages.findIndex(function(m) {
|
|
@@ -1896,24 +2193,31 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1896
2193
|
}
|
|
1897
2194
|
this.bgTaskQueue.forEach(function(entry) {
|
|
1898
2195
|
if (entry.serviceId !== svcId || entry.platform !== plat) return;
|
|
1899
|
-
if (presentIds[entry.id])
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
2196
|
+
if (!presentIds[entry.id]) {
|
|
2197
|
+
var isRunning = entry.status === "running";
|
|
2198
|
+
var userBubble = { role: "user", content: self.host.formatIndexingLabel(entry.filename, entry.mime, entry.size, entry.storagePath, entry.isReindex), isBackgroundTask: true, _serverItemId: entry.id };
|
|
2199
|
+
if (isRunning) userBubble.isPendingInProcess = true;
|
|
2200
|
+
else userBubble.isPendingQueued = true;
|
|
2201
|
+
self.state.messages.push(userBubble);
|
|
2202
|
+
if (isRunning) {
|
|
2203
|
+
self.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, isBackgroundTask: true, _serverItemId: entry.id });
|
|
2204
|
+
}
|
|
2205
|
+
presentIds[entry.id] = true;
|
|
2206
|
+
self.host.notify();
|
|
2207
|
+
self.updateHistoryCache();
|
|
2208
|
+
self.host.scrollToBottom(false);
|
|
1907
2209
|
}
|
|
1908
|
-
|
|
1909
|
-
self.host.notify();
|
|
1910
|
-
self.updateHistoryCache();
|
|
1911
|
-
self.host.scrollToBottom(false);
|
|
1912
|
-
if (!self.historyItemPolls.has(entry.id) && typeof entry.poll === "function") {
|
|
1913
|
-
self.historyItemPolls.set(entry.id, true);
|
|
2210
|
+
if (!self.isPollingPaused() && !self.historyItemPolls.has(entry.id) && typeof entry.poll === "function") {
|
|
1914
2211
|
var capturedId = entry.id, capturedPlat = plat;
|
|
1915
2212
|
var capturedEntry = entry;
|
|
1916
|
-
|
|
2213
|
+
var wasStopped = false;
|
|
2214
|
+
var bp = entry.poll({ latency: POLL_INTERVAL });
|
|
2215
|
+
self._trackPoll(entry.id, "bg", bp);
|
|
2216
|
+
bp.then(function(response) {
|
|
2217
|
+
if (isPollStopped(response)) {
|
|
2218
|
+
wasStopped = true;
|
|
2219
|
+
return;
|
|
2220
|
+
}
|
|
1917
2221
|
self.handleHistoryItemResolution(capturedId, response, capturedPlat);
|
|
1918
2222
|
self.maybeResumeIndexing(capturedEntry, response, capturedPlat);
|
|
1919
2223
|
}).catch(function(err) {
|
|
@@ -1929,6 +2233,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1929
2233
|
self.updateHistoryCache();
|
|
1930
2234
|
}
|
|
1931
2235
|
}).then(function() {
|
|
2236
|
+
if (wasStopped) return;
|
|
1932
2237
|
var qi = self.bgTaskQueue.findIndex(function(q) {
|
|
1933
2238
|
return q.id === capturedId;
|
|
1934
2239
|
});
|
|
@@ -1939,25 +2244,30 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1939
2244
|
this.promoteNextBgQueuedToRunning();
|
|
1940
2245
|
}
|
|
1941
2246
|
// Resume-across-passes: if a background INDEXING task for a paged file (spreadsheet or
|
|
1942
|
-
//
|
|
2247
|
+
// text) finished WITHOUT the completion marker, the agent ran out of room before reading
|
|
1943
2248
|
// the whole file - dispatch a CONTINUE pass (up to a cap) that resumes from where the
|
|
1944
2249
|
// already-saved records leave off. Additive + guarded so it never loops forever and
|
|
1945
2250
|
// never breaks the resolution path.
|
|
2251
|
+
//
|
|
2252
|
+
// VISION files (PDFs rendered to page images) are NOT resumed here: the proxy worker
|
|
2253
|
+
// advances their page window itself, off the renderer's true page count. Driving them
|
|
2254
|
+
// from the browser is what used to lose pages on long documents - the chain lived in tab
|
|
2255
|
+
// memory (a reload or a closed tab ended it), and it stopped whenever the model claimed
|
|
2256
|
+
// completion, which on an 88-page file happened at page 15. Continuing to dispatch here
|
|
2257
|
+
// as well would now double-index every window.
|
|
1946
2258
|
maybeResumeIndexing(entry, response, platform) {
|
|
1947
2259
|
var self = this;
|
|
1948
2260
|
try {
|
|
1949
2261
|
if (!entry || !entry.storagePath) return;
|
|
1950
2262
|
if (!isPagedReadFile(entry.filename, entry.mime)) return;
|
|
2263
|
+
if (isImageVisionFile(entry.filename, entry.mime)) return;
|
|
1951
2264
|
if (isErrorResponseBody(response)) return;
|
|
1952
2265
|
var answer = (platform === "openai" ? extractOpenAIText(response) : extractClaudeText(response)) || "";
|
|
1953
2266
|
if (answer.indexOf(INDEXING_COMPLETE_MARKER) !== -1) return;
|
|
1954
2267
|
var pass = (entry.resumePass || 0) + 1;
|
|
1955
|
-
|
|
1956
|
-
var maxPasses = isVision ? MAX_VISION_RESUME_PASSES : MAX_INDEXING_RESUME_PASSES;
|
|
1957
|
-
if (pass > maxPasses) return;
|
|
2268
|
+
if (pass > MAX_INDEXING_RESUME_PASSES) return;
|
|
1958
2269
|
var id = this.host.getIdentity();
|
|
1959
2270
|
if (!id || id.platform === "none" || id.serviceId !== entry.serviceId) return;
|
|
1960
|
-
var renderFrom = isVision ? pass * RENDER_PAGES_PER_WINDOW : void 0;
|
|
1961
2271
|
notifyAgentContinueIndexing({
|
|
1962
2272
|
platform: id.platform,
|
|
1963
2273
|
model: id.model,
|
|
@@ -1966,7 +2276,6 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1966
2276
|
userId: id.userId || id.serviceId,
|
|
1967
2277
|
serviceName: id.serviceName,
|
|
1968
2278
|
serviceDescription: id.serviceDescription,
|
|
1969
|
-
renderFrom,
|
|
1970
2279
|
attachment: {
|
|
1971
2280
|
name: entry.filename,
|
|
1972
2281
|
storagePath: entry.storagePath,
|
|
@@ -2006,6 +2315,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2006
2315
|
loadHistory(fetchMore, token) {
|
|
2007
2316
|
var self = this;
|
|
2008
2317
|
var id = this.host.getIdentity();
|
|
2318
|
+
var loadKey = !id.serviceId || id.platform === "none" ? "" : id.serviceId + "#" + id.platform;
|
|
2009
2319
|
if (token === void 0) token = this.state.gateRefreshToken;
|
|
2010
2320
|
if (this.state.loadingHistory && this.state.historyRequestToken === token || id.platform === "none" || !id.serviceId) {
|
|
2011
2321
|
return Promise.resolve();
|
|
@@ -2028,9 +2338,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2028
2338
|
if (token !== self.state.gateRefreshToken) return;
|
|
2029
2339
|
var chatList = history && Array.isArray(history.list) ? history.list : [];
|
|
2030
2340
|
chatList.forEach(function(item) {
|
|
2031
|
-
if (
|
|
2341
|
+
if (isBgIndexingQueue(item.queue_name)) {
|
|
2032
2342
|
var userText = extractLastUserTextFromRequest(item.request_body);
|
|
2033
|
-
if (typeof userText === "string" && userText.indexOf("A new file has just been uploaded") === 0) item._isBgTask = true;
|
|
2343
|
+
if (typeof userText === "string" && (userText.indexOf("A new file has just been uploaded") === 0 || userText.indexOf("CONTINUE indexing") === 0)) item._isBgTask = true;
|
|
2034
2344
|
else item._isOnBgQueue = true;
|
|
2035
2345
|
}
|
|
2036
2346
|
});
|
|
@@ -2062,6 +2372,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2062
2372
|
for (var ri = 0; ri < self.state.messages.length; ri++) {
|
|
2063
2373
|
var mm = self.state.messages[ri];
|
|
2064
2374
|
if (mm.isBackgroundTask) continue;
|
|
2375
|
+
if (mm._ownerKey !== void 0 && mm._ownerKey !== loadKey) continue;
|
|
2065
2376
|
if (mm._serverItemId && serverIds[mm._serverItemId]) continue;
|
|
2066
2377
|
if (!mm._serverItemId) {
|
|
2067
2378
|
if (mappedHasPendingAssistant) continue;
|
|
@@ -2106,11 +2417,12 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2106
2417
|
if (!item.poll || !item.id) return;
|
|
2107
2418
|
if (self.historyItemPolls.has(item.id)) return;
|
|
2108
2419
|
if (self.pendingAgentRequests[self.getHistoryCacheKey()] && !item._isBgTask && !item._isOnBgQueue) return;
|
|
2109
|
-
|
|
2420
|
+
if ((item._isBgTask || item._isOnBgQueue) && self.isPollingPaused()) return;
|
|
2110
2421
|
var capturedId = item.id;
|
|
2111
2422
|
var pp = item.poll({
|
|
2112
2423
|
latency: POLL_INTERVAL,
|
|
2113
2424
|
onResponse: function(response) {
|
|
2425
|
+
if (isPollStopped(response)) return;
|
|
2114
2426
|
self.handleHistoryItemResolution(capturedId, response, platform);
|
|
2115
2427
|
},
|
|
2116
2428
|
onError: function(err) {
|
|
@@ -2146,6 +2458,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2146
2458
|
}
|
|
2147
2459
|
}
|
|
2148
2460
|
});
|
|
2461
|
+
self._trackPoll(capturedId, item._isBgTask || item._isOnBgQueue ? "bg" : "fg", pp);
|
|
2149
2462
|
if (pp && pp.catch) pp.catch(function() {
|
|
2150
2463
|
});
|
|
2151
2464
|
});
|
|
@@ -2362,7 +2675,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2362
2675
|
(function() {
|
|
2363
2676
|
var MCP_PROD = "https://mcp.broadwayinc.computer";
|
|
2364
2677
|
var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
|
|
2365
|
-
var BQ_VERSION = "1.6.
|
|
2678
|
+
var BQ_VERSION = "1.6.1" ;
|
|
2366
2679
|
var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
|
|
2367
2680
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
2368
2681
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -5625,7 +5938,17 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
5625
5938
|
if (!S._visBound && typeof document !== "undefined" && document.addEventListener) {
|
|
5626
5939
|
S._visBound = true;
|
|
5627
5940
|
document.addEventListener("visibilitychange", function() {
|
|
5628
|
-
if (document.visibilityState === "
|
|
5941
|
+
if (document.visibilityState === "hidden") {
|
|
5942
|
+
if (session && session.pausePolling) session.pausePolling("hidden");
|
|
5943
|
+
return;
|
|
5944
|
+
}
|
|
5945
|
+
if (document.visibilityState === "visible") {
|
|
5946
|
+
var refreshed = S.user ? ensureMcpGrantFresh() : null;
|
|
5947
|
+
Promise.resolve(refreshed).catch(function() {
|
|
5948
|
+
}).then(function() {
|
|
5949
|
+
if (session && session.resumePolling) session.resumePolling("hidden");
|
|
5950
|
+
});
|
|
5951
|
+
}
|
|
5629
5952
|
});
|
|
5630
5953
|
}
|
|
5631
5954
|
boot();
|