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/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 they sit, and whatever
849
- * else is interleaved between them) is represented by a single group entry,
850
- * rendered at the position of that file's NEWEST turn. Newest, not oldest, so:
851
- * - a running index sits at the bottom, where the activity is, and
852
- * - paging older history in never moves a row that is already on screen
853
- * (older passes simply join the group they already belong to).
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. Numbered from the NEWEST run backwards (`#0` is the
877
- * newest) so paging in older history never renames a row already on screen.
878
- * This is the render key and the expansion key. */
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
+ * `&amp;` (or the numeric `&#38;` / `&#x26;`). 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 `&amp;` to begin with. Mirrors repairUrlWhitespace: it repairs
438
+ * model damage, not the URL. The loop collapses a doubly-escaped `&amp;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 they sit, and whatever
849
- * else is interleaved between them) is represented by a single group entry,
850
- * rendered at the position of that file's NEWEST turn. Newest, not oldest, so:
851
- * - a running index sits at the bottom, where the activity is, and
852
- * - paging older history in never moves a row that is already on screen
853
- * (older passes simply join the group they already belong to).
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. Numbered from the NEWEST run backwards (`#0` is the
877
- * newest) so paging in older history never renames a row already on screen.
878
- * This is the render key and the expansion key. */
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(/&amp;/gi, "&").replace(/&#0*38;/g, "&").replace(/&#x0*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(rawPath), fullLabel: rawPath, href: rawPath, expired: false },
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
- anchorIndex: i
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