bunnyquery 1.11.1 → 1.13.0
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/README.md +5 -0
- package/bunnyquery.css +59 -0
- package/bunnyquery.js +332 -9
- package/dist/engine.cjs +263 -8
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +93 -1
- package/dist/engine.d.ts +93 -1
- package/dist/engine.mjs +263 -9
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/errors.ts +15 -0
- package/src/engine/history.ts +15 -2
- package/src/engine/host.ts +4 -0
- package/src/engine/index.ts +1 -0
- package/src/engine/indexing_groups.ts +24 -3
- package/src/engine/prompts/chat_system_prompt.ts +23 -0
- package/src/engine/session.ts +277 -6
- package/styles/chat.css +59 -0
package/dist/engine.mjs
CHANGED
|
@@ -357,10 +357,15 @@ function buildChatSystemPrompt(params) {
|
|
|
357
357
|
const { projectId, serviceName, serviceDescription, greeting, canUpload, client } = params;
|
|
358
358
|
const g = params.indexAccessGroup;
|
|
359
359
|
const indexGroupLiteral = typeof g === "number" ? String(g) : g === "public" || g === "private" || g === "authorized" || g === "admin" ? `"${g}"` : '"authorized"';
|
|
360
|
+
const groundingRules = `ANSWER ONLY FROM THE DATA. Every fact you state about this project's data - a name, a number, a date, a status, what a document says, whether something exists - must come from one of four places: a tool result returned in THIS conversation, a file or image attached to it (including one you read through its attachment link), the project description at the end of this prompt, or what the user told you themselves in this chat. Your general knowledge, the open internet and a plausible guess are NOT sources. Never use them to answer a question about the user's data, to fill a gap in it, or to complete a record that came back partial. A value that is not in what you retrieved is unknown: say it is not in the data instead of supplying a likely one.
|
|
361
|
+
WHEN THE DATA DOES NOT HAVE IT, SAY SO. Once you have searched the way these rules require and nothing answers the question, tell the user plainly, in their language, that this project's data does not contain it, and say what you searched (the tables, files or terms) so they can tell missing data from a missed search. Do not answer from memory instead, and do not turn a missing answer into a general explanation of the topic. Offer what would let you answer it: ${canUpload === false ? `another name or file to look under, or asking the project's owner to add it` : `the document to upload, or another name or file to look under`}.
|
|
362
|
+
KEEP WHAT THE DATA SAYS APART FROM WHAT YOU WORKED OUT. A total you computed, a comparison, a trend, an interpretation or a recommendation is yours, not the data's: label it as such ("calculated from...", "this suggests...") and name the rows or files it was made from. Never present an estimate, an assumption or an inference as something the data states.
|
|
363
|
+
These three rules govern every claim about the project's data. They do not limit the About BunnyQuery section (answer product questions from it), a greeting, or text the user explicitly asks you to write.`;
|
|
360
364
|
let systemPrompt = `
|
|
361
365
|
You are a dedicated assistant for the project ID: "${projectId}".
|
|
362
366
|
Scope: Only answer questions about this project and its data. Do not answer questions about other projects or topics unrelated to this project. When the user refers to "my database", "my data", or "my files", treat those as references to this project's database and file storage. The ONE exception is BunnyQuery itself - what this app is, what it can do, and how to use it - which is always in scope: answer it from the "About BunnyQuery" section at the end of this prompt.
|
|
363
367
|
Knowledge lookup: Before saying you don't know or that something isn't in the chat history, ALWAYS query this project's database through the available MCP tools to look for the answer. The user's data is the source of truth - the chat transcript is not. Only respond with "I don't know" or "I couldn't find that" after you have actually searched the project's data and come back empty.
|
|
368
|
+
${groundingRules}
|
|
364
369
|
NUMBERS FROM A SPREADSHEET: use queryGrid, never mental arithmetic over records. A total, a count, an average, a "how many mention X", a "which one is biggest" - all of those are computed server-side over EVERY row of the file and come back with the sheet, the row count and the row numbers they were made from. Records are a SAMPLE, and a sample added up is a confident wrong number. Quote the row count and the sheet alongside the figure so the reader can check it.
|
|
365
370
|
CALL queryGrid describe FIRST, before any figure. Workbooks routinely state the same money more than once: a detail sheet, then per-song, per-album and per-artist sheets that each re-total it, plus a summary sheet whose bottom row is the file total. Those look like four different answers and are one. describe names which sheets restate which, and which rows are totals. Pick ONE sheet, say which you picked, and never add figures across a sheet and its summary. If the reply carries a warning about restatement, repeat it to the user.
|
|
366
371
|
A FILE TOTAL IS NOT A ROW'S TOTAL. The biggest number on a summary sheet is the whole file, not the thing that was asked about. Before quoting any figure, check it is scoped to what the question named: filter by the column that identifies it and report how many rows matched.
|
|
@@ -625,6 +630,11 @@ function getErrorMessage(input) {
|
|
|
625
630
|
}
|
|
626
631
|
return "Something went wrong.";
|
|
627
632
|
}
|
|
633
|
+
function isRetriedFailure(input) {
|
|
634
|
+
var envErr = csrEnvelopeError(input);
|
|
635
|
+
if (envErr !== void 0) input = envErr;
|
|
636
|
+
return !!input && typeof input === "object" && input.retried === true;
|
|
637
|
+
}
|
|
628
638
|
function isErrorResponseBody(response) {
|
|
629
639
|
var envErr = csrEnvelopeError(response);
|
|
630
640
|
if (envErr !== void 0) response = envErr;
|
|
@@ -3248,8 +3258,9 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
3248
3258
|
var createdTs = Number(item && item.created);
|
|
3249
3259
|
var updatedTs = Number(item && item.updated);
|
|
3250
3260
|
var executedTs = Number(item && item.executed);
|
|
3261
|
+
var respondedTs = Number(item && item.responded);
|
|
3251
3262
|
var userTs = isFinite(createdTs) && createdTs > 0 ? createdTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : void 0;
|
|
3252
|
-
var replyTs = isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
3263
|
+
var replyTs = isFinite(respondedTs) && respondedTs > 0 ? respondedTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
3253
3264
|
if (userText) {
|
|
3254
3265
|
var displayContent;
|
|
3255
3266
|
var indexFile = void 0;
|
|
@@ -3296,6 +3307,7 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
3296
3307
|
mapped.push(ph);
|
|
3297
3308
|
} else if (isQueued) ; else if (isErrorResponse) {
|
|
3298
3309
|
var em = { role: "assistant", content: getErrorMessage(response), isError: true };
|
|
3310
|
+
if (isRetriedFailure(response)) em.isRetried = true;
|
|
3299
3311
|
if (item._fromBgChain) em._fromBgChain = true;
|
|
3300
3312
|
if (item._isBgTask) em.isBackgroundTask = true;
|
|
3301
3313
|
if (serverItemId !== void 0) em._serverItemId = serverItemId;
|
|
@@ -3875,6 +3887,13 @@ var ChatSession = class {
|
|
|
3875
3887
|
// the request budget MAX_CONCURRENT_BG_POLLS exists to protect.
|
|
3876
3888
|
/** Live streams by server item id. One per in-flight streamed turn. */
|
|
3877
3889
|
this.liveStreams = {};
|
|
3890
|
+
/** When skapi recorded a turn's response as having finished arriving, by server
|
|
3891
|
+
* item id. Read off the terminal envelope of a STREAMED turn (the only settle
|
|
3892
|
+
* that carries it, see _settleLiveStream) and used as the answer bubble's time
|
|
3893
|
+
* in place of this browser's clock. Entries are never removed: a settle reaches
|
|
3894
|
+
* here twice (the poll's onResponse, then the promise), and the value for an id
|
|
3895
|
+
* cannot change. */
|
|
3896
|
+
this._respondedAt = {};
|
|
3878
3897
|
// ─── compact-stub hydration ─────────────────────────────────────────────
|
|
3879
3898
|
// Split-fetch bg pages arrive as label stubs (no bodies). When the user
|
|
3880
3899
|
// expands a row, the real reply text is fetched per item (csr-poll point
|
|
@@ -4324,7 +4343,9 @@ var ChatSession = class {
|
|
|
4324
4343
|
timers = [];
|
|
4325
4344
|
};
|
|
4326
4345
|
var stopBase = base && typeof base.stop === "function" ? base.stop.bind(base) : null;
|
|
4346
|
+
var resolveRaced = null;
|
|
4327
4347
|
var raced = new Promise(function(resolve, reject) {
|
|
4348
|
+
resolveRaced = resolve;
|
|
4328
4349
|
base.then(function(res) {
|
|
4329
4350
|
if (settled) return;
|
|
4330
4351
|
settled = true;
|
|
@@ -4365,9 +4386,16 @@ var ChatSession = class {
|
|
|
4365
4386
|
});
|
|
4366
4387
|
});
|
|
4367
4388
|
raced.stop = function() {
|
|
4389
|
+
if (settled) return;
|
|
4368
4390
|
settled = true;
|
|
4369
4391
|
clearProbes();
|
|
4370
|
-
if (stopBase)
|
|
4392
|
+
if (stopBase) {
|
|
4393
|
+
try {
|
|
4394
|
+
stopBase();
|
|
4395
|
+
} catch (e) {
|
|
4396
|
+
}
|
|
4397
|
+
}
|
|
4398
|
+
if (resolveRaced) resolveRaced({ id: itemId, status: "stopped" });
|
|
4371
4399
|
};
|
|
4372
4400
|
return raced;
|
|
4373
4401
|
}
|
|
@@ -4391,6 +4419,23 @@ var ChatSession = class {
|
|
|
4391
4419
|
});
|
|
4392
4420
|
return n;
|
|
4393
4421
|
}
|
|
4422
|
+
_noteRespondedAt(itemId, response) {
|
|
4423
|
+
if (!itemId || !isCsrStatusEnvelope(response)) return;
|
|
4424
|
+
var at = Number(response.responded);
|
|
4425
|
+
if (!isFinite(at) || at <= 0) return;
|
|
4426
|
+
if (!this._respondedAt) this._respondedAt = {};
|
|
4427
|
+
this._respondedAt[itemId] = at;
|
|
4428
|
+
}
|
|
4429
|
+
/** The time a live answer bubble is stamped with: when skapi received the
|
|
4430
|
+
* response, if the settle told us, else now. "Now" is what every bubble got
|
|
4431
|
+
* before, and it is still right for a buffered turn, whose settle hands back the
|
|
4432
|
+
* destination's body and nothing of skapi's; a history load then replaces it
|
|
4433
|
+
* with the server's value. */
|
|
4434
|
+
_replyStampFor(msg) {
|
|
4435
|
+
var id = msg ? msg._serverItemId : void 0;
|
|
4436
|
+
var at = typeof id === "string" && id && this._respondedAt ? this._respondedAt[id] : void 0;
|
|
4437
|
+
return typeof at === "number" ? at : wallClockNow();
|
|
4438
|
+
}
|
|
4394
4439
|
/**
|
|
4395
4440
|
* Open (or re-open) the live stream for `itemId`, or null when this poll must
|
|
4396
4441
|
* not carry one.
|
|
@@ -4580,6 +4625,7 @@ var ChatSession = class {
|
|
|
4580
4625
|
_settleLiveStream(st, response) {
|
|
4581
4626
|
this._closeLiveStream(st, true);
|
|
4582
4627
|
if (!isCsrStatusEnvelope(response)) return response;
|
|
4628
|
+
this._noteRespondedAt(st.id, response);
|
|
4583
4629
|
if (response.status !== "resolved") return response;
|
|
4584
4630
|
if (!this._mayFinalize(st)) this._rec().incomplete[st.id] = true;
|
|
4585
4631
|
if (st.finalBody == null) return response;
|
|
@@ -5380,6 +5426,7 @@ var ChatSession = class {
|
|
|
5380
5426
|
}
|
|
5381
5427
|
return response;
|
|
5382
5428
|
}).then(function(response) {
|
|
5429
|
+
if (isPollStopped(response) || self._isCancelledPollResult(response)) return { content: "", isError: false, cancelled: true };
|
|
5383
5430
|
if (isErrorResponseBody(response)) return { content: getErrorMessage(response), isError: true };
|
|
5384
5431
|
var answer = params.aiPlatform === "openai" ? extractOpenAIText(response) : extractClaudeText(response);
|
|
5385
5432
|
answer = (answer || "").trim();
|
|
@@ -5389,6 +5436,10 @@ var ChatSession = class {
|
|
|
5389
5436
|
}).then(function(result) {
|
|
5390
5437
|
delete self.pendingAgentRequests[params.key];
|
|
5391
5438
|
if (dispatchItemId) self.historyItemPolls.delete(dispatchItemId);
|
|
5439
|
+
if (result.cancelled) {
|
|
5440
|
+
if (dispatchItemId) self._settleCancelledTurn(dispatchItemId, params.key);
|
|
5441
|
+
return result;
|
|
5442
|
+
}
|
|
5392
5443
|
var reply = { role: "assistant", content: result.content, isError: result.isError };
|
|
5393
5444
|
if (dispatchItemId) reply._serverItemId = dispatchItemId;
|
|
5394
5445
|
self._applyReplyToCache(params.key, reply, dispatchItemId);
|
|
@@ -5867,8 +5918,9 @@ var ChatSession = class {
|
|
|
5867
5918
|
}
|
|
5868
5919
|
});
|
|
5869
5920
|
Promise.resolve(run).catch(function() {
|
|
5870
|
-
}).then(function() {
|
|
5921
|
+
}).then(function(result) {
|
|
5871
5922
|
self.state.sending = false;
|
|
5923
|
+
if (result && result.cancelled) return;
|
|
5872
5924
|
if (!(self.host.isViewMounted() && self.getHistoryCacheKey() === key)) return;
|
|
5873
5925
|
return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
|
|
5874
5926
|
self.host.scrollToBottomIfSticky(true);
|
|
@@ -6015,7 +6067,7 @@ var ChatSession = class {
|
|
|
6015
6067
|
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
6016
6068
|
}
|
|
6017
6069
|
insertAtTarget(msg, targetIdx) {
|
|
6018
|
-
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts =
|
|
6070
|
+
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts = this._replyStampFor(msg);
|
|
6019
6071
|
var tgt = targetIdx >= 0 ? this.state.messages[targetIdx] : void 0;
|
|
6020
6072
|
var replaceable = !!tgt && !!tgt.isPending && !tgt.isBackgroundTask && this._isOwnPlaceholderOf(targetIdx, this._owningUserIndex(targetIdx));
|
|
6021
6073
|
if (replaceable) this.state.messages[targetIdx] = msg;
|
|
@@ -6047,6 +6099,10 @@ var ChatSession = class {
|
|
|
6047
6099
|
}
|
|
6048
6100
|
onQueuedSendResponse(_composed, response, platform, serverId, ownerKey) {
|
|
6049
6101
|
if (serverId) this.historyItemPolls.delete(serverId);
|
|
6102
|
+
if (this._isCancelledPollResult(response)) {
|
|
6103
|
+
if (serverId) this._settleCancelledTurn(serverId, ownerKey);
|
|
6104
|
+
return;
|
|
6105
|
+
}
|
|
6050
6106
|
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
6051
6107
|
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." };
|
|
6052
6108
|
this._applyReplyToCache(ownerKey, offReply, serverId);
|
|
@@ -6246,6 +6302,191 @@ var ChatSession = class {
|
|
|
6246
6302
|
}
|
|
6247
6303
|
});
|
|
6248
6304
|
}
|
|
6305
|
+
/**
|
|
6306
|
+
* Stop waiting for the reply to the chat turn that is running now, and move on.
|
|
6307
|
+
*
|
|
6308
|
+
* `msg` is the turn's pending ASSISTANT bubble (the dot-trail "Thinking"
|
|
6309
|
+
* placeholder), which is where both views draw the control. A turn's two bubbles
|
|
6310
|
+
* carry the same _serverItemId, so it is also how the request is found.
|
|
6311
|
+
* cancelQueuedMessage is the sibling for a turn still waiting in the queue and
|
|
6312
|
+
* takes the USER bubble; the two differ in what they are handed and in what has
|
|
6313
|
+
* to be unwound afterwards, not in what the server is asked.
|
|
6314
|
+
*
|
|
6315
|
+
* What ends the wait:
|
|
6316
|
+
* 1. csr-cancel marks the running row cancelled. The worker stops relaying and
|
|
6317
|
+
* never stores an answer for it, so the reply cannot turn up later on this
|
|
6318
|
+
* device or another, and history replays the turn as cancelled;
|
|
6319
|
+
* 2. the poll on it is stopped, which settles whichever chain was awaiting it
|
|
6320
|
+
* with a stopped result: dispatchAgentRequest's for an immediate send, the
|
|
6321
|
+
* queued chain for a promoted turn, the history poll after a reload. Each
|
|
6322
|
+
* reads a stopped or cancelled result as "no reply" and releases what it
|
|
6323
|
+
* holds (state.sending, pendingAgentRequests, historyItemPolls), which is
|
|
6324
|
+
* what lets the next send go out immediately instead of queueing;
|
|
6325
|
+
* 3. the bubbles settle at once, without waiting for that chain: the user
|
|
6326
|
+
* bubble takes its cancelled form, the placeholder goes, and the next queued
|
|
6327
|
+
* turn, if there is one, is promoted so its own "Thinking" appears.
|
|
6328
|
+
*
|
|
6329
|
+
* What it does NOT do: the provider call already in flight on the worker runs
|
|
6330
|
+
* to its end (a streamed one stops within about a second). A message sent
|
|
6331
|
+
* behind it on the same queue starts once the worker acks the cancelled row.
|
|
6332
|
+
*
|
|
6333
|
+
* Refused without a server id (the ack has not come back yet; the views disable
|
|
6334
|
+
* the control until it has) and while a cancel is already out. Indexing passes
|
|
6335
|
+
* are not this method's business: their Stop is cancelIndexingGroup, which ends
|
|
6336
|
+
* the file's whole chain.
|
|
6337
|
+
*/
|
|
6338
|
+
cancelPendingReply(msg, idx) {
|
|
6339
|
+
var self = this;
|
|
6340
|
+
var id = this.host.getIdentity();
|
|
6341
|
+
var serverId = msg._serverItemId;
|
|
6342
|
+
if (!serverId || msg._cancelling) return;
|
|
6343
|
+
if (msg.role !== "assistant" || !msg.isPending || msg.isBackgroundTask) return;
|
|
6344
|
+
var platform = id.platform;
|
|
6345
|
+
if (platform !== "claude" && platform !== "openai") return;
|
|
6346
|
+
var url = platform === "claude" ? ANTHROPIC_MESSAGES_API_URL : OPENAI_RESPONSES_API_URL;
|
|
6347
|
+
var queueBase = id.userId || id.projectId;
|
|
6348
|
+
var queue = msg._useBgQueue ? bgIndexingQueueName(queueBase) : queueBase;
|
|
6349
|
+
var findPlaceholder = function() {
|
|
6350
|
+
return self.state.messages.findIndex(function(m) {
|
|
6351
|
+
return m._serverItemId === serverId && m.role === "assistant" && !!m.isPending;
|
|
6352
|
+
});
|
|
6353
|
+
};
|
|
6354
|
+
var at = this.state.messages[idx] && this.state.messages[idx]._serverItemId === serverId && this.state.messages[idx].role === "assistant" ? idx : findPlaceholder();
|
|
6355
|
+
if (at !== -1) {
|
|
6356
|
+
this.state.messages[at] = Object.assign({}, this.state.messages[at], { _cancelling: true, _cancelError: void 0 });
|
|
6357
|
+
}
|
|
6358
|
+
this.host.notify();
|
|
6359
|
+
var unmark = function(errMsg) {
|
|
6360
|
+
var ci = findPlaceholder();
|
|
6361
|
+
if (ci === -1) return;
|
|
6362
|
+
self.state.messages[ci] = Object.assign({}, self.state.messages[ci], { _cancelling: false, _cancelError: errMsg });
|
|
6363
|
+
self.host.notify();
|
|
6364
|
+
};
|
|
6365
|
+
Promise.resolve(this.host.cancelRequest({
|
|
6366
|
+
url,
|
|
6367
|
+
method: "POST",
|
|
6368
|
+
id: serverId,
|
|
6369
|
+
queue,
|
|
6370
|
+
service: id.projectId,
|
|
6371
|
+
owner: id.owner
|
|
6372
|
+
})).then(function(result) {
|
|
6373
|
+
if (result && result.removed) {
|
|
6374
|
+
self._stopPoll(serverId);
|
|
6375
|
+
self._settleCancelledTurn(serverId);
|
|
6376
|
+
return;
|
|
6377
|
+
}
|
|
6378
|
+
unmark(void 0);
|
|
6379
|
+
}).catch(function(err) {
|
|
6380
|
+
unmark(err && typeof err.message === "string" && err.message ? err.message : "Could not stop this request.");
|
|
6381
|
+
});
|
|
6382
|
+
}
|
|
6383
|
+
/** The cancelled form of a request bubble, with everything that keeps it in
|
|
6384
|
+
* place carried over: the file markers that hold an indexing pass in its
|
|
6385
|
+
* collapsed row, the chat it belongs to, its send time and its render key. */
|
|
6386
|
+
_cancelledCopyOf(m, itemId) {
|
|
6387
|
+
var cancelled = { role: "user", content: m.content, isCancelled: true };
|
|
6388
|
+
if (itemId !== void 0) cancelled._serverItemId = itemId;
|
|
6389
|
+
else if (m._serverItemId !== void 0) cancelled._serverItemId = m._serverItemId;
|
|
6390
|
+
if (m.isBackgroundTask) cancelled.isBackgroundTask = true;
|
|
6391
|
+
if (m._indexFile) cancelled._indexFile = m._indexFile;
|
|
6392
|
+
if (m._useBgQueue) cancelled._useBgQueue = true;
|
|
6393
|
+
if (m._ownerKey !== void 0) cancelled._ownerKey = m._ownerKey;
|
|
6394
|
+
if (m._ts !== void 0) cancelled._ts = m._ts;
|
|
6395
|
+
if (m._localId !== void 0) cancelled._localId = m._localId;
|
|
6396
|
+
return cancelled;
|
|
6397
|
+
}
|
|
6398
|
+
/**
|
|
6399
|
+
* Settle a turn as cancelled wherever it lives, and let the queue move on.
|
|
6400
|
+
*
|
|
6401
|
+
* On the chat on screen: every pending placeholder carrying the id goes (a
|
|
6402
|
+
* history refetch can have re-mapped the running turn into a second one), the
|
|
6403
|
+
* request bubble takes its cancelled form, and the next queued turn is promoted
|
|
6404
|
+
* so the conversation does not sit on an "(In queue)" bubble with nothing
|
|
6405
|
+
* running. For a chat that is NOT on screen (the user moved on while the turn
|
|
6406
|
+
* ran, and it was cancelled from elsewhere) the same edit is made in that chat's
|
|
6407
|
+
* history cache, which is what the next visit renders.
|
|
6408
|
+
*
|
|
6409
|
+
* Idempotent on purpose: a cancel this client made settles the bubbles at once,
|
|
6410
|
+
* and the chain that was awaiting the poll settles again a moment later when the
|
|
6411
|
+
* stop resolves it. The second pass finds nothing to do. Deliberately NOT
|
|
6412
|
+
* _removeStrayPendingAssistants: by then the placeholder on screen is the freshly
|
|
6413
|
+
* promoted turn's, whose user bubble is pending, and the sweep would take it.
|
|
6414
|
+
*/
|
|
6415
|
+
_settleCancelledTurn(itemId, ownerKey) {
|
|
6416
|
+
if (!itemId) return;
|
|
6417
|
+
if (ownerKey && this.getHistoryCacheKey() !== ownerKey) {
|
|
6418
|
+
this._applyCancelToCache(ownerKey, itemId);
|
|
6419
|
+
return;
|
|
6420
|
+
}
|
|
6421
|
+
var changed = false, wasBg = false;
|
|
6422
|
+
for (var i = this.state.messages.length - 1; i >= 0; i--) {
|
|
6423
|
+
var m = this.state.messages[i];
|
|
6424
|
+
if (!m || m._serverItemId !== itemId) continue;
|
|
6425
|
+
if (m.role === "assistant" && m.isPending) {
|
|
6426
|
+
this.state.messages.splice(i, 1);
|
|
6427
|
+
changed = true;
|
|
6428
|
+
continue;
|
|
6429
|
+
}
|
|
6430
|
+
if (m.role === "user" && !m.isCancelled) {
|
|
6431
|
+
if (m.isBackgroundTask) wasBg = true;
|
|
6432
|
+
this.state.messages[i] = this._cancelledCopyOf(m, itemId);
|
|
6433
|
+
changed = true;
|
|
6434
|
+
}
|
|
6435
|
+
}
|
|
6436
|
+
this.cancelledServerIds.delete(itemId);
|
|
6437
|
+
if (!changed) return;
|
|
6438
|
+
if (wasBg) this.promoteNextBgQueuedToRunning();
|
|
6439
|
+
else this.promoteNextQueuedToRunning();
|
|
6440
|
+
this.updateHistoryCache();
|
|
6441
|
+
this.host.notify();
|
|
6442
|
+
}
|
|
6443
|
+
/**
|
|
6444
|
+
* _settleCancelledTurn for a chat that is not on screen: the same edit, made in
|
|
6445
|
+
* its history cache. Matched by id where the cached bubbles carry one; an
|
|
6446
|
+
* off-chat send caches its pair before the ack (see dispatchComposedMessage), so
|
|
6447
|
+
* the fallback is positional, exactly as _applyReplyToCache's is: the trailing
|
|
6448
|
+
* pending placeholder, and the last request bubble still marked in flight.
|
|
6449
|
+
*/
|
|
6450
|
+
_applyCancelToCache(key, itemId) {
|
|
6451
|
+
var existing = key ? this.aiChatHistoryCache[key] : void 0;
|
|
6452
|
+
if (!existing) return;
|
|
6453
|
+
var msgs = existing.messages.slice();
|
|
6454
|
+
var changed = false;
|
|
6455
|
+
var pIdx = -1, uIdx = -1;
|
|
6456
|
+
for (var i = msgs.length - 1; i >= 0; i--) {
|
|
6457
|
+
var m = msgs[i];
|
|
6458
|
+
if (!m || m._serverItemId !== itemId) continue;
|
|
6459
|
+
if (m.role === "assistant" && m.isPending) {
|
|
6460
|
+
msgs.splice(i, 1);
|
|
6461
|
+
changed = true;
|
|
6462
|
+
pIdx = i;
|
|
6463
|
+
} else if (m.role === "user" && !m.isCancelled && uIdx === -1) uIdx = i;
|
|
6464
|
+
}
|
|
6465
|
+
if (pIdx === -1) {
|
|
6466
|
+
for (var p = msgs.length - 1; p >= 0; p--) {
|
|
6467
|
+
var pm = msgs[p];
|
|
6468
|
+
if (!pm || !pm.isPending || pm.role !== "assistant" || pm.isBackgroundTask || pm._serverItemId !== void 0) continue;
|
|
6469
|
+
msgs.splice(p, 1);
|
|
6470
|
+
changed = true;
|
|
6471
|
+
break;
|
|
6472
|
+
}
|
|
6473
|
+
}
|
|
6474
|
+
if (uIdx === -1) {
|
|
6475
|
+
for (var u = msgs.length - 1; u >= 0; u--) {
|
|
6476
|
+
var um = msgs[u];
|
|
6477
|
+
if (!um || um.role !== "user" || um.isBackgroundTask || um._serverItemId !== void 0 || um._stageId) continue;
|
|
6478
|
+
if (!(um.isPendingQueued || um.isPendingInProcess || um.isSendingToServer)) continue;
|
|
6479
|
+
uIdx = u;
|
|
6480
|
+
break;
|
|
6481
|
+
}
|
|
6482
|
+
}
|
|
6483
|
+
if (uIdx !== -1) {
|
|
6484
|
+
msgs[uIdx] = this._cancelledCopyOf(msgs[uIdx], itemId);
|
|
6485
|
+
changed = true;
|
|
6486
|
+
}
|
|
6487
|
+
if (!changed) return;
|
|
6488
|
+
this.aiChatHistoryCache[key] = { messages: msgs, endOfList: existing.endOfList, startKeyHistory: existing.startKeyHistory };
|
|
6489
|
+
}
|
|
6249
6490
|
/**
|
|
6250
6491
|
* Stop indexing a file, from its collapsed row — every pass at once, not just
|
|
6251
6492
|
* the bubble the user happens to see.
|
|
@@ -6312,6 +6553,7 @@ var ChatSession = class {
|
|
|
6312
6553
|
}
|
|
6313
6554
|
}
|
|
6314
6555
|
this._adoptWorkerIndexingPasses(0);
|
|
6556
|
+
this._sweepCancelledIndexing();
|
|
6315
6557
|
var ids = group.cancellableIds || [];
|
|
6316
6558
|
if (!ids.length) {
|
|
6317
6559
|
this.host.notify();
|
|
@@ -6448,7 +6690,7 @@ var ChatSession = class {
|
|
|
6448
6690
|
enqueueTypewrite(idx, fullText, localId, paintedText) {
|
|
6449
6691
|
var self = this;
|
|
6450
6692
|
var target = this.state.messages[idx];
|
|
6451
|
-
if (target && target._ts === void 0) target._ts =
|
|
6693
|
+
if (target && target._ts === void 0) target._ts = this._replyStampFor(target);
|
|
6452
6694
|
if (!this.typewriterQueue) this.typewriterQueue = Promise.resolve();
|
|
6453
6695
|
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
6454
6696
|
return self.typewriteIntoIndex(idx, fullText, localId, paintedText);
|
|
@@ -6594,9 +6836,10 @@ var ChatSession = class {
|
|
|
6594
6836
|
this.state.sending = true;
|
|
6595
6837
|
this.host.scrollToBottomIfSticky(true);
|
|
6596
6838
|
return Promise.resolve(pending).catch(function() {
|
|
6597
|
-
}).then(function() {
|
|
6839
|
+
}).then(function(result) {
|
|
6598
6840
|
if (token !== self.state.gateRefreshToken) return;
|
|
6599
6841
|
self.state.sending = false;
|
|
6842
|
+
if (result && result.cancelled) return;
|
|
6600
6843
|
return Promise.resolve(self.typewriteLatestReply(key)).then(function() {
|
|
6601
6844
|
self.host.scrollToBottomIfSticky(true);
|
|
6602
6845
|
});
|
|
@@ -8225,8 +8468,19 @@ function buildChatDisplayList(messages, opts) {
|
|
|
8225
8468
|
} else if (stopped) {
|
|
8226
8469
|
grp.status = "cancelled";
|
|
8227
8470
|
} else {
|
|
8228
|
-
var
|
|
8229
|
-
|
|
8471
|
+
var retriedIds = {};
|
|
8472
|
+
for (var ri = 0; ri < grp.members.length; ri++) {
|
|
8473
|
+
var rm = grp.members[ri].msg;
|
|
8474
|
+
if (rm.isRetried && rm._serverItemId) retriedIds[rm._serverItemId] = true;
|
|
8475
|
+
}
|
|
8476
|
+
var li = grp.members.length - 1;
|
|
8477
|
+
while (li >= 0 && (grp.members[li].msg.isRetried || grp.members[li].msg._serverItemId && retriedIds[grp.members[li].msg._serverItemId])) li--;
|
|
8478
|
+
if (li < 0) {
|
|
8479
|
+
grp.status = "active";
|
|
8480
|
+
} else {
|
|
8481
|
+
var last = grp.members[li].msg;
|
|
8482
|
+
grp.status = last.isError ? "error" : "done";
|
|
8483
|
+
}
|
|
8230
8484
|
}
|
|
8231
8485
|
var sawFirstPass = false;
|
|
8232
8486
|
for (var pi = 0; pi < grp.members.length; pi++) {
|
|
@@ -8539,6 +8793,6 @@ function clearProjectSettings(service) {
|
|
|
8539
8793
|
else cache.clear();
|
|
8540
8794
|
}
|
|
8541
8795
|
|
|
8542
|
-
export { BG_INDEXING_QUEUE_SUFFIX, BOM, BOM_EXTS, CHAT_GREETING_MAX_LENGTH, CLAUDE_INPUT_CAP_RATIO, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_CONTEXT_WINDOW, DEFAULT_OPENAI_MODEL, DEFAULT_UPLOAD_ACCESS_GROUP, EMPTY_INDEXING_REPLY, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, EXT_CONTENT_TYPES, HISTORY_BUDGET_RATIO, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, HTML_EXTS, HTML_HEAD_WINDOW, IMAGE_PREVIEWS_PER_MESSAGE, INDEXING_COMPLETE_MARKER, INDEXING_MAX_OUTPUT_TOKENS, INLINE_LINK_GLYPH, INLINE_LINK_UNAVAILABLE_GLYPH, INLINE_LINK_UNAVAILABLE_SUFFIX, INPUT_CAP_RATIO, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_CONCURRENT_BG_POLLS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_OUTPUT_BY_MODEL, MAX_OUTPUT_TOKENS, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MINT_CACHE_GENERATION, MIN_INPUT_TOKEN_BUDGET, MIN_PER_REQUEST_INPUT_CAP, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, PRESIGN_SAFETY_MARGIN_MS, PREVIEWABLE_IMAGE_CONTENT_TYPES, PREVIEW_BROWSER_CACHE_SECONDS, PREVIEW_LAYOUT_BOX_SELECTOR, PREVIEW_URL_EXPIRES_SECONDS, PROJECT_SETTINGS_ACCESS_GROUP, PROJECT_SETTINGS_TABLE, PROJECT_SETTINGS_UNIQUE_ID, RENDER_FROM_TOKEN, RTF_EXTS, RUN_RECORD_WORKING_STALE_MS, STREAM_POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, UPLOAD_ACCESS_GROUPS, UPLOAD_ACCESS_HINTS, UPLOAD_ACCESS_LABELS, UPLOAD_ACCESS_OPTIONS, XML_EXTS, __resetSplitHistoryState, accessSettingFrom, adoptLocalAnswerIntoPage, applyEncodingDeclaration, asksUploadAccessFrom, bgIndexingQueueName, buildAiAgentValue, buildBoundedChatMessages, buildChatDisplayList, buildChatGreeting, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildHistoryItemFullId, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, cachedProjectSettings, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, canonicalizePathForm, chatCacheKey, chatEngineConfig, chatGreetingFrom, chatStreamWiring, classifyInlineLink, clearAttachmentParsers, clearImagePreviewCache, clearProjectSettings, composeUserMessage, configureChatEngine, configureProjectSettings, contentTypeForExt, createHistoryFiller, createInlineLinkRegex, createScrollAnchor, createSseParser, csrEnvelopeError, encodePathSegments, encodingClassForExt, ensureHtmlCharset, ensureXmlEncoding, escapeInlineHtml, escapeRtfNonAscii, estimateMessageTokens, estimateTextTokens, extOf, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fetchLiveIndexingKeys, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, formatDuration, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, getInputTokenBudget, getMaxOutputTokens, getModelContextWindow, getProjectContextWindow, getSplitChatHistory, getVisionProfile, groupAttachmentFailures, hasBom, hydrateImagePreviews, indexDoneUniqueId, indexScopeKey, indexingAccessGroup, isAuthExpiredError, isBgIndexingQueue, isCsrStatusEnvelope, isErrorResponseBody, isHttpUrlLike, isImageVisionFile, isIndexingRequestText, isLinkUnavailable, isNonRetryableRequestError, isOfficeFile, isPagedReadFile, isPreviewableImagePath, isProviderApiKeyError, isServerExtractable, isServiceDbAttachmentHref, isWindowedReadFile, linkUnavailableKeyForHref, linkUnavailableKeyForPath, linkUnavailableKeysForPath, listClaudeModels, listOpenAIModels, liveSafePrefix, loadProjectSettings, looksLikeRtf, makeExtractPlaceholder, mapHistoryListToMessages, markImagePreviewStale, mayKeepStreamedAnswer, mintCacheBustStamp, needsBomForExt, normalizeAttachmentPathCandidate, normalizeChatGreeting, normalizeExt, normalizeProjectAccessSetting, normalizeTextContent, normalizeTrailingInlineToken, normalizeUploadAccessGroup, notifyAgentSaveAttachment, parseAiAgentValue, parseAttachmentContent, parseIndexingLabel, parseIndexingRequestText, patchProjectSettings, peekImagePreviewUrl, prepareDownloadText, presignExpiryEpochMs, previewImageContentType, previewLayoutBox, previewMintCacheToken, previewableExtOf, primeProjectSettings, projectAccessSetting, projectAsksUploadAccess, projectChatGreeting, projectSettingsSettled, projectUploadAccessGroup, readExpiredAttachmentHref, readyProjectSettings, registerAttachmentParser, registerModelContextWindows, renderInlineLinkHtml, repairUrlEntities, repairUrlWhitespace, resolveImagePreviewUrl, runIndexUniqueId, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, setProjectContextWindow, setProjectSettings, shouldRescueInFlightMessage, skapiSupportsStreaming, streamRecoveryEnabled, streamRecoveryLabels, streamRecoveryPhase, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, typewriterResumeIndex, uploadAccessGroupFrom, upsertIndexRunRecordSafe, wallClockNow };
|
|
8796
|
+
export { BG_INDEXING_QUEUE_SUFFIX, BOM, BOM_EXTS, CHAT_GREETING_MAX_LENGTH, CLAUDE_INPUT_CAP_RATIO, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_CONTEXT_WINDOW, DEFAULT_OPENAI_MODEL, DEFAULT_UPLOAD_ACCESS_GROUP, EMPTY_INDEXING_REPLY, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, EXT_CONTENT_TYPES, HISTORY_BUDGET_RATIO, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, HTML_EXTS, HTML_HEAD_WINDOW, IMAGE_PREVIEWS_PER_MESSAGE, INDEXING_COMPLETE_MARKER, INDEXING_MAX_OUTPUT_TOKENS, INLINE_LINK_GLYPH, INLINE_LINK_UNAVAILABLE_GLYPH, INLINE_LINK_UNAVAILABLE_SUFFIX, INPUT_CAP_RATIO, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_CONCURRENT_BG_POLLS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_OUTPUT_BY_MODEL, MAX_OUTPUT_TOKENS, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MINT_CACHE_GENERATION, MIN_INPUT_TOKEN_BUDGET, MIN_PER_REQUEST_INPUT_CAP, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, PRESIGN_SAFETY_MARGIN_MS, PREVIEWABLE_IMAGE_CONTENT_TYPES, PREVIEW_BROWSER_CACHE_SECONDS, PREVIEW_LAYOUT_BOX_SELECTOR, PREVIEW_URL_EXPIRES_SECONDS, PROJECT_SETTINGS_ACCESS_GROUP, PROJECT_SETTINGS_TABLE, PROJECT_SETTINGS_UNIQUE_ID, RENDER_FROM_TOKEN, RTF_EXTS, RUN_RECORD_WORKING_STALE_MS, STREAM_POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, UPLOAD_ACCESS_GROUPS, UPLOAD_ACCESS_HINTS, UPLOAD_ACCESS_LABELS, UPLOAD_ACCESS_OPTIONS, XML_EXTS, __resetSplitHistoryState, accessSettingFrom, adoptLocalAnswerIntoPage, applyEncodingDeclaration, asksUploadAccessFrom, bgIndexingQueueName, buildAiAgentValue, buildBoundedChatMessages, buildChatDisplayList, buildChatGreeting, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildHistoryItemFullId, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, cachedProjectSettings, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, canonicalizePathForm, chatCacheKey, chatEngineConfig, chatGreetingFrom, chatStreamWiring, classifyInlineLink, clearAttachmentParsers, clearImagePreviewCache, clearProjectSettings, composeUserMessage, configureChatEngine, configureProjectSettings, contentTypeForExt, createHistoryFiller, createInlineLinkRegex, createScrollAnchor, createSseParser, csrEnvelopeError, encodePathSegments, encodingClassForExt, ensureHtmlCharset, ensureXmlEncoding, escapeInlineHtml, escapeRtfNonAscii, estimateMessageTokens, estimateTextTokens, extOf, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fetchLiveIndexingKeys, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, formatDuration, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, getInputTokenBudget, getMaxOutputTokens, getModelContextWindow, getProjectContextWindow, getSplitChatHistory, getVisionProfile, groupAttachmentFailures, hasBom, hydrateImagePreviews, indexDoneUniqueId, indexScopeKey, indexingAccessGroup, isAuthExpiredError, isBgIndexingQueue, isCsrStatusEnvelope, isErrorResponseBody, isHttpUrlLike, isImageVisionFile, isIndexingRequestText, isLinkUnavailable, isNonRetryableRequestError, isOfficeFile, isPagedReadFile, isPreviewableImagePath, isProviderApiKeyError, isRetriedFailure, isServerExtractable, isServiceDbAttachmentHref, isWindowedReadFile, linkUnavailableKeyForHref, linkUnavailableKeyForPath, linkUnavailableKeysForPath, listClaudeModels, listOpenAIModels, liveSafePrefix, loadProjectSettings, looksLikeRtf, makeExtractPlaceholder, mapHistoryListToMessages, markImagePreviewStale, mayKeepStreamedAnswer, mintCacheBustStamp, needsBomForExt, normalizeAttachmentPathCandidate, normalizeChatGreeting, normalizeExt, normalizeProjectAccessSetting, normalizeTextContent, normalizeTrailingInlineToken, normalizeUploadAccessGroup, notifyAgentSaveAttachment, parseAiAgentValue, parseAttachmentContent, parseIndexingLabel, parseIndexingRequestText, patchProjectSettings, peekImagePreviewUrl, prepareDownloadText, presignExpiryEpochMs, previewImageContentType, previewLayoutBox, previewMintCacheToken, previewableExtOf, primeProjectSettings, projectAccessSetting, projectAsksUploadAccess, projectChatGreeting, projectSettingsSettled, projectUploadAccessGroup, readExpiredAttachmentHref, readyProjectSettings, registerAttachmentParser, registerModelContextWindows, renderInlineLinkHtml, repairUrlEntities, repairUrlWhitespace, resolveImagePreviewUrl, runIndexUniqueId, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, setProjectContextWindow, setProjectSettings, shouldRescueInFlightMessage, skapiSupportsStreaming, streamRecoveryEnabled, streamRecoveryLabels, streamRecoveryPhase, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, typewriterResumeIndex, uploadAccessGroupFrom, upsertIndexRunRecordSafe, wallClockNow };
|
|
8543
8797
|
//# sourceMappingURL=engine.mjs.map
|
|
8544
8798
|
//# sourceMappingURL=engine.mjs.map
|