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/dist/engine.d.mts
CHANGED
|
@@ -423,6 +423,21 @@ declare function isHttpUrlLike(target: string): boolean;
|
|
|
423
423
|
* space in it, and deleting it points at a file that does not exist.
|
|
424
424
|
*/
|
|
425
425
|
declare function repairUrlWhitespace(href: string): string;
|
|
426
|
+
/**
|
|
427
|
+
* A model reproducing a URL sometimes HTML-escapes its `&` query separators as
|
|
428
|
+
* `&` (or the numeric `&` / `&`). Left in the href that escaping
|
|
429
|
+
* survives the client's own escapeAttr -> v-html/innerHTML decode round-trip and
|
|
430
|
+
* reaches the browser LITERALLY, so a presigned S3 URL is navigated with its
|
|
431
|
+
* parameters named `amp;Signature`, `amp;Expires`, `amp;response-content-type`,
|
|
432
|
+
* ... — the real params vanish, the signature can't be located, and S3 rejects
|
|
433
|
+
* it (the "링크가 안되" dead export link). Undo just that entity escaping.
|
|
434
|
+
*
|
|
435
|
+
* This is a no-op on a clean URL: a valid link carries a raw `&` between params
|
|
436
|
+
* and percent-encodes (`%26`) any literal ampersand that is data, so a real URL
|
|
437
|
+
* never contains `&` to begin with. Mirrors repairUrlWhitespace: it repairs
|
|
438
|
+
* model damage, not the URL. The loop collapses a doubly-escaped `&` too.
|
|
439
|
+
*/
|
|
440
|
+
declare function repairUrlEntities(href: string): string;
|
|
426
441
|
/**
|
|
427
442
|
* Trim punctuation and unmatched wrappers that cling to a token in prose.
|
|
428
443
|
* `src::a/b.pdf).` -> `src::a/b.pdf`, while a balanced `file (v2).pdf` is kept.
|
|
@@ -466,6 +481,23 @@ declare function classifyInlineLink(full: string, groups: Array<string | undefin
|
|
|
466
481
|
} | null;
|
|
467
482
|
declare function truncateLabelForDisplay(label: string): string;
|
|
468
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Chat timestamp formatting, shared so agent.vue and the widget render an
|
|
486
|
+
* identical "small text under the bubble". Pure and locale-aware: it formats a
|
|
487
|
+
* given epoch-ms value, it never reads the current time, so it stays testable and
|
|
488
|
+
* DOM-free.
|
|
489
|
+
*/
|
|
490
|
+
/** Wall-clock epoch ms. Separate from the engine's monotonic nowMs() (which is
|
|
491
|
+
* performance.now() when available and therefore NOT epoch): a displayed
|
|
492
|
+
* timestamp must be wall time. */
|
|
493
|
+
declare function wallClockNow(): number;
|
|
494
|
+
/**
|
|
495
|
+
* "Jul 24, 2026, 3:42:07 PM" (locale-formatted). Empty string for a missing or
|
|
496
|
+
* non-finite value, so a caller can gate rendering on the result being truthy and
|
|
497
|
+
* a pending bubble (no timestamp yet) simply shows nothing.
|
|
498
|
+
*/
|
|
499
|
+
declare function formatChatTimestamp(ms?: number): string;
|
|
500
|
+
|
|
469
501
|
declare function filterListByClearHorizon(list: any[], clearedAt: number): any[];
|
|
470
502
|
declare function normalizeTextContent(content: any): string;
|
|
471
503
|
declare function extractLastUserTextFromRequest(requestBody: any): string;
|
|
@@ -753,6 +785,12 @@ interface ChatMessage {
|
|
|
753
785
|
_localId?: string;
|
|
754
786
|
_cancelling?: boolean;
|
|
755
787
|
_cancelError?: string;
|
|
788
|
+
/** Epoch ms shown as small text under the bubble. From the request history a
|
|
789
|
+
* USER bubble carries the request's `created` time and an ASSISTANT bubble the
|
|
790
|
+
* `updated` (response) time; a live bubble is stamped with the wall clock when
|
|
791
|
+
* it is created, then reconciled to the server value on the next history load.
|
|
792
|
+
* Absent while a turn is still pending, so no time shows on a "Thinking" bubble. */
|
|
793
|
+
_ts?: number;
|
|
756
794
|
_ownerKey?: string;
|
|
757
795
|
}
|
|
758
796
|
interface ChatState {
|
|
@@ -845,12 +883,34 @@ interface ChatHost {
|
|
|
845
883
|
* repeating forever, and any real question the user asks in between gets buried.
|
|
846
884
|
*
|
|
847
885
|
* buildChatDisplayList turns the flat message array into a DISPLAY list in which
|
|
848
|
-
* every message belonging to one file (however far apart
|
|
849
|
-
* else is interleaved between them) is represented by a single
|
|
850
|
-
* rendered at the position of that
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
886
|
+
* every message belonging to one run of one file (however far apart the passes
|
|
887
|
+
* sit, and whatever else is interleaved between them) is represented by a single
|
|
888
|
+
* group entry, rendered at the position of that run's FIRST loaded pass.
|
|
889
|
+
*
|
|
890
|
+
* First, not newest. The message array is ordered by request CREATION time, and
|
|
891
|
+
* a run's later passes are created one at a time as the previous one resolves —
|
|
892
|
+
* by the client for the paged text path, and by the WORKER itself for the
|
|
893
|
+
* rendered-page (PDF) path, which the client only learns about on its next
|
|
894
|
+
* first-page history fetch. So a pass is routinely created minutes after the
|
|
895
|
+
* upload, on a queue that runs in parallel with the foreground chat. Anchoring
|
|
896
|
+
* at the newest pass meant one such late pass dragged the whole run — every
|
|
897
|
+
* earlier pass with it — below any question the user had asked in the meantime,
|
|
898
|
+
* and made a run that had visibly finished before the question render after it.
|
|
899
|
+
* Anchoring at the first pass puts the row where the run actually began, which
|
|
900
|
+
* is a position no later pass can change:
|
|
901
|
+
* - a new pass never relocates the row, so nothing under the reader shifts;
|
|
902
|
+
* - a question asked after the upload always renders below the row, which is
|
|
903
|
+
* the order it happened in.
|
|
904
|
+
* The cost is that a long-running index does not follow the conversation to the
|
|
905
|
+
* bottom: once the user has chatted past the upload, the spinner and the Stop
|
|
906
|
+
* button sit above their newest turns. That is a scroll away, and the row no
|
|
907
|
+
* longer lies about when the work started.
|
|
908
|
+
*
|
|
909
|
+
* Paging older history is the one thing that CAN move the row: an older page
|
|
910
|
+
* carrying earlier passes of a run already on screen moves it up into that page.
|
|
911
|
+
* That happens at most once per run, only for a run whose start was never
|
|
912
|
+
* loaded (`mayHaveOlder`), and it is the same event that already re-derives
|
|
913
|
+
* `runKey` — so the view treats it as a new row either way.
|
|
854
914
|
*
|
|
855
915
|
* The group deliberately reports no authoritative pass TOTAL. History is paged
|
|
856
916
|
* newest-first, so any total computed from loaded messages is a lower bound that
|
|
@@ -873,9 +933,10 @@ type IndexingGroup = {
|
|
|
873
933
|
* Monday and re-indexed on Wednesday is two runs, and collapsing them into
|
|
874
934
|
* one row erased Monday's from Monday's place in the conversation, claimed
|
|
875
935
|
* its passes for Wednesday, and let Monday's failure be overwritten by
|
|
876
|
-
* Wednesday's success.
|
|
877
|
-
*
|
|
878
|
-
*
|
|
936
|
+
* Wednesday's success. Named after the run's FIRST loaded pass (see where it
|
|
937
|
+
* is assigned below), so passes appended to the run and other runs appearing
|
|
938
|
+
* on either side of it never rename a row already on screen. This is the
|
|
939
|
+
* render key and the expansion key. */
|
|
879
940
|
runKey: string;
|
|
880
941
|
name: string;
|
|
881
942
|
path?: string;
|
|
@@ -904,8 +965,16 @@ type IndexingGroup = {
|
|
|
904
965
|
/** The file's first pass is not among the loaded messages, so earlier passes
|
|
905
966
|
* exist in history that has not been paged in yet. */
|
|
906
967
|
mayHaveOlder: boolean;
|
|
907
|
-
/** Position in the source array this collapsed row renders at
|
|
968
|
+
/** Position in the source array this collapsed row renders at: the index of
|
|
969
|
+
* the run's FIRST loaded pass (see the file docstring for why not the last). */
|
|
908
970
|
anchorIndex: number;
|
|
971
|
+
/** Identity of the turn at `anchorIndex` — its server item id, or its local id
|
|
972
|
+
* while it has none, or `''` when it has neither. The views stamp this on the
|
|
973
|
+
* row (`data-row-pos`) so the scroll anchor can tell a row that RELOCATED (an
|
|
974
|
+
* older page moved the run's start) from one that merely gained a pass. They
|
|
975
|
+
* must not re-derive it from `members`: which member the row renders at is
|
|
976
|
+
* this module's decision, and the two silently disagreed once already. */
|
|
977
|
+
anchorId: string;
|
|
909
978
|
};
|
|
910
979
|
type DisplayEntry = {
|
|
911
980
|
kind: 'message';
|
|
@@ -1134,4 +1203,4 @@ declare class ChatSession {
|
|
|
1134
1203
|
bumpGate(): void;
|
|
1135
1204
|
}
|
|
1136
1205
|
|
|
1137
|
-
export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, type DisplayEntry, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, type PinnedDispatchContext, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAttachmentContent, parseIndexingLabel, readExpiredAttachmentHref, registerAttachmentParser, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
|
|
1206
|
+
export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, type DisplayEntry, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, type PinnedDispatchContext, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAttachmentContent, parseIndexingLabel, readExpiredAttachmentHref, registerAttachmentParser, repairUrlEntities, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, wallClockNow };
|
package/dist/engine.d.ts
CHANGED
|
@@ -423,6 +423,21 @@ declare function isHttpUrlLike(target: string): boolean;
|
|
|
423
423
|
* space in it, and deleting it points at a file that does not exist.
|
|
424
424
|
*/
|
|
425
425
|
declare function repairUrlWhitespace(href: string): string;
|
|
426
|
+
/**
|
|
427
|
+
* A model reproducing a URL sometimes HTML-escapes its `&` query separators as
|
|
428
|
+
* `&` (or the numeric `&` / `&`). Left in the href that escaping
|
|
429
|
+
* survives the client's own escapeAttr -> v-html/innerHTML decode round-trip and
|
|
430
|
+
* reaches the browser LITERALLY, so a presigned S3 URL is navigated with its
|
|
431
|
+
* parameters named `amp;Signature`, `amp;Expires`, `amp;response-content-type`,
|
|
432
|
+
* ... — the real params vanish, the signature can't be located, and S3 rejects
|
|
433
|
+
* it (the "링크가 안되" dead export link). Undo just that entity escaping.
|
|
434
|
+
*
|
|
435
|
+
* This is a no-op on a clean URL: a valid link carries a raw `&` between params
|
|
436
|
+
* and percent-encodes (`%26`) any literal ampersand that is data, so a real URL
|
|
437
|
+
* never contains `&` to begin with. Mirrors repairUrlWhitespace: it repairs
|
|
438
|
+
* model damage, not the URL. The loop collapses a doubly-escaped `&amp;` too.
|
|
439
|
+
*/
|
|
440
|
+
declare function repairUrlEntities(href: string): string;
|
|
426
441
|
/**
|
|
427
442
|
* Trim punctuation and unmatched wrappers that cling to a token in prose.
|
|
428
443
|
* `src::a/b.pdf).` -> `src::a/b.pdf`, while a balanced `file (v2).pdf` is kept.
|
|
@@ -466,6 +481,23 @@ declare function classifyInlineLink(full: string, groups: Array<string | undefin
|
|
|
466
481
|
} | null;
|
|
467
482
|
declare function truncateLabelForDisplay(label: string): string;
|
|
468
483
|
|
|
484
|
+
/**
|
|
485
|
+
* Chat timestamp formatting, shared so agent.vue and the widget render an
|
|
486
|
+
* identical "small text under the bubble". Pure and locale-aware: it formats a
|
|
487
|
+
* given epoch-ms value, it never reads the current time, so it stays testable and
|
|
488
|
+
* DOM-free.
|
|
489
|
+
*/
|
|
490
|
+
/** Wall-clock epoch ms. Separate from the engine's monotonic nowMs() (which is
|
|
491
|
+
* performance.now() when available and therefore NOT epoch): a displayed
|
|
492
|
+
* timestamp must be wall time. */
|
|
493
|
+
declare function wallClockNow(): number;
|
|
494
|
+
/**
|
|
495
|
+
* "Jul 24, 2026, 3:42:07 PM" (locale-formatted). Empty string for a missing or
|
|
496
|
+
* non-finite value, so a caller can gate rendering on the result being truthy and
|
|
497
|
+
* a pending bubble (no timestamp yet) simply shows nothing.
|
|
498
|
+
*/
|
|
499
|
+
declare function formatChatTimestamp(ms?: number): string;
|
|
500
|
+
|
|
469
501
|
declare function filterListByClearHorizon(list: any[], clearedAt: number): any[];
|
|
470
502
|
declare function normalizeTextContent(content: any): string;
|
|
471
503
|
declare function extractLastUserTextFromRequest(requestBody: any): string;
|
|
@@ -753,6 +785,12 @@ interface ChatMessage {
|
|
|
753
785
|
_localId?: string;
|
|
754
786
|
_cancelling?: boolean;
|
|
755
787
|
_cancelError?: string;
|
|
788
|
+
/** Epoch ms shown as small text under the bubble. From the request history a
|
|
789
|
+
* USER bubble carries the request's `created` time and an ASSISTANT bubble the
|
|
790
|
+
* `updated` (response) time; a live bubble is stamped with the wall clock when
|
|
791
|
+
* it is created, then reconciled to the server value on the next history load.
|
|
792
|
+
* Absent while a turn is still pending, so no time shows on a "Thinking" bubble. */
|
|
793
|
+
_ts?: number;
|
|
756
794
|
_ownerKey?: string;
|
|
757
795
|
}
|
|
758
796
|
interface ChatState {
|
|
@@ -845,12 +883,34 @@ interface ChatHost {
|
|
|
845
883
|
* repeating forever, and any real question the user asks in between gets buried.
|
|
846
884
|
*
|
|
847
885
|
* buildChatDisplayList turns the flat message array into a DISPLAY list in which
|
|
848
|
-
* every message belonging to one file (however far apart
|
|
849
|
-
* else is interleaved between them) is represented by a single
|
|
850
|
-
* rendered at the position of that
|
|
851
|
-
*
|
|
852
|
-
*
|
|
853
|
-
*
|
|
886
|
+
* every message belonging to one run of one file (however far apart the passes
|
|
887
|
+
* sit, and whatever else is interleaved between them) is represented by a single
|
|
888
|
+
* group entry, rendered at the position of that run's FIRST loaded pass.
|
|
889
|
+
*
|
|
890
|
+
* First, not newest. The message array is ordered by request CREATION time, and
|
|
891
|
+
* a run's later passes are created one at a time as the previous one resolves —
|
|
892
|
+
* by the client for the paged text path, and by the WORKER itself for the
|
|
893
|
+
* rendered-page (PDF) path, which the client only learns about on its next
|
|
894
|
+
* first-page history fetch. So a pass is routinely created minutes after the
|
|
895
|
+
* upload, on a queue that runs in parallel with the foreground chat. Anchoring
|
|
896
|
+
* at the newest pass meant one such late pass dragged the whole run — every
|
|
897
|
+
* earlier pass with it — below any question the user had asked in the meantime,
|
|
898
|
+
* and made a run that had visibly finished before the question render after it.
|
|
899
|
+
* Anchoring at the first pass puts the row where the run actually began, which
|
|
900
|
+
* is a position no later pass can change:
|
|
901
|
+
* - a new pass never relocates the row, so nothing under the reader shifts;
|
|
902
|
+
* - a question asked after the upload always renders below the row, which is
|
|
903
|
+
* the order it happened in.
|
|
904
|
+
* The cost is that a long-running index does not follow the conversation to the
|
|
905
|
+
* bottom: once the user has chatted past the upload, the spinner and the Stop
|
|
906
|
+
* button sit above their newest turns. That is a scroll away, and the row no
|
|
907
|
+
* longer lies about when the work started.
|
|
908
|
+
*
|
|
909
|
+
* Paging older history is the one thing that CAN move the row: an older page
|
|
910
|
+
* carrying earlier passes of a run already on screen moves it up into that page.
|
|
911
|
+
* That happens at most once per run, only for a run whose start was never
|
|
912
|
+
* loaded (`mayHaveOlder`), and it is the same event that already re-derives
|
|
913
|
+
* `runKey` — so the view treats it as a new row either way.
|
|
854
914
|
*
|
|
855
915
|
* The group deliberately reports no authoritative pass TOTAL. History is paged
|
|
856
916
|
* newest-first, so any total computed from loaded messages is a lower bound that
|
|
@@ -873,9 +933,10 @@ type IndexingGroup = {
|
|
|
873
933
|
* Monday and re-indexed on Wednesday is two runs, and collapsing them into
|
|
874
934
|
* one row erased Monday's from Monday's place in the conversation, claimed
|
|
875
935
|
* its passes for Wednesday, and let Monday's failure be overwritten by
|
|
876
|
-
* Wednesday's success.
|
|
877
|
-
*
|
|
878
|
-
*
|
|
936
|
+
* Wednesday's success. Named after the run's FIRST loaded pass (see where it
|
|
937
|
+
* is assigned below), so passes appended to the run and other runs appearing
|
|
938
|
+
* on either side of it never rename a row already on screen. This is the
|
|
939
|
+
* render key and the expansion key. */
|
|
879
940
|
runKey: string;
|
|
880
941
|
name: string;
|
|
881
942
|
path?: string;
|
|
@@ -904,8 +965,16 @@ type IndexingGroup = {
|
|
|
904
965
|
/** The file's first pass is not among the loaded messages, so earlier passes
|
|
905
966
|
* exist in history that has not been paged in yet. */
|
|
906
967
|
mayHaveOlder: boolean;
|
|
907
|
-
/** Position in the source array this collapsed row renders at
|
|
968
|
+
/** Position in the source array this collapsed row renders at: the index of
|
|
969
|
+
* the run's FIRST loaded pass (see the file docstring for why not the last). */
|
|
908
970
|
anchorIndex: number;
|
|
971
|
+
/** Identity of the turn at `anchorIndex` — its server item id, or its local id
|
|
972
|
+
* while it has none, or `''` when it has neither. The views stamp this on the
|
|
973
|
+
* row (`data-row-pos`) so the scroll anchor can tell a row that RELOCATED (an
|
|
974
|
+
* older page moved the run's start) from one that merely gained a pass. They
|
|
975
|
+
* must not re-derive it from `members`: which member the row renders at is
|
|
976
|
+
* this module's decision, and the two silently disagreed once already. */
|
|
977
|
+
anchorId: string;
|
|
909
978
|
};
|
|
910
979
|
type DisplayEntry = {
|
|
911
980
|
kind: 'message';
|
|
@@ -1134,4 +1203,4 @@ declare class ChatSession {
|
|
|
1134
1203
|
bumpGate(): void;
|
|
1135
1204
|
}
|
|
1136
1205
|
|
|
1137
|
-
export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, type DisplayEntry, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, type PinnedDispatchContext, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAttachmentContent, parseIndexingLabel, readExpiredAttachmentHref, registerAttachmentParser, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
|
|
1206
|
+
export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, type BuildDisplayListOptions, type BuildIndexingUserMessageOptions, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, type CallClaudeWithMcpParams, type ChatEngineConfig, type ChatHost, type ChatIdentity, type ChatMessage, ChatSession, type ChatState, type ChatSystemPromptParams, type ClaudeMcpServerRequest, type ClaudeMcpToolConfig, type ClaudeMessage, type ClaudeRole, type ComposedUserMessage, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, type DisplayEntry, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, type ExtractDirective, type FillHistoryViewportOptions, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingFileRef, type IndexingGroup, type IndexingGroupStatus, type IndexingSystemPromptParams, type InlineLinkContext, type InlineLinkPart, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, type PinnedDispatchContext, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAttachmentContent, parseIndexingLabel, readExpiredAttachmentHref, registerAttachmentParser, repairUrlEntities, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, wallClockNow };
|
package/dist/engine.mjs
CHANGED
|
@@ -284,6 +284,9 @@ function buildChatSystemPrompt(params) {
|
|
|
284
284
|
You are a dedicated assistant for the project ID: "${formattedServiceId}".
|
|
285
285
|
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.
|
|
286
286
|
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.
|
|
287
|
+
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.
|
|
288
|
+
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.
|
|
289
|
+
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.
|
|
287
290
|
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.
|
|
288
291
|
- 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.
|
|
289
292
|
- 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.
|
|
@@ -618,6 +621,15 @@ function repairUrlWhitespace(href) {
|
|
|
618
621
|
if (/^https?:\/\/[^/\s]+\/download\/[A-Za-z0-9_-]+(\.[A-Za-z0-9_-]+)?$/i.test(stripped)) return stripped;
|
|
619
622
|
return href.trim().replace(/\s/g, "%20");
|
|
620
623
|
}
|
|
624
|
+
function repairUrlEntities(href) {
|
|
625
|
+
if (!href || href.indexOf("&") === -1) return href;
|
|
626
|
+
var out = href, prev = "";
|
|
627
|
+
while (out !== prev) {
|
|
628
|
+
prev = out;
|
|
629
|
+
out = out.replace(/&/gi, "&").replace(/�*38;/g, "&").replace(/�*26;/gi, "&");
|
|
630
|
+
}
|
|
631
|
+
return out;
|
|
632
|
+
}
|
|
621
633
|
function normalizeTrailingInlineToken(value) {
|
|
622
634
|
if (!value) return value;
|
|
623
635
|
var out = value.replace(/[.,;:!?]+$/, "");
|
|
@@ -665,8 +677,9 @@ function classifyInlineLink(full, groups, ctx) {
|
|
|
665
677
|
var tail = full.slice(("src::" + rawPath).length);
|
|
666
678
|
var srcIsUrl = isHttpUrlLike(rawPath);
|
|
667
679
|
if (srcIsUrl && !isDbHost(rawPath) && !readExpiredAttachmentHref(rawPath)) {
|
|
680
|
+
var srcUrl = repairUrlEntities(rawPath);
|
|
668
681
|
return {
|
|
669
|
-
part: { type: "link", label: truncateLabelForDisplay(
|
|
682
|
+
part: { type: "link", label: truncateLabelForDisplay(srcUrl), fullLabel: srcUrl, href: srcUrl, expired: false },
|
|
670
683
|
tail
|
|
671
684
|
};
|
|
672
685
|
}
|
|
@@ -700,6 +713,7 @@ function classifyInlineLink(full, groups, ctx) {
|
|
|
700
713
|
}
|
|
701
714
|
var originalHref = g3 || g6 || "";
|
|
702
715
|
if (!originalHref) return null;
|
|
716
|
+
originalHref = repairUrlEntities(originalHref);
|
|
703
717
|
var urlTail;
|
|
704
718
|
if (!g3 && g6) {
|
|
705
719
|
var trimmedUrl = normalizeTrailingInlineToken(originalHref);
|
|
@@ -800,6 +814,26 @@ function buildBoundedChatMessages(options) {
|
|
|
800
814
|
};
|
|
801
815
|
}
|
|
802
816
|
|
|
817
|
+
// src/engine/time.ts
|
|
818
|
+
function wallClockNow() {
|
|
819
|
+
return Date.now();
|
|
820
|
+
}
|
|
821
|
+
function formatChatTimestamp(ms) {
|
|
822
|
+
if (typeof ms !== "number" || !isFinite(ms) || ms <= 0) return "";
|
|
823
|
+
try {
|
|
824
|
+
return new Date(ms).toLocaleString(void 0, {
|
|
825
|
+
year: "numeric",
|
|
826
|
+
month: "short",
|
|
827
|
+
day: "numeric",
|
|
828
|
+
hour: "numeric",
|
|
829
|
+
minute: "2-digit",
|
|
830
|
+
second: "2-digit"
|
|
831
|
+
});
|
|
832
|
+
} catch (e) {
|
|
833
|
+
return "";
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
803
837
|
// src/engine/requests.ts
|
|
804
838
|
var ANTHROPIC_MESSAGES_API_URL = "https://api.anthropic.com/v1/messages";
|
|
805
839
|
var ANTHROPIC_MODELS_API_URL = "https://api.anthropic.com/v1/models";
|
|
@@ -1361,6 +1395,10 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
1361
1395
|
var assistantText = isPending ? "" : (extractAssistantText(response) || "").trim() || "";
|
|
1362
1396
|
var isErrorResponse = !isPending && (isFailed || isErrorResponseBody(response));
|
|
1363
1397
|
var serverItemId = item && typeof item.id === "string" && item.id ? item.id : void 0;
|
|
1398
|
+
var createdTs = Number(item && item.created);
|
|
1399
|
+
var updatedTs = Number(item && item.updated);
|
|
1400
|
+
var userTs = isFinite(createdTs) && createdTs > 0 ? createdTs : isFinite(updatedTs) && updatedTs > 0 ? updatedTs : void 0;
|
|
1401
|
+
var replyTs = isFinite(updatedTs) && updatedTs > 0 ? updatedTs : isFinite(createdTs) && createdTs > 0 ? createdTs : void 0;
|
|
1364
1402
|
if (userText) {
|
|
1365
1403
|
var displayContent;
|
|
1366
1404
|
var indexFile = void 0;
|
|
@@ -1400,6 +1438,7 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
1400
1438
|
if (indexFile) userMsg._indexFile = indexFile;
|
|
1401
1439
|
if (item._isOnBgQueue) userMsg._useBgQueue = true;
|
|
1402
1440
|
if (serverItemId !== void 0) userMsg._serverItemId = serverItemId;
|
|
1441
|
+
if (userTs !== void 0) userMsg._ts = userTs;
|
|
1403
1442
|
mapped.push(userMsg);
|
|
1404
1443
|
}
|
|
1405
1444
|
if (isCancelledItem) ; else if (isInProcess) {
|
|
@@ -1414,11 +1453,13 @@ function mapHistoryListToMessages(list, platform, opts) {
|
|
|
1414
1453
|
var em = { role: "assistant", content: getErrorMessage(response), isError: true };
|
|
1415
1454
|
if (item._isBgTask) em.isBackgroundTask = true;
|
|
1416
1455
|
if (serverItemId !== void 0) em._serverItemId = serverItemId;
|
|
1456
|
+
if (replyTs !== void 0) em._ts = replyTs;
|
|
1417
1457
|
mapped.push(em);
|
|
1418
1458
|
} else if (assistantText) {
|
|
1419
1459
|
var okm = { role: "assistant", content: sanitizeAttachmentLinksForHistory(assistantText, opts.serviceId, true) };
|
|
1420
1460
|
if (item._isBgTask) okm.isBackgroundTask = true;
|
|
1421
1461
|
if (serverItemId !== void 0) okm._serverItemId = serverItemId;
|
|
1462
|
+
if (replyTs !== void 0) okm._ts = replyTs;
|
|
1422
1463
|
mapped.push(okm);
|
|
1423
1464
|
}
|
|
1424
1465
|
});
|
|
@@ -1831,7 +1872,7 @@ var ChatSession = class {
|
|
|
1831
1872
|
var offExisting = this.aiChatHistoryCache[key] || { messages: [], endOfList: false, startKeyHistory: [] };
|
|
1832
1873
|
this.aiChatHistoryCache[key] = {
|
|
1833
1874
|
messages: offExisting.messages.concat([
|
|
1834
|
-
{ role: "user", content: composed, _ownerKey: key },
|
|
1875
|
+
{ role: "user", content: composed, _ownerKey: key, _ts: wallClockNow() },
|
|
1835
1876
|
{ role: "assistant", content: "", isPending: true, isPendingInProcess: true, _ownerKey: key }
|
|
1836
1877
|
]),
|
|
1837
1878
|
endOfList: offExisting.endOfList,
|
|
@@ -1863,7 +1904,7 @@ var ChatSession = class {
|
|
|
1863
1904
|
serviceId: id.serviceId,
|
|
1864
1905
|
history: resolvedHistory.concat([{ role: "user", content: llmComposed }])
|
|
1865
1906
|
});
|
|
1866
|
-
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true };
|
|
1907
|
+
var queuedBubble = { role: "user", content: composed, isPendingQueued: true, isSendingToServer: true, _ts: wallClockNow() };
|
|
1867
1908
|
if (key) queuedBubble._ownerKey = key;
|
|
1868
1909
|
if (useBgQueue) queuedBubble._useBgQueue = true;
|
|
1869
1910
|
this.state.messages.push(queuedBubble);
|
|
@@ -1898,7 +1939,7 @@ var ChatSession = class {
|
|
|
1898
1939
|
});
|
|
1899
1940
|
return;
|
|
1900
1941
|
}
|
|
1901
|
-
this.state.messages.push({ role: "user", content: composed, ...key ? { _ownerKey: key } : {} });
|
|
1942
|
+
this.state.messages.push({ role: "user", content: composed, _ts: wallClockNow(), ...key ? { _ownerKey: key } : {} });
|
|
1902
1943
|
this.state.messages.push({ role: "assistant", content: "", isPending: true, isPendingInProcess: true, ...key ? { _ownerKey: key } : {} });
|
|
1903
1944
|
this.host.notify();
|
|
1904
1945
|
this.updateHistoryCache();
|
|
@@ -1955,6 +1996,7 @@ var ChatSession = class {
|
|
|
1955
1996
|
var existing = this.state.messages[nextIdx];
|
|
1956
1997
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true, isBackgroundTask: true };
|
|
1957
1998
|
if (existing._indexFile) promoted._indexFile = existing._indexFile;
|
|
1999
|
+
if (existing._ts !== void 0) promoted._ts = existing._ts;
|
|
1958
2000
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1959
2001
|
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1960
2002
|
this.state.messages[nextIdx] = promoted;
|
|
@@ -1976,6 +2018,7 @@ var ChatSession = class {
|
|
|
1976
2018
|
var promoted = { role: "user", content: existing.content, isPendingInProcess: true };
|
|
1977
2019
|
if (existing.isBackgroundTask) promoted.isBackgroundTask = true;
|
|
1978
2020
|
if (existing._indexFile) promoted._indexFile = existing._indexFile;
|
|
2021
|
+
if (existing._ts !== void 0) promoted._ts = existing._ts;
|
|
1979
2022
|
if (existing._serverItemId !== void 0) promoted._serverItemId = existing._serverItemId;
|
|
1980
2023
|
if (existing._ownerKey !== void 0) promoted._ownerKey = existing._ownerKey;
|
|
1981
2024
|
if (existing.isSendingToServer) promoted.isSendingToServer = true;
|
|
@@ -2025,6 +2068,7 @@ var ChatSession = class {
|
|
|
2025
2068
|
var repl = { role: "user", content: exist.content };
|
|
2026
2069
|
if (exist._serverItemId !== void 0) repl._serverItemId = exist._serverItemId;
|
|
2027
2070
|
if (exist._ownerKey !== void 0) repl._ownerKey = exist._ownerKey;
|
|
2071
|
+
if (exist._ts !== void 0) repl._ts = exist._ts;
|
|
2028
2072
|
this.state.messages[userIdx] = repl;
|
|
2029
2073
|
}
|
|
2030
2074
|
var thinkingIdx = userIdx >= 0 ? this.state.messages.findIndex(function(m, i) {
|
|
@@ -2033,6 +2077,7 @@ var ChatSession = class {
|
|
|
2033
2077
|
return thinkingIdx !== -1 ? thinkingIdx : userIdx >= 0 ? userIdx + 1 : -1;
|
|
2034
2078
|
}
|
|
2035
2079
|
insertAtTarget(msg, targetIdx) {
|
|
2080
|
+
if (msg && msg.role === "assistant" && msg._ts === void 0) msg._ts = wallClockNow();
|
|
2036
2081
|
if (targetIdx >= 0 && this.state.messages[targetIdx] && this.state.messages[targetIdx].isPending) this.state.messages[targetIdx] = msg;
|
|
2037
2082
|
else if (targetIdx >= 0) this.state.messages.splice(targetIdx, 0, msg);
|
|
2038
2083
|
else this.state.messages.push(msg);
|
|
@@ -2369,6 +2414,8 @@ var ChatSession = class {
|
|
|
2369
2414
|
}
|
|
2370
2415
|
enqueueTypewrite(idx, fullText, localId) {
|
|
2371
2416
|
var self = this;
|
|
2417
|
+
var target = this.state.messages[idx];
|
|
2418
|
+
if (target && target._ts === void 0) target._ts = wallClockNow();
|
|
2372
2419
|
this.typewriterQueue = this.typewriterQueue.then(function() {
|
|
2373
2420
|
return self.typewriteIntoIndex(idx, fullText, localId);
|
|
2374
2421
|
});
|
|
@@ -2438,6 +2485,7 @@ var ChatSession = class {
|
|
|
2438
2485
|
var u = this.state.messages[uIdx];
|
|
2439
2486
|
var cleaned = { role: "user", content: u.content, _serverItemId: itemId };
|
|
2440
2487
|
if (u.isBackgroundTask) cleaned.isBackgroundTask = true;
|
|
2488
|
+
if (u._ts !== void 0) cleaned._ts = u._ts;
|
|
2441
2489
|
if (u._indexFile) cleaned._indexFile = u._indexFile;
|
|
2442
2490
|
this.state.messages[uIdx] = cleaned;
|
|
2443
2491
|
}
|
|
@@ -2510,6 +2558,7 @@ var ChatSession = class {
|
|
|
2510
2558
|
var ex = this.state.messages[userIdx];
|
|
2511
2559
|
var settledUser = { role: "user", content: ex.content, _serverItemId: itemId };
|
|
2512
2560
|
if (ex.isBackgroundTask) settledUser.isBackgroundTask = true;
|
|
2561
|
+
if (ex._ts !== void 0) settledUser._ts = ex._ts;
|
|
2513
2562
|
if (ex._indexFile) settledUser._indexFile = ex._indexFile;
|
|
2514
2563
|
if (ex._useBgQueue) settledUser._useBgQueue = true;
|
|
2515
2564
|
this.state.messages[userIdx] = settledUser;
|
|
@@ -3286,7 +3335,10 @@ function buildChatDisplayList(messages, opts) {
|
|
|
3286
3335
|
cancellableIds: [],
|
|
3287
3336
|
cancelling: false,
|
|
3288
3337
|
mayHaveOlder: false,
|
|
3289
|
-
|
|
3338
|
+
// The run's first loaded pass, and never re-stamped: see the file
|
|
3339
|
+
// docstring. `anchorId` is filled in once every member is known.
|
|
3340
|
+
anchorIndex: i,
|
|
3341
|
+
anchorId: ""
|
|
3290
3342
|
};
|
|
3291
3343
|
order.push(runId);
|
|
3292
3344
|
}
|
|
@@ -3300,7 +3352,6 @@ function buildChatDisplayList(messages, opts) {
|
|
|
3300
3352
|
g.passCount++;
|
|
3301
3353
|
}
|
|
3302
3354
|
g.members.push({ msg, index: i });
|
|
3303
|
-
g.anchorIndex = i;
|
|
3304
3355
|
runOfIndex[i] = runId;
|
|
3305
3356
|
if (msg._serverItemId) runByItemId[msg._serverItemId] = runId;
|
|
3306
3357
|
if (ref && ref.name) keyByName[ref.name] = g.key;
|
|
@@ -3362,6 +3413,9 @@ function buildChatDisplayList(messages, opts) {
|
|
|
3362
3413
|
}
|
|
3363
3414
|
}
|
|
3364
3415
|
grp.mayHaveOlder = !sawFirstPass && hasMoreHistory;
|
|
3416
|
+
var anchor = grp.members[0];
|
|
3417
|
+
grp.anchorIndex = anchor.index;
|
|
3418
|
+
grp.anchorId = anchor.msg._serverItemId || anchor.msg._localId || "";
|
|
3365
3419
|
}
|
|
3366
3420
|
var out = [];
|
|
3367
3421
|
for (var j = 0; j < list.length; j++) {
|
|
@@ -3375,6 +3429,6 @@ function buildChatDisplayList(messages, opts) {
|
|
|
3375
3429
|
return out;
|
|
3376
3430
|
}
|
|
3377
3431
|
|
|
3378
|
-
export { BG_INDEXING_QUEUE_SUFFIX, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAttachmentContent, parseIndexingLabel, readExpiredAttachmentHref, registerAttachmentParser, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
|
|
3432
|
+
export { BG_INDEXING_QUEUE_SUFFIX, CLAUDE_PER_REQUEST_INPUT_CAP, CONTEXT_WINDOW_BY_MODEL, CONTEXT_WINDOW_DEFAULT, ChatSession, DEFAULT_CLAUDE_MODEL, DEFAULT_OPENAI_MODEL, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, EXPIRED_LINK_REFRESH_EXPIRES_SECONDS, HISTORY_FILL_SLACK_PX, HISTORY_TOKEN_BUDGET, LINK_LABEL_MAX_DISPLAY_CHARS, LINK_REFRESH_WINDOW_MS, MAX_HISTORY_FILL_PAGES, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, OUTPUT_TOKEN_RESERVE, POLL_INTERVAL, RENDER_FROM_TOKEN, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatDisplayList, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, classifyInlineLink, clearAttachmentParsers, composeUserMessage, configureChatEngine, createHistoryFiller, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, fillHistoryViewport, filterListByClearHorizon, findAttachmentParser, formatChatTimestamp, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isHttpUrlLike, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, normalizeTrailingInlineToken, notifyAgentSaveAttachment, parseAttachmentContent, parseIndexingLabel, readExpiredAttachmentHref, registerAttachmentParser, repairUrlEntities, repairUrlWhitespace, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay, wallClockNow };
|
|
3379
3433
|
//# sourceMappingURL=engine.mjs.map
|
|
3380
3434
|
//# sourceMappingURL=engine.mjs.map
|