bunnyquery 1.7.0 → 1.8.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.css +12 -0
- package/bunnyquery.js +65 -10
- package/dist/engine.cjs +63 -6
- package/dist/engine.cjs.map +1 -1
- package/dist/engine.d.mts +80 -11
- package/dist/engine.d.ts +80 -11
- package/dist/engine.mjs +61 -7
- package/dist/engine.mjs.map +1 -1
- package/package.json +1 -1
- package/src/engine/history.ts +10 -0
- package/src/engine/host.ts +6 -0
- package/src/engine/index.ts +1 -0
- package/src/engine/indexing_groups.ts +50 -11
- package/src/engine/links.ts +35 -1
- package/src/engine/prompts/chat_system_prompt.ts +3 -0
- package/src/engine/session.ts +18 -3
- package/src/engine/time.ts +34 -0
- package/styles/chat.css +12 -0
package/bunnyquery.css
CHANGED
|
@@ -1072,6 +1072,18 @@
|
|
|
1072
1072
|
font-style: italic;
|
|
1073
1073
|
clear: both;
|
|
1074
1074
|
}
|
|
1075
|
+
/* Timestamp under a settled bubble (created for a user turn, response time for an
|
|
1076
|
+
assistant turn). Muted and small so it never competes with the message. */
|
|
1077
|
+
.bq-msg-time {
|
|
1078
|
+
display: block;
|
|
1079
|
+
margin-top: 0.25rem;
|
|
1080
|
+
font-size: 0.62rem;
|
|
1081
|
+
line-height: 1;
|
|
1082
|
+
color: var(--bq-muted);
|
|
1083
|
+
opacity: 0.7;
|
|
1084
|
+
clear: both;
|
|
1085
|
+
}
|
|
1086
|
+
.bq-message.is-user .bq-msg-time { text-align: right; }
|
|
1075
1087
|
.bq-cancel-queue-btn {
|
|
1076
1088
|
float: right;
|
|
1077
1089
|
margin: -0.25rem -0.25rem 0.2rem 0.4rem;
|
package/bunnyquery.js
CHANGED
|
@@ -280,6 +280,9 @@ Extracted content of attached office files (read inline below; do NOT fetch thei
|
|
|
280
280
|
You are a dedicated assistant for the project ID: "${formattedServiceId}".
|
|
281
281
|
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.
|
|
282
282
|
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.
|
|
283
|
+
Complete answers over stored data: The database holds one record per spreadsheet row, and each uploaded file becomes many records that usually share a table. So a question about the data almost always spans many records across several files. For any request that counts, sums, totals, lists every match, compares across records, finds which one, or asks whether something is present or ABSENT (for example "how many", "total spent", "which card", "is there any", "\uC5C6\uC5B4?", "\uD558\uB098\uB3C4 \uC5C6\uB098?"), you MUST read the COMPLETE matching set before answering. Query with fetch_all set to true, or page through getToolResponsePage until pagination.complete is true, across EVERY relevant table and EVERY relevant file. A single default query returns only the first page (about 50 records). That is a SAMPLE. Never treat it as the whole dataset.
|
|
284
|
+
Never assert absence from a partial read. Do not say "there is no X", "none", "not found", or "\uC544\uB2C8\uC694, \uC5C6\uC2B5\uB2C8\uB2E4" until a complete scan has come back empty. If you have not finished scanning every relevant table and file, keep querying instead of guessing. A confident "no" that later turns out wrong is worse than telling the user you are still checking.
|
|
285
|
+
Embedded values: a search term is often stored inside a larger string. A merchant "GODADDY" appears as "DNH*GODADDY#4070277042", and a card as "4140****2941". Server-side index and tag filters match only exact values or leading prefixes, not substrings, so filtering on such a field silently drops rows. When the value you are looking for may be embedded, do not trust a narrow filter to be complete. Fetch the full set with fetch_all and match the substring yourself.
|
|
283
286
|
File attachments: When a user message contains an "Attached files:" section with markdown links, those links point to short-lived signed URLs in this project's db storage and will expire.
|
|
284
287
|
- Image files (.jpg, .jpeg, .png, .gif, .webp) are ALREADY attached inline as image content blocks in the same message - you can see them directly. Do NOT call web_fetch on image URLs; that will fail or return garbage. Just look at the image block and answer.
|
|
285
288
|
- Most attached files (office documents like .docx/.xlsx/.pptx/.hwp/.hwpx/.ods, and text/data/code files like .csv/.tsv/.json/.xml/.txt/.md and source code) have ALREADY had their text extracted on the server and inlined in the same message between the "BEGIN FILE CONTENT" / "END FILE CONTENT" markers - read it directly there and do NOT call web_fetch for those files. A "[skapi: ...]" note in that block means the file could not be extracted.
|
|
@@ -614,6 +617,15 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
614
617
|
if (/^https?:\/\/[^/\s]+\/download\/[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)?$/i.test(stripped)) return stripped;
|
|
615
618
|
return href.trim().replace(/\s/g, "%20");
|
|
616
619
|
}
|
|
620
|
+
function repairUrlEntities(href) {
|
|
621
|
+
if (!href || href.indexOf("&") === -1) return href;
|
|
622
|
+
var out = href, prev = "";
|
|
623
|
+
while (out !== prev) {
|
|
624
|
+
prev = out;
|
|
625
|
+
out = out.replace(/&/gi, "&").replace(/�*38;/g, "&").replace(/�*26;/gi, "&");
|
|
626
|
+
}
|
|
627
|
+
return out;
|
|
628
|
+
}
|
|
617
629
|
function normalizeTrailingInlineToken(value) {
|
|
618
630
|
if (!value) return value;
|
|
619
631
|
var out = value.replace(/[.,;:!?]+$/, "");
|
|
@@ -661,8 +673,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
661
673
|
var tail = full.slice(("src::" + rawPath).length);
|
|
662
674
|
var srcIsUrl = isHttpUrlLike(rawPath);
|
|
663
675
|
if (srcIsUrl && !isDbHost(rawPath) && !readExpiredAttachmentHref(rawPath)) {
|
|
676
|
+
var srcUrl = repairUrlEntities(rawPath);
|
|
664
677
|
return {
|
|
665
|
-
part: { type: "link", label: truncateLabelForDisplay(
|
|
678
|
+
part: { type: "link", label: truncateLabelForDisplay(srcUrl), fullLabel: srcUrl, href: srcUrl, expired: false },
|
|
666
679
|
tail
|
|
667
680
|
};
|
|
668
681
|
}
|
|
@@ -696,6 +709,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
696
709
|
}
|
|
697
710
|
var originalHref = g3 || g6 || "";
|
|
698
711
|
if (!originalHref) return null;
|
|
712
|
+
originalHref = repairUrlEntities(originalHref);
|
|
699
713
|
var urlTail;
|
|
700
714
|
if (!g3 && g6) {
|
|
701
715
|
var trimmedUrl = normalizeTrailingInlineToken(originalHref);
|
|
@@ -795,6 +809,26 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
795
809
|
};
|
|
796
810
|
}
|
|
797
811
|
|
|
812
|
+
// src/engine/time.ts
|
|
813
|
+
function wallClockNow() {
|
|
814
|
+
return Date.now();
|
|
815
|
+
}
|
|
816
|
+
function formatChatTimestamp(ms) {
|
|
817
|
+
if (typeof ms !== "number" || !isFinite(ms) || ms <= 0) return "";
|
|
818
|
+
try {
|
|
819
|
+
return new Date(ms).toLocaleString(void 0, {
|
|
820
|
+
year: "numeric",
|
|
821
|
+
month: "short",
|
|
822
|
+
day: "numeric",
|
|
823
|
+
hour: "numeric",
|
|
824
|
+
minute: "2-digit",
|
|
825
|
+
second: "2-digit"
|
|
826
|
+
});
|
|
827
|
+
} catch (e) {
|
|
828
|
+
return "";
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
|
|
798
832
|
// src/engine/requests.ts
|
|
799
833
|
var ANTHROPIC_MESSAGES_API_URL = "https://api.anthropic.com/v1/messages";
|
|
800
834
|
var ANTHROPIC_VERSION = "2023-06-01";
|
|
@@ -1329,6 +1363,10 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1329
1363
|
var assistantText = isPending ? "" : (extractAssistantText(response) || "").trim() || "";
|
|
1330
1364
|
var isErrorResponse = !isPending && (isFailed || isErrorResponseBody(response));
|
|
1331
1365
|
var serverItemId = item && typeof item.id === "string" && item.id ? item.id : void 0;
|
|
1366
|
+
var createdTs = Number(item && item.created);
|
|
1367
|
+
var updatedTs = Number(item && item.updated);
|
|
1368
|
+
var userTs = isFinite(createdTs) && createdTs > 0 ? createdTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : void 0;
|
|
1369
|
+
var replyTs = isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
1332
1370
|
if (userText) {
|
|
1333
1371
|
var displayContent;
|
|
1334
1372
|
var indexFile = void 0;
|
|
@@ -1368,6 +1406,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1368
1406
|
if (indexFile) userMsg._indexFile = indexFile;
|
|
1369
1407
|
if (item._isOnBgQueue) userMsg._useBgQueue = true;
|
|
1370
1408
|
if (serverItemId !== void 0) userMsg._serverItemId = serverItemId;
|
|
1409
|
+
if (userTs !== void 0) userMsg._ts = userTs;
|
|
1371
1410
|
mapped.push(userMsg);
|
|
1372
1411
|
}
|
|
1373
1412
|
if (isCancelledItem) ; else if (isInProcess) {
|
|
@@ -1382,11 +1421,13 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1382
1421
|
var em = { role: "assistant", content: getErrorMessage(response), isError: true };
|
|
1383
1422
|
if (item._isBgTask) em.isBackgroundTask = true;
|
|
1384
1423
|
if (serverItemId !== void 0) em._serverItemId = serverItemId;
|
|
1424
|
+
if (replyTs !== void 0) em._ts = replyTs;
|
|
1385
1425
|
mapped.push(em);
|
|
1386
1426
|
} else if (assistantText) {
|
|
1387
1427
|
var okm = { role: "assistant", content: sanitizeAttachmentLinksForHistory(assistantText, opts.serviceId, true) };
|
|
1388
1428
|
if (item._isBgTask) okm.isBackgroundTask = true;
|
|
1389
1429
|
if (serverItemId !== void 0) okm._serverItemId = serverItemId;
|
|
1430
|
+
if (replyTs !== void 0) okm._ts = replyTs;
|
|
1390
1431
|
mapped.push(okm);
|
|
1391
1432
|
}
|
|
1392
1433
|
});
|
|
@@ -1799,7 +1840,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1799
1840
|
var offExisting = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1800
1841
|
this.aiChatHistoryCache[key] = {
|
|
1801
1842
|
messages: offExisting.messages.concat([
|
|
1802
|
-
{ role: "user", content: composed, _ownerKey: key },
|
|
1843
|
+
{ role: "user", content: composed, _ownerKey: key, _ts: wallClockNow() },
|
|
1803
1844
|
{ role: "assistant", content: "", isPending: true, isPendingInProcess: true, _ownerKey: key }
|
|
1804
1845
|
]),
|
|
1805
1846
|
endOfList: offExisting.endOfList,
|
|
@@ -1831,7 +1872,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1831
1872
|
serviceId: id.serviceId,
|
|
1832
1873
|
history: resolvedHistory.concat([{ role: "user", content: llmComposed }])
|
|
1833
1874
|
});
|
|
1834
|
-
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true };
|
|
1875
|
+
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true, _ts: wallClockNow() };
|
|
1835
1876
|
if (key) queuedBubble._ownerKey = key;
|
|
1836
1877
|
if (useBgQueue) queuedBubble._useBgQueue = true;
|
|
1837
1878
|
this.state.messages.push(queuedBubble);
|
|
@@ -1866,7 +1907,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1866
1907
|
});
|
|
1867
1908
|
return;
|
|
1868
1909
|
}
|
|
1869
|
-
this.state.messages.push({ role: "user", content: composed, ...key ? { _ownerKey: key } : {} });
|
|
1910
|
+
this.state.messages.push({ role: "user", content: composed, _ts: wallClockNow(), ...key ? { _ownerKey: key } : {} });
|
|
1870
1911
|
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} });
|
|
1871
1912
|
this.host.notify();
|
|
1872
1913
|
this.updateHistoryCache();
|
|
@@ -1923,6 +1964,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1923
1964
|
var existing = this.state.messages[nextIdx];
|
|
1924
1965
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true, isBackgroundTask: true };
|
|
1925
1966
|
if (existing._indexFile) promoted._indexFile = existing._indexFile;
|
|
1967
|
+
if (existing._ts !== void 0) promoted._ts = existing._ts;
|
|
1926
1968
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1927
1969
|
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1928
1970
|
this.state.messages[nextIdx] = promoted;
|
|
@@ -1944,6 +1986,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1944
1986
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true };
|
|
1945
1987
|
if (existing.isBackgroundTask) promoted.isBackgroundTask = true;
|
|
1946
1988
|
if (existing._indexFile) promoted._indexFile = existing._indexFile;
|
|
1989
|
+
if (existing._ts !== void 0) promoted._ts = existing._ts;
|
|
1947
1990
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1948
1991
|
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1949
1992
|
if (existing.isSendingToServer) promoted.isSendingToServer = true;
|
|
@@ -1993,6 +2036,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
1993
2036
|
var repl = { role: "user", content: exist.content };
|
|
1994
2037
|
if (exist._serverItemId !== void 0) repl._serverItemId = exist._serverItemId;
|
|
1995
2038
|
if (exist._ownerKey !== void 0) repl._ownerKey = exist._ownerKey;
|
|
2039
|
+
if (exist._ts !== void 0) repl._ts = exist._ts;
|
|
1996
2040
|
this.state.messages[userIdx] = repl;
|
|
1997
2041
|
}
|
|
1998
2042
|
var thinkingIdx = userIdx >= 0 ? this.state.messages.findIndex(function(m, i) {
|
|
@@ -2001,6 +2045,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2001
2045
|
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
2002
2046
|
}
|
|
2003
2047
|
insertAtTarget(msg, targetIdx) {
|
|
2048
|
+
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts = wallClockNow();
|
|
2004
2049
|
if (targetIdx >= 0 && this.state.messages[targetIdx] && this.state.messages[targetIdx].isPending) this.state.messages[targetIdx] = msg;
|
|
2005
2050
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
2006
2051
|
else this.state.messages.push(msg);
|
|
@@ -2337,6 +2382,8 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2337
2382
|
}
|
|
2338
2383
|
enqueueTypewrite(idx, fullText, localId) {
|
|
2339
2384
|
var self = this;
|
|
2385
|
+
var target = this.state.messages[idx];
|
|
2386
|
+
if (target && target._ts === void 0) target._ts = wallClockNow();
|
|
2340
2387
|
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
2341
2388
|
return self.typewriteIntoIndex(idx, fullText, localId);
|
|
2342
2389
|
});
|
|
@@ -2406,6 +2453,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2406
2453
|
var u = this.state.messages[uIdx];
|
|
2407
2454
|
var cleaned = { role: "user", content: u.content, _serverItemId: itemId };
|
|
2408
2455
|
if (u.isBackgroundTask) cleaned.isBackgroundTask = true;
|
|
2456
|
+
if (u._ts !== void 0) cleaned._ts = u._ts;
|
|
2409
2457
|
if (u._indexFile) cleaned._indexFile = u._indexFile;
|
|
2410
2458
|
this.state.messages[uIdx] = cleaned;
|
|
2411
2459
|
}
|
|
@@ -2478,6 +2526,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
2478
2526
|
var ex = this.state.messages[userIdx];
|
|
2479
2527
|
var settledUser = { role: "user", content: ex.content, _serverItemId: itemId };
|
|
2480
2528
|
if (ex.isBackgroundTask) settledUser.isBackgroundTask = true;
|
|
2529
|
+
if (ex._ts !== void 0) settledUser._ts = ex._ts;
|
|
2481
2530
|
if (ex._indexFile) settledUser._indexFile = ex._indexFile;
|
|
2482
2531
|
if (ex._useBgQueue) settledUser._useBgQueue = true;
|
|
2483
2532
|
this.state.messages[userIdx] = settledUser;
|
|
@@ -3254,7 +3303,10 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3254
3303
|
cancellableIds: [],
|
|
3255
3304
|
cancelling: false,
|
|
3256
3305
|
mayHaveOlder: false,
|
|
3257
|
-
|
|
3306
|
+
// The run's first loaded pass, and never re-stamped: see the file
|
|
3307
|
+
// docstring. `anchorId` is filled in once every member is known.
|
|
3308
|
+
anchorIndex: i,
|
|
3309
|
+
anchorId: ""
|
|
3258
3310
|
};
|
|
3259
3311
|
order.push(runId);
|
|
3260
3312
|
}
|
|
@@ -3268,7 +3320,6 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3268
3320
|
g.passCount++;
|
|
3269
3321
|
}
|
|
3270
3322
|
g.members.push({ msg, index: i });
|
|
3271
|
-
g.anchorIndex = i;
|
|
3272
3323
|
runOfIndex[i] = runId;
|
|
3273
3324
|
if (msg._serverItemId) runByItemId[msg._serverItemId] = runId;
|
|
3274
3325
|
if (ref && ref.name) keyByName[ref.name] = g.key;
|
|
@@ -3330,6 +3381,9 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3330
3381
|
}
|
|
3331
3382
|
}
|
|
3332
3383
|
grp.mayHaveOlder = !sawFirstPass && hasMoreHistory;
|
|
3384
|
+
var anchor = grp.members[0];
|
|
3385
|
+
grp.anchorIndex = anchor.index;
|
|
3386
|
+
grp.anchorId = anchor.msg._serverItemId || anchor.msg._localId || "";
|
|
3333
3387
|
}
|
|
3334
3388
|
var out = [];
|
|
3335
3389
|
for (var j = 0; j < list.length; j++) {
|
|
@@ -3347,7 +3401,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
3347
3401
|
(function() {
|
|
3348
3402
|
var MCP_PROD = "https://mcp.broadwayinc.computer";
|
|
3349
3403
|
var MCP_DEV = "https://mcp-dev.broadwayinc.computer";
|
|
3350
|
-
var BQ_VERSION = "1.
|
|
3404
|
+
var BQ_VERSION = "1.8.0" ;
|
|
3351
3405
|
var ATTACHMENT_URL_EXPIRES_SECONDS = 600;
|
|
3352
3406
|
var GOOGLE_AUTH_URL = "https://accounts.google.com/o/oauth2/v2/auth";
|
|
3353
3407
|
var GOOGLE_TOKEN_URL = "https://oauth2.googleapis.com/token";
|
|
@@ -6170,12 +6224,14 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6170
6224
|
});
|
|
6171
6225
|
bubble.appendChild(cancelBtn);
|
|
6172
6226
|
}
|
|
6173
|
-
var md = h("div", { class: "bq-md", html: parseMsgPartsHtml(msg.content) });
|
|
6227
|
+
var md = h("div", { class: "bq-md", translate: "no", html: parseMsgPartsHtml(msg.content) });
|
|
6174
6228
|
md.addEventListener("click", onBubbleLinkClick);
|
|
6175
6229
|
bubble.appendChild(md);
|
|
6176
6230
|
if (msg.isPendingQueued) bubble.appendChild(h("span", { class: "bq-pending-note", text: "(In queue)" }));
|
|
6177
6231
|
if (msg.isCancelled) bubble.appendChild(h("span", { class: "bq-cancel-error", text: "(cancelled)" }));
|
|
6178
6232
|
if (msg._cancelError) bubble.appendChild(h("span", { class: "bq-cancel-error", text: msg._cancelError }));
|
|
6233
|
+
var ts = formatChatTimestamp(msg._ts);
|
|
6234
|
+
if (ts) bubble.appendChild(h("time", { class: "bq-msg-time", text: ts }));
|
|
6179
6235
|
}
|
|
6180
6236
|
return h("div", { class: cls.join(" "), dataset: { msgIndex: String(idx) } }, bubble);
|
|
6181
6237
|
}
|
|
@@ -6296,8 +6352,7 @@ Index the REMAINING windows - one record per row/item, looking at any page image
|
|
|
6296
6352
|
return id ? "s" + id + ":" + msg.role : "i" + index;
|
|
6297
6353
|
}
|
|
6298
6354
|
function indexGroupAnchorId(group) {
|
|
6299
|
-
|
|
6300
|
-
return last && last.msg && last.msg._serverItemId || "";
|
|
6355
|
+
return group.anchorId || "";
|
|
6301
6356
|
}
|
|
6302
6357
|
function captureScrollAnchor() {
|
|
6303
6358
|
var box = CS.messagesBox;
|
package/dist/engine.cjs
CHANGED
|
@@ -286,6 +286,9 @@ function buildChatSystemPrompt(params) {
|
|
|
286
286
|
You are a dedicated assistant for the project ID: "${formattedServiceId}".
|
|
287
287
|
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.
|
|
288
288
|
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.
|
|
289
|
+
Complete answers over stored data: The database holds one record per spreadsheet row, and each uploaded file becomes many records that usually share a table. So a question about the data almost always spans many records across several files. For any request that counts, sums, totals, lists every match, compares across records, finds which one, or asks whether something is present or ABSENT (for example "how many", "total spent", "which card", "is there any", "\uC5C6\uC5B4?", "\uD558\uB098\uB3C4 \uC5C6\uB098?"), you MUST read the COMPLETE matching set before answering. Query with fetch_all set to true, or page through getToolResponsePage until pagination.complete is true, across EVERY relevant table and EVERY relevant file. A single default query returns only the first page (about 50 records). That is a SAMPLE. Never treat it as the whole dataset.
|
|
290
|
+
Never assert absence from a partial read. Do not say "there is no X", "none", "not found", or "\uC544\uB2C8\uC694, \uC5C6\uC2B5\uB2C8\uB2E4" until a complete scan has come back empty. If you have not finished scanning every relevant table and file, keep querying instead of guessing. A confident "no" that later turns out wrong is worse than telling the user you are still checking.
|
|
291
|
+
Embedded values: a search term is often stored inside a larger string. A merchant "GODADDY" appears as "DNH*GODADDY#4070277042", and a card as "4140****2941". Server-side index and tag filters match only exact values or leading prefixes, not substrings, so filtering on such a field silently drops rows. When the value you are looking for may be embedded, do not trust a narrow filter to be complete. Fetch the full set with fetch_all and match the substring yourself.
|
|
289
292
|
File attachments: When a user message contains an "Attached files:" section with markdown links, those links point to short-lived signed URLs in this project's db storage and will expire.
|
|
290
293
|
- Image files (.jpg, .jpeg, .png, .gif, .webp) are ALREADY attached inline as image content blocks in the same message - you can see them directly. Do NOT call web_fetch on image URLs; that will fail or return garbage. Just look at the image block and answer.
|
|
291
294
|
- Most attached files (office documents like .docx/.xlsx/.pptx/.hwp/.hwpx/.ods, and text/data/code files like .csv/.tsv/.json/.xml/.txt/.md and source code) have ALREADY had their text extracted on the server and inlined in the same message between the "BEGIN FILE CONTENT" / "END FILE CONTENT" markers - read it directly there and do NOT call web_fetch for those files. A "[skapi: ...]" note in that block means the file could not be extracted.
|
|
@@ -620,6 +623,15 @@ function repairUrlWhitespace(href) {
|
|
|
620
623
|
if (/^https?:\/\/[^/\s]+\/download\/[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)?$/i.test(stripped)) return stripped;
|
|
621
624
|
return href.trim().replace(/\s/g, "%20");
|
|
622
625
|
}
|
|
626
|
+
function repairUrlEntities(href) {
|
|
627
|
+
if (!href || href.indexOf("&") === -1) return href;
|
|
628
|
+
var out = href, prev = "";
|
|
629
|
+
while (out !== prev) {
|
|
630
|
+
prev = out;
|
|
631
|
+
out = out.replace(/&/gi, "&").replace(/�*38;/g, "&").replace(/�*26;/gi, "&");
|
|
632
|
+
}
|
|
633
|
+
return out;
|
|
634
|
+
}
|
|
623
635
|
function normalizeTrailingInlineToken(value) {
|
|
624
636
|
if (!value) return value;
|
|
625
637
|
var out = value.replace(/[.,;:!?]+$/, "");
|
|
@@ -667,8 +679,9 @@ function classifyInlineLink(full, groups, ctx) {
|
|
|
667
679
|
var tail = full.slice(("src::" + rawPath).length);
|
|
668
680
|
var srcIsUrl = isHttpUrlLike(rawPath);
|
|
669
681
|
if (srcIsUrl && !isDbHost(rawPath) && !readExpiredAttachmentHref(rawPath)) {
|
|
682
|
+
var srcUrl = repairUrlEntities(rawPath);
|
|
670
683
|
return {
|
|
671
|
-
part: { type: "link", label: truncateLabelForDisplay(
|
|
684
|
+
part: { type: "link", label: truncateLabelForDisplay(srcUrl), fullLabel: srcUrl, href: srcUrl, expired: false },
|
|
672
685
|
tail
|
|
673
686
|
};
|
|
674
687
|
}
|
|
@@ -702,6 +715,7 @@ function classifyInlineLink(full, groups, ctx) {
|
|
|
702
715
|
}
|
|
703
716
|
var originalHref = g3 || g6 || "";
|
|
704
717
|
if (!originalHref) return null;
|
|
718
|
+
originalHref = repairUrlEntities(originalHref);
|
|
705
719
|
var urlTail;
|
|
706
720
|
if (!g3 && g6) {
|
|
707
721
|
var trimmedUrl = normalizeTrailingInlineToken(originalHref);
|
|
@@ -802,6 +816,26 @@ function buildBoundedChatMessages(options) {
|
|
|
802
816
|
};
|
|
803
817
|
}
|
|
804
818
|
|
|
819
|
+
// src/engine/time.ts
|
|
820
|
+
function wallClockNow() {
|
|
821
|
+
return Date.now();
|
|
822
|
+
}
|
|
823
|
+
function formatChatTimestamp(ms) {
|
|
824
|
+
if (typeof ms !== "number" || !isFinite(ms) || ms <= 0) return "";
|
|
825
|
+
try {
|
|
826
|
+
return new Date(ms).toLocaleString(void 0, {
|
|
827
|
+
year: "numeric",
|
|
828
|
+
month: "short",
|
|
829
|
+
day: "numeric",
|
|
830
|
+
hour: "numeric",
|
|
831
|
+
minute: "2-digit",
|
|
832
|
+
second: "2-digit"
|
|
833
|
+
});
|
|
834
|
+
} catch (e) {
|
|
835
|
+
return "";
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
|
|
805
839
|
// src/engine/requests.ts
|
|
806
840
|
var ANTHROPIC_MESSAGES_API_URL = "https://api.anthropic.com/v1/messages";
|
|
807
841
|
var ANTHROPIC_MODELS_API_URL = "https://api.anthropic.com/v1/models";
|
|
@@ -1363,6 +1397,10 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
1363
1397
|
var assistantText = isPending ? "" : (extractAssistantText(response) || "").trim() || "";
|
|
1364
1398
|
var isErrorResponse = !isPending && (isFailed || isErrorResponseBody(response));
|
|
1365
1399
|
var serverItemId = item && typeof item.id === "string" && item.id ? item.id : void 0;
|
|
1400
|
+
var createdTs = Number(item && item.created);
|
|
1401
|
+
var updatedTs = Number(item && item.updated);
|
|
1402
|
+
var userTs = isFinite(createdTs) && createdTs > 0 ? createdTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : void 0;
|
|
1403
|
+
var replyTs = isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
1366
1404
|
if (userText) {
|
|
1367
1405
|
var displayContent;
|
|
1368
1406
|
var indexFile = void 0;
|
|
@@ -1402,6 +1440,7 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
1402
1440
|
if (indexFile) userMsg._indexFile = indexFile;
|
|
1403
1441
|
if (item._isOnBgQueue) userMsg._useBgQueue = true;
|
|
1404
1442
|
if (serverItemId !== void 0) userMsg._serverItemId = serverItemId;
|
|
1443
|
+
if (userTs !== void 0) userMsg._ts = userTs;
|
|
1405
1444
|
mapped.push(userMsg);
|
|
1406
1445
|
}
|
|
1407
1446
|
if (isCancelledItem) ; else if (isInProcess) {
|
|
@@ -1416,11 +1455,13 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
1416
1455
|
var em = { role: "assistant", content: getErrorMessage(response), isError: true };
|
|
1417
1456
|
if (item._isBgTask) em.isBackgroundTask = true;
|
|
1418
1457
|
if (serverItemId !== void 0) em._serverItemId = serverItemId;
|
|
1458
|
+
if (replyTs !== void 0) em._ts = replyTs;
|
|
1419
1459
|
mapped.push(em);
|
|
1420
1460
|
} else if (assistantText) {
|
|
1421
1461
|
var okm = { role: "assistant", content: sanitizeAttachmentLinksForHistory(assistantText, opts.serviceId, true) };
|
|
1422
1462
|
if (item._isBgTask) okm.isBackgroundTask = true;
|
|
1423
1463
|
if (serverItemId !== void 0) okm._serverItemId = serverItemId;
|
|
1464
|
+
if (replyTs !== void 0) okm._ts = replyTs;
|
|
1424
1465
|
mapped.push(okm);
|
|
1425
1466
|
}
|
|
1426
1467
|
});
|
|
@@ -1833,7 +1874,7 @@ var ChatSession = class {
|
|
|
1833
1874
|
var offExisting = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1834
1875
|
this.aiChatHistoryCache[key] = {
|
|
1835
1876
|
messages: offExisting.messages.concat([
|
|
1836
|
-
{ role: "user", content: composed, _ownerKey: key },
|
|
1877
|
+
{ role: "user", content: composed, _ownerKey: key, _ts: wallClockNow() },
|
|
1837
1878
|
{ role: "assistant", content: "", isPending: true, isPendingInProcess: true, _ownerKey: key }
|
|
1838
1879
|
]),
|
|
1839
1880
|
endOfList: offExisting.endOfList,
|
|
@@ -1865,7 +1906,7 @@ var ChatSession = class {
|
|
|
1865
1906
|
serviceId: id.serviceId,
|
|
1866
1907
|
history: resolvedHistory.concat([{ role: "user", content: llmComposed }])
|
|
1867
1908
|
});
|
|
1868
|
-
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true };
|
|
1909
|
+
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true, _ts: wallClockNow() };
|
|
1869
1910
|
if (key) queuedBubble._ownerKey = key;
|
|
1870
1911
|
if (useBgQueue) queuedBubble._useBgQueue = true;
|
|
1871
1912
|
this.state.messages.push(queuedBubble);
|
|
@@ -1900,7 +1941,7 @@ var ChatSession = class {
|
|
|
1900
1941
|
});
|
|
1901
1942
|
return;
|
|
1902
1943
|
}
|
|
1903
|
-
this.state.messages.push({ role: "user", content: composed, ...key ? { _ownerKey: key } : {} });
|
|
1944
|
+
this.state.messages.push({ role: "user", content: composed, _ts: wallClockNow(), ...key ? { _ownerKey: key } : {} });
|
|
1904
1945
|
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} });
|
|
1905
1946
|
this.host.notify();
|
|
1906
1947
|
this.updateHistoryCache();
|
|
@@ -1957,6 +1998,7 @@ var ChatSession = class {
|
|
|
1957
1998
|
var existing = this.state.messages[nextIdx];
|
|
1958
1999
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true, isBackgroundTask: true };
|
|
1959
2000
|
if (existing._indexFile) promoted._indexFile = existing._indexFile;
|
|
2001
|
+
if (existing._ts !== void 0) promoted._ts = existing._ts;
|
|
1960
2002
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1961
2003
|
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1962
2004
|
this.state.messages[nextIdx] = promoted;
|
|
@@ -1978,6 +2020,7 @@ var ChatSession = class {
|
|
|
1978
2020
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true };
|
|
1979
2021
|
if (existing.isBackgroundTask) promoted.isBackgroundTask = true;
|
|
1980
2022
|
if (existing._indexFile) promoted._indexFile = existing._indexFile;
|
|
2023
|
+
if (existing._ts !== void 0) promoted._ts = existing._ts;
|
|
1981
2024
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1982
2025
|
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1983
2026
|
if (existing.isSendingToServer) promoted.isSendingToServer = true;
|
|
@@ -2027,6 +2070,7 @@ var ChatSession = class {
|
|
|
2027
2070
|
var repl = { role: "user", content: exist.content };
|
|
2028
2071
|
if (exist._serverItemId !== void 0) repl._serverItemId = exist._serverItemId;
|
|
2029
2072
|
if (exist._ownerKey !== void 0) repl._ownerKey = exist._ownerKey;
|
|
2073
|
+
if (exist._ts !== void 0) repl._ts = exist._ts;
|
|
2030
2074
|
this.state.messages[userIdx] = repl;
|
|
2031
2075
|
}
|
|
2032
2076
|
var thinkingIdx = userIdx >= 0 ? this.state.messages.findIndex(function(m, i) {
|
|
@@ -2035,6 +2079,7 @@ var ChatSession = class {
|
|
|
2035
2079
|
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
2036
2080
|
}
|
|
2037
2081
|
insertAtTarget(msg, targetIdx) {
|
|
2082
|
+
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts = wallClockNow();
|
|
2038
2083
|
if (targetIdx >= 0 && this.state.messages[targetIdx] && this.state.messages[targetIdx].isPending) this.state.messages[targetIdx] = msg;
|
|
2039
2084
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
2040
2085
|
else this.state.messages.push(msg);
|
|
@@ -2371,6 +2416,8 @@ var ChatSession = class {
|
|
|
2371
2416
|
}
|
|
2372
2417
|
enqueueTypewrite(idx, fullText, localId) {
|
|
2373
2418
|
var self = this;
|
|
2419
|
+
var target = this.state.messages[idx];
|
|
2420
|
+
if (target && target._ts === void 0) target._ts = wallClockNow();
|
|
2374
2421
|
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
2375
2422
|
return self.typewriteIntoIndex(idx, fullText, localId);
|
|
2376
2423
|
});
|
|
@@ -2440,6 +2487,7 @@ var ChatSession = class {
|
|
|
2440
2487
|
var u = this.state.messages[uIdx];
|
|
2441
2488
|
var cleaned = { role: "user", content: u.content, _serverItemId: itemId };
|
|
2442
2489
|
if (u.isBackgroundTask) cleaned.isBackgroundTask = true;
|
|
2490
|
+
if (u._ts !== void 0) cleaned._ts = u._ts;
|
|
2443
2491
|
if (u._indexFile) cleaned._indexFile = u._indexFile;
|
|
2444
2492
|
this.state.messages[uIdx] = cleaned;
|
|
2445
2493
|
}
|
|
@@ -2512,6 +2560,7 @@ var ChatSession = class {
|
|
|
2512
2560
|
var ex = this.state.messages[userIdx];
|
|
2513
2561
|
var settledUser = { role: "user", content: ex.content, _serverItemId: itemId };
|
|
2514
2562
|
if (ex.isBackgroundTask) settledUser.isBackgroundTask = true;
|
|
2563
|
+
if (ex._ts !== void 0) settledUser._ts = ex._ts;
|
|
2515
2564
|
if (ex._indexFile) settledUser._indexFile = ex._indexFile;
|
|
2516
2565
|
if (ex._useBgQueue) settledUser._useBgQueue = true;
|
|
2517
2566
|
this.state.messages[userIdx] = settledUser;
|
|
@@ -3288,7 +3337,10 @@ function buildChatDisplayList(messages, opts) {
|
|
|
3288
3337
|
cancellableIds: [],
|
|
3289
3338
|
cancelling: false,
|
|
3290
3339
|
mayHaveOlder: false,
|
|
3291
|
-
|
|
3340
|
+
// The run's first loaded pass, and never re-stamped: see the file
|
|
3341
|
+
// docstring. `anchorId` is filled in once every member is known.
|
|
3342
|
+
anchorIndex: i,
|
|
3343
|
+
anchorId: ""
|
|
3292
3344
|
};
|
|
3293
3345
|
order.push(runId);
|
|
3294
3346
|
}
|
|
@@ -3302,7 +3354,6 @@ function buildChatDisplayList(messages, opts) {
|
|
|
3302
3354
|
g.passCount++;
|
|
3303
3355
|
}
|
|
3304
3356
|
g.members.push({ msg, index: i });
|
|
3305
|
-
g.anchorIndex = i;
|
|
3306
3357
|
runOfIndex[i] = runId;
|
|
3307
3358
|
if (msg._serverItemId) runByItemId[msg._serverItemId] = runId;
|
|
3308
3359
|
if (ref && ref.name) keyByName[ref.name] = g.key;
|
|
@@ -3364,6 +3415,9 @@ function buildChatDisplayList(messages, opts) {
|
|
|
3364
3415
|
}
|
|
3365
3416
|
}
|
|
3366
3417
|
grp.mayHaveOlder = !sawFirstPass && hasMoreHistory;
|
|
3418
|
+
var anchor = grp.members[0];
|
|
3419
|
+
grp.anchorIndex = anchor.index;
|
|
3420
|
+
grp.anchorId = anchor.msg._serverItemId || anchor.msg._localId || "";
|
|
3367
3421
|
}
|
|
3368
3422
|
var out = [];
|
|
3369
3423
|
for (var j = 0; j < list.length; j++) {
|
|
@@ -3430,6 +3484,7 @@ exports.extractRemotePathFromAttachmentHref = extractRemotePathFromAttachmentHre
|
|
|
3430
3484
|
exports.fillHistoryViewport = fillHistoryViewport;
|
|
3431
3485
|
exports.filterListByClearHorizon = filterListByClearHorizon;
|
|
3432
3486
|
exports.findAttachmentParser = findAttachmentParser;
|
|
3487
|
+
exports.formatChatTimestamp = formatChatTimestamp;
|
|
3433
3488
|
exports.getAttachmentParsers = getAttachmentParsers;
|
|
3434
3489
|
exports.getChatHistory = getChatHistory;
|
|
3435
3490
|
exports.getContextWindow = getContextWindow;
|
|
@@ -3456,6 +3511,7 @@ exports.parseAttachmentContent = parseAttachmentContent;
|
|
|
3456
3511
|
exports.parseIndexingLabel = parseIndexingLabel;
|
|
3457
3512
|
exports.readExpiredAttachmentHref = readExpiredAttachmentHref;
|
|
3458
3513
|
exports.registerAttachmentParser = registerAttachmentParser;
|
|
3514
|
+
exports.repairUrlEntities = repairUrlEntities;
|
|
3459
3515
|
exports.repairUrlWhitespace = repairUrlWhitespace;
|
|
3460
3516
|
exports.safeDecodeURIComponent = safeDecodeURIComponent;
|
|
3461
3517
|
exports.sanitizeAttachmentLinksForHistory = sanitizeAttachmentLinksForHistory;
|
|
@@ -3463,5 +3519,6 @@ exports.stripFileBlocksFromHistory = stripFileBlocksFromHistory;
|
|
|
3463
3519
|
exports.transformContentWithImages = transformContentWithImages;
|
|
3464
3520
|
exports.transformContentWithOpenAIImages = transformContentWithOpenAIImages;
|
|
3465
3521
|
exports.truncateLabelForDisplay = truncateLabelForDisplay;
|
|
3522
|
+
exports.wallClockNow = wallClockNow;
|
|
3466
3523
|
//# sourceMappingURL=engine.cjs.map
|
|
3467
3524
|
//# sourceMappingURL=engine.cjs.map
|