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