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