bunnyquery 1.12.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/bunnyquery.js +87 -4
- package/dist/engine.cjs +34 -3
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +14 -0
- package/dist/engine.d.ts +14 -0
- package/dist/engine.mjs +34 -3
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/history.ts +10 -1
- package/src/engine/prompts/chat_system_prompt.ts +23 -0
- package/src/engine/session.ts +38 -3
package/dist/engine.d.mts
CHANGED
|
@@ -3859,6 +3859,20 @@ declare class ChatSession {
|
|
|
3859
3859
|
private _countBgPolls;
|
|
3860
3860
|
/** Live streams by server item id. One per in-flight streamed turn. */
|
|
3861
3861
|
private liveStreams;
|
|
3862
|
+
/** When skapi recorded a turn's response as having finished arriving, by server
|
|
3863
|
+
* item id. Read off the terminal envelope of a STREAMED turn (the only settle
|
|
3864
|
+
* that carries it, see _settleLiveStream) and used as the answer bubble's time
|
|
3865
|
+
* in place of this browser's clock. Entries are never removed: a settle reaches
|
|
3866
|
+
* here twice (the poll's onResponse, then the promise), and the value for an id
|
|
3867
|
+
* cannot change. */
|
|
3868
|
+
private _respondedAt;
|
|
3869
|
+
private _noteRespondedAt;
|
|
3870
|
+
/** The time a live answer bubble is stamped with: when skapi received the
|
|
3871
|
+
* response, if the settle told us, else now. "Now" is what every bubble got
|
|
3872
|
+
* before, and it is still right for a buffered turn, whose settle hands back the
|
|
3873
|
+
* destination's body and nothing of skapi's; a history load then replaces it
|
|
3874
|
+
* with the server's value. */
|
|
3875
|
+
private _replyStampFor;
|
|
3862
3876
|
/**
|
|
3863
3877
|
* Open (or re-open) the live stream for `itemId`, or null when this poll must
|
|
3864
3878
|
* not carry one.
|
package/dist/engine.d.ts
CHANGED
|
@@ -3859,6 +3859,20 @@ declare class ChatSession {
|
|
|
3859
3859
|
private _countBgPolls;
|
|
3860
3860
|
/** Live streams by server item id. One per in-flight streamed turn. */
|
|
3861
3861
|
private liveStreams;
|
|
3862
|
+
/** When skapi recorded a turn's response as having finished arriving, by server
|
|
3863
|
+
* item id. Read off the terminal envelope of a STREAMED turn (the only settle
|
|
3864
|
+
* that carries it, see _settleLiveStream) and used as the answer bubble's time
|
|
3865
|
+
* in place of this browser's clock. Entries are never removed: a settle reaches
|
|
3866
|
+
* here twice (the poll's onResponse, then the promise), and the value for an id
|
|
3867
|
+
* cannot change. */
|
|
3868
|
+
private _respondedAt;
|
|
3869
|
+
private _noteRespondedAt;
|
|
3870
|
+
/** The time a live answer bubble is stamped with: when skapi received the
|
|
3871
|
+
* response, if the settle told us, else now. "Now" is what every bubble got
|
|
3872
|
+
* before, and it is still right for a buffered turn, whose settle hands back the
|
|
3873
|
+
* destination's body and nothing of skapi's; a history load then replaces it
|
|
3874
|
+
* with the server's value. */
|
|
3875
|
+
private _replyStampFor;
|
|
3862
3876
|
/**
|
|
3863
3877
|
* Open (or re-open) the live stream for `itemId`, or null when this poll must
|
|
3864
3878
|
* not carry one.
|
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.
|
|
@@ -3253,8 +3258,9 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
3253
3258
|
var createdTs = Number(item && item.created);
|
|
3254
3259
|
var updatedTs = Number(item && item.updated);
|
|
3255
3260
|
var executedTs = Number(item && item.executed);
|
|
3261
|
+
var respondedTs = Number(item && item.responded);
|
|
3256
3262
|
var userTs = isFinite(createdTs) && createdTs > 0 ? createdTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : void 0;
|
|
3257
|
-
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;
|
|
3258
3264
|
if (userText) {
|
|
3259
3265
|
var displayContent;
|
|
3260
3266
|
var indexFile = void 0;
|
|
@@ -3881,6 +3887,13 @@ var ChatSession = class {
|
|
|
3881
3887
|
// the request budget MAX_CONCURRENT_BG_POLLS exists to protect.
|
|
3882
3888
|
/** Live streams by server item id. One per in-flight streamed turn. */
|
|
3883
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 = {};
|
|
3884
3897
|
// ─── compact-stub hydration ─────────────────────────────────────────────
|
|
3885
3898
|
// Split-fetch bg pages arrive as label stubs (no bodies). When the user
|
|
3886
3899
|
// expands a row, the real reply text is fetched per item (csr-poll point
|
|
@@ -4406,6 +4419,23 @@ var ChatSession = class {
|
|
|
4406
4419
|
});
|
|
4407
4420
|
return n;
|
|
4408
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
|
+
}
|
|
4409
4439
|
/**
|
|
4410
4440
|
* Open (or re-open) the live stream for `itemId`, or null when this poll must
|
|
4411
4441
|
* not carry one.
|
|
@@ -4595,6 +4625,7 @@ var ChatSession = class {
|
|
|
4595
4625
|
_settleLiveStream(st, response) {
|
|
4596
4626
|
this._closeLiveStream(st, true);
|
|
4597
4627
|
if (!isCsrStatusEnvelope(response)) return response;
|
|
4628
|
+
this._noteRespondedAt(st.id, response);
|
|
4598
4629
|
if (response.status !== "resolved") return response;
|
|
4599
4630
|
if (!this._mayFinalize(st)) this._rec().incomplete[st.id] = true;
|
|
4600
4631
|
if (st.finalBody == null) return response;
|
|
@@ -6036,7 +6067,7 @@ var ChatSession = class {
|
|
|
6036
6067
|
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
6037
6068
|
}
|
|
6038
6069
|
insertAtTarget(msg, targetIdx) {
|
|
6039
|
-
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);
|
|
6040
6071
|
var tgt = targetIdx >= 0 ? this.state.messages[targetIdx] : void 0;
|
|
6041
6072
|
var replaceable = !!tgt && !!tgt.isPending && !tgt.isBackgroundTask && this._isOwnPlaceholderOf(targetIdx, this._owningUserIndex(targetIdx));
|
|
6042
6073
|
if (replaceable) this.state.messages[targetIdx] = msg;
|
|
@@ -6659,7 +6690,7 @@ var ChatSession = class {
|
|
|
6659
6690
|
enqueueTypewrite(idx, fullText, localId, paintedText) {
|
|
6660
6691
|
var self = this;
|
|
6661
6692
|
var target = this.state.messages[idx];
|
|
6662
|
-
if (target && target._ts === void 0) target._ts =
|
|
6693
|
+
if (target && target._ts === void 0) target._ts = this._replyStampFor(target);
|
|
6663
6694
|
if (!this.typewriterQueue) this.typewriterQueue = Promise.resolve();
|
|
6664
6695
|
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
6665
6696
|
return self.typewriteIntoIndex(idx, fullText, localId, paintedText);
|