bunnyquery 1.6.0 → 1.6.2

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
@@ -81,6 +81,20 @@ interface ChatEngineConfig {
81
81
  * `registerAttachmentParser()`. See attachment_parsers.ts.
82
82
  */
83
83
  attachmentParsers?: AttachmentParser[];
84
+ /**
85
+ * Opt in to SERVER-DRIVEN windowed indexing for text/grid files.
86
+ *
87
+ * Off by default, and deliberately so. When on, the client emits a
88
+ * `_skapi_window` directive and the WORKER reads the file one window at a time,
89
+ * continuing until the reader says it is exhausted. When off, the agent pages the
90
+ * file itself with readFileContent, exactly as before.
91
+ *
92
+ * The flag exists because the backend must ship FIRST: a client emitting the
93
+ * directive against a worker that does not strip it leaves an unknown field in the
94
+ * request body, and the provider rejects the whole call with no retry. Keep it off
95
+ * until the worker is deployed, then flip it per environment.
96
+ */
97
+ windowedIndexing?: boolean;
84
98
  }
85
99
  declare function configureChatEngine(config: ChatEngineConfig): void;
86
100
  declare function chatEngineConfig(): ChatEngineConfig;
@@ -241,18 +255,46 @@ type BuildIndexingUserMessageOptions = {
241
255
  pagedRead?: boolean;
242
256
  };
243
257
  declare function buildIndexingUserMessage(attachment: IndexingAttachmentInfo, options?: BuildIndexingUserMessageOptions): string;
258
+ /**
259
+ * Token the WORKER substitutes with the 1-based first page of the window it is about to
260
+ * render, when it builds the next pass of a document from `RENDER_CONTINUE_TEMPLATE`.
261
+ * Must match the worker's RENDER_FROM_TOKEN.
262
+ */
263
+ declare const RENDER_FROM_TOKEN = "{{RENDER_FROM}}";
244
264
  /**
245
265
  * User message for a VISION file (PDF): its pages are delivered as RENDERED PAGE IMAGES that
246
266
  * the proxy worker injects into THIS message at the `placeholder` token (tool-result images
247
267
  * render on neither provider, so the pages must be image blocks in the message itself). Each
248
- * pass shows one WINDOW of pages starting at `renderFrom` (0-based); the resume loop advances
249
- * the window a pass at a time until the injected note says the last window was reached.
268
+ * pass shows one WINDOW of pages starting at `renderFrom` (0-based).
269
+ *
270
+ * The WORKER advances the window: when its renderer reports pages remaining it enqueues the
271
+ * next pass itself, off the true page count, so a document indexes end-to-end with no browser
272
+ * involved. This message therefore only ever describes ONE window, and the model is never
273
+ * asked to decide whether the document is finished.
250
274
  *
251
275
  * renderFrom === 0 is the FIRST pass (leads with "A new file has just been uploaded." so the
252
- * client builds the "Indexing: <name>" bubble); renderFrom > 0 is a RESUME pass (leads with
253
- * "CONTINUE indexing" like the paged continue message, so it is not a duplicate primary bubble).
276
+ * client builds the "Indexing: <name>" bubble); a continue pass (built by the worker from
277
+ * buildIndexingRenderContinueTemplate) leads with "CONTINUE indexing" so it is not a duplicate
278
+ * primary bubble.
254
279
  */
255
280
  declare function buildIndexingRenderMessage(attachment: IndexingAttachmentInfo, placeholder: string, renderFrom: number): string;
281
+ /**
282
+ * The CONTINUE pass, as a template the worker fills in. `pageLabel` defaults to the
283
+ * RENDER_FROM_TOKEN placeholder, which the worker replaces with the real 1-based start page
284
+ * of the window it is rendering; passing an explicit label produces a ready-to-send message.
285
+ */
286
+ declare function buildIndexingRenderContinueTemplate(attachment: IndexingAttachmentInfo, placeholder: string, pageLabel?: string): string;
287
+ /**
288
+ * User message for a WINDOWED file: the worker splices ONE window of the file's rows or
289
+ * text into this message at `placeholder`, then continues from the reader's own cursor
290
+ * until the file is exhausted.
291
+ *
292
+ * The agent is deliberately NOT asked to page the file itself, and is NOT asked to judge
293
+ * whether it is finished. Both used to be its job, and both failed the same way: the
294
+ * traversal lived inside a single turn's budget, so a large file simply stopped partway
295
+ * with a confident summary of the part it had seen.
296
+ */
297
+ declare function buildIndexingWindowMessage(attachment: IndexingAttachmentInfo, placeholder: string, isContinuation: boolean, positionLabel?: string): string;
256
298
  /**
257
299
  * User message for a RESUME pass: a previous indexing pass could not finish this large
258
300
  * file, so continue it from where the already-saved records leave off (never restart).
@@ -423,6 +465,16 @@ declare function extractOpenAIText(response: any): any;
423
465
  declare function listClaudeModels(service: string, owner: string): Promise<any>;
424
466
  declare function listOpenAIModels(service: string, owner: string): Promise<any>;
425
467
  declare const BG_INDEXING_QUEUE_SUFFIX = "-bg";
468
+ /**
469
+ * True when a request belongs to the background-indexing queue.
470
+ *
471
+ * Accepts BOTH shapes this value arrives in: the bare queue name the client sends
472
+ * ("<userId>-bg"), and the server qid that comes back on history/poll responses
473
+ * ("<service>:<queue>|<seq>"). Testing the tail of the raw value only works for the
474
+ * first: a qid ends in "|<seq>", so `endsWith('-bg')` is always false for it — which
475
+ * silently meant history items were NEVER recognised as background tasks.
476
+ */
477
+ declare function isBgIndexingQueue(queueName?: string): boolean;
426
478
  type BgTaskEntry = {
427
479
  serviceId: string;
428
480
  platform: 'claude' | 'openai';
@@ -464,6 +516,17 @@ interface ChatIdentity {
464
516
  serviceName?: string;
465
517
  serviceDescription?: string;
466
518
  }
519
+ /**
520
+ * Project context captured at the moment the user hit Send, so a turn whose
521
+ * dispatch is delayed (attachment uploads are awaited first) still reaches the
522
+ * project the question was asked of rather than whichever project the user has
523
+ * navigated to by then. Both fields are identity-derived and must be snapshotted
524
+ * together — the system prompt embeds the service name/description/id.
525
+ */
526
+ interface PinnedDispatchContext {
527
+ identity: ChatIdentity;
528
+ systemPrompt: string;
529
+ }
467
530
  interface ChatMessage {
468
531
  role: 'user' | 'assistant';
469
532
  content: string;
@@ -480,6 +543,7 @@ interface ChatMessage {
480
543
  _localId?: string;
481
544
  _cancelling?: boolean;
482
545
  _cancelError?: string;
546
+ _ownerKey?: string;
483
547
  }
484
548
  interface ChatState {
485
549
  messages: ChatMessage[];
@@ -576,6 +640,13 @@ interface ChatHost {
576
640
  * session via the public methods and re-renders in host.notify().
577
641
  */
578
642
 
643
+ /** A live poll registered in ChatSession.historyItemPolls. */
644
+ type PollHandle = {
645
+ /** 'bg' = background indexing, pausable. 'fg' = a reply the user is waiting on. */
646
+ kind: 'fg' | 'bg';
647
+ /** Absent on an older skapi-js that cannot stop an attached poll. */
648
+ stop?: () => void;
649
+ };
579
650
  declare class ChatSession {
580
651
  host: ChatHost;
581
652
  state: ChatState;
@@ -587,21 +658,73 @@ declare class ChatSession {
587
658
  endOfList: boolean;
588
659
  startKeyHistory: string[];
589
660
  }>;
590
- historyItemPolls: Map<string, boolean>;
661
+ historyItemPolls: Map<string, PollHandle>;
662
+ /** Non-empty while polling is paused; keyed by reason so overlapping causes
663
+ * (view detached AND tab hidden) do not resume each other prematurely. */
664
+ private _pauseReasons;
665
+ private _resuming;
591
666
  private _lidSeq;
592
667
  constructor(host: ChatHost);
668
+ /**
669
+ * Register a live poll so (a) a remount dedupes against it instead of stacking a
670
+ * SECOND poll on the same item, and (b) pausePolling can stop it.
671
+ *
672
+ * `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
673
+ * poll simply cannot be stopped and is left running — see pausePolling.
674
+ */
675
+ private _trackPoll;
676
+ /** True while any pause reason is active. */
677
+ isPollingPaused(): boolean;
678
+ /**
679
+ * Stop BACKGROUND polling until resumePolling. Foreground polls (a reply the user is
680
+ * waiting on) keep running deliberately: their results must still land in the history
681
+ * cache so resumePendingRequest can render them on return, otherwise a user who sends
682
+ * a message then navigates away comes back to a permanently stuck "Thinking...".
683
+ *
684
+ * Server-side work is untouched; this only stops asking about it. That is safe for
685
+ * document indexing because the worker drives that loop itself.
686
+ */
687
+ pausePolling(reason: string): void;
688
+ /**
689
+ * Lift a pause reason WITHOUT running the reconcile. For a caller that is about to
690
+ * reload history anyway (a view remounting), letting resumePolling also reconcile
691
+ * would race that load and can double-attach.
692
+ */
693
+ clearPauseReason(reason: string): void;
694
+ /**
695
+ * Clear a pause reason and, once none remain, re-attach polling and reconcile.
696
+ * Deliberately does NOT touch gateRefreshToken: bumping it would silently discard
697
+ * the results of anything still in flight across the pause.
698
+ */
699
+ resumePolling(reason: string): Promise<void>;
593
700
  private _newLocalId;
594
701
  getHistoryCacheKey(): string;
595
702
  updateHistoryCache(): void;
703
+ /**
704
+ * Land a resolved reply in the history cache of a chat that is NOT currently
705
+ * visible, without touching state.messages. Mirrors the cache-only path in
706
+ * dispatchAgentRequest: REPLACE the trailing pending "Thinking..." bubble
707
+ * (append only when there is none), and settle the matching pending user
708
+ * bubble, so the cached copy never keeps a stuck "Thinking..." that a later
709
+ * cache-first load would re-render forever.
710
+ */
711
+ private _applyReplyToCache;
712
+ /**
713
+ * serviceId/owner are passed explicitly by every caller: a request can be
714
+ * dispatched after the user moved to another project, and re-reading the live
715
+ * identity here would silently send the turn to THAT project instead of the
716
+ * one it was composed for. Falls back to the live read only when a caller
717
+ * omits them.
718
+ */
596
719
  private _callProviderFor;
597
720
  dispatchAgentRequest(params: any): Promise<any>;
598
- dispatchComposedMessage(composed: string, useBgQueue?: boolean, composedForLlm?: string, extractContent?: any, fileUrls?: any): void;
721
+ dispatchComposedMessage(composed: string, useBgQueue?: boolean, composedForLlm?: string, extractContent?: any, fileUrls?: any, pinned?: PinnedDispatchContext): void;
599
722
  promoteNextBgQueuedToRunning(): void;
600
723
  promoteNextQueuedToRunning(): void;
601
724
  resolveQueuedUserBubble(serverId?: string): number | undefined;
602
725
  insertAtTarget(msg: ChatMessage, targetIdx: number): void;
603
- onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string): void;
604
- onQueuedSendError(_composed: string, err: any, serverId?: string): void;
726
+ onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string, ownerKey?: string): void;
727
+ onQueuedSendError(_composed: string, err: any, serverId?: string, ownerKey?: string): void;
605
728
  cancelQueuedMessage(msg: ChatMessage, idx: number): void;
606
729
  typewriteIntoIndex(idx: number, fullText: string, localId?: string): Promise<void>;
607
730
  private typewriterQueue;
@@ -629,4 +752,4 @@ declare class ChatSession {
629
752
  bumpGate(): void;
630
753
  }
631
754
 
632
- export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, 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, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, type ExtractDirective, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingSystemPromptParams, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, clearAttachmentParsers, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, parseAttachmentContent, registerAttachmentParser, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
755
+ export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, 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, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, type ExtractDirective, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingSystemPromptParams, LINK_LABEL_MAX_DISPLAY_CHARS, 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, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, clearAttachmentParsers, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, parseAttachmentContent, registerAttachmentParser, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
package/dist/engine.d.ts CHANGED
@@ -81,6 +81,20 @@ interface ChatEngineConfig {
81
81
  * `registerAttachmentParser()`. See attachment_parsers.ts.
82
82
  */
83
83
  attachmentParsers?: AttachmentParser[];
84
+ /**
85
+ * Opt in to SERVER-DRIVEN windowed indexing for text/grid files.
86
+ *
87
+ * Off by default, and deliberately so. When on, the client emits a
88
+ * `_skapi_window` directive and the WORKER reads the file one window at a time,
89
+ * continuing until the reader says it is exhausted. When off, the agent pages the
90
+ * file itself with readFileContent, exactly as before.
91
+ *
92
+ * The flag exists because the backend must ship FIRST: a client emitting the
93
+ * directive against a worker that does not strip it leaves an unknown field in the
94
+ * request body, and the provider rejects the whole call with no retry. Keep it off
95
+ * until the worker is deployed, then flip it per environment.
96
+ */
97
+ windowedIndexing?: boolean;
84
98
  }
85
99
  declare function configureChatEngine(config: ChatEngineConfig): void;
86
100
  declare function chatEngineConfig(): ChatEngineConfig;
@@ -241,18 +255,46 @@ type BuildIndexingUserMessageOptions = {
241
255
  pagedRead?: boolean;
242
256
  };
243
257
  declare function buildIndexingUserMessage(attachment: IndexingAttachmentInfo, options?: BuildIndexingUserMessageOptions): string;
258
+ /**
259
+ * Token the WORKER substitutes with the 1-based first page of the window it is about to
260
+ * render, when it builds the next pass of a document from `RENDER_CONTINUE_TEMPLATE`.
261
+ * Must match the worker's RENDER_FROM_TOKEN.
262
+ */
263
+ declare const RENDER_FROM_TOKEN = "{{RENDER_FROM}}";
244
264
  /**
245
265
  * User message for a VISION file (PDF): its pages are delivered as RENDERED PAGE IMAGES that
246
266
  * the proxy worker injects into THIS message at the `placeholder` token (tool-result images
247
267
  * render on neither provider, so the pages must be image blocks in the message itself). Each
248
- * pass shows one WINDOW of pages starting at `renderFrom` (0-based); the resume loop advances
249
- * the window a pass at a time until the injected note says the last window was reached.
268
+ * pass shows one WINDOW of pages starting at `renderFrom` (0-based).
269
+ *
270
+ * The WORKER advances the window: when its renderer reports pages remaining it enqueues the
271
+ * next pass itself, off the true page count, so a document indexes end-to-end with no browser
272
+ * involved. This message therefore only ever describes ONE window, and the model is never
273
+ * asked to decide whether the document is finished.
250
274
  *
251
275
  * renderFrom === 0 is the FIRST pass (leads with "A new file has just been uploaded." so the
252
- * client builds the "Indexing: <name>" bubble); renderFrom > 0 is a RESUME pass (leads with
253
- * "CONTINUE indexing" like the paged continue message, so it is not a duplicate primary bubble).
276
+ * client builds the "Indexing: <name>" bubble); a continue pass (built by the worker from
277
+ * buildIndexingRenderContinueTemplate) leads with "CONTINUE indexing" so it is not a duplicate
278
+ * primary bubble.
254
279
  */
255
280
  declare function buildIndexingRenderMessage(attachment: IndexingAttachmentInfo, placeholder: string, renderFrom: number): string;
281
+ /**
282
+ * The CONTINUE pass, as a template the worker fills in. `pageLabel` defaults to the
283
+ * RENDER_FROM_TOKEN placeholder, which the worker replaces with the real 1-based start page
284
+ * of the window it is rendering; passing an explicit label produces a ready-to-send message.
285
+ */
286
+ declare function buildIndexingRenderContinueTemplate(attachment: IndexingAttachmentInfo, placeholder: string, pageLabel?: string): string;
287
+ /**
288
+ * User message for a WINDOWED file: the worker splices ONE window of the file's rows or
289
+ * text into this message at `placeholder`, then continues from the reader's own cursor
290
+ * until the file is exhausted.
291
+ *
292
+ * The agent is deliberately NOT asked to page the file itself, and is NOT asked to judge
293
+ * whether it is finished. Both used to be its job, and both failed the same way: the
294
+ * traversal lived inside a single turn's budget, so a large file simply stopped partway
295
+ * with a confident summary of the part it had seen.
296
+ */
297
+ declare function buildIndexingWindowMessage(attachment: IndexingAttachmentInfo, placeholder: string, isContinuation: boolean, positionLabel?: string): string;
256
298
  /**
257
299
  * User message for a RESUME pass: a previous indexing pass could not finish this large
258
300
  * file, so continue it from where the already-saved records leave off (never restart).
@@ -423,6 +465,16 @@ declare function extractOpenAIText(response: any): any;
423
465
  declare function listClaudeModels(service: string, owner: string): Promise<any>;
424
466
  declare function listOpenAIModels(service: string, owner: string): Promise<any>;
425
467
  declare const BG_INDEXING_QUEUE_SUFFIX = "-bg";
468
+ /**
469
+ * True when a request belongs to the background-indexing queue.
470
+ *
471
+ * Accepts BOTH shapes this value arrives in: the bare queue name the client sends
472
+ * ("<userId>-bg"), and the server qid that comes back on history/poll responses
473
+ * ("<service>:<queue>|<seq>"). Testing the tail of the raw value only works for the
474
+ * first: a qid ends in "|<seq>", so `endsWith('-bg')` is always false for it — which
475
+ * silently meant history items were NEVER recognised as background tasks.
476
+ */
477
+ declare function isBgIndexingQueue(queueName?: string): boolean;
426
478
  type BgTaskEntry = {
427
479
  serviceId: string;
428
480
  platform: 'claude' | 'openai';
@@ -464,6 +516,17 @@ interface ChatIdentity {
464
516
  serviceName?: string;
465
517
  serviceDescription?: string;
466
518
  }
519
+ /**
520
+ * Project context captured at the moment the user hit Send, so a turn whose
521
+ * dispatch is delayed (attachment uploads are awaited first) still reaches the
522
+ * project the question was asked of rather than whichever project the user has
523
+ * navigated to by then. Both fields are identity-derived and must be snapshotted
524
+ * together — the system prompt embeds the service name/description/id.
525
+ */
526
+ interface PinnedDispatchContext {
527
+ identity: ChatIdentity;
528
+ systemPrompt: string;
529
+ }
467
530
  interface ChatMessage {
468
531
  role: 'user' | 'assistant';
469
532
  content: string;
@@ -480,6 +543,7 @@ interface ChatMessage {
480
543
  _localId?: string;
481
544
  _cancelling?: boolean;
482
545
  _cancelError?: string;
546
+ _ownerKey?: string;
483
547
  }
484
548
  interface ChatState {
485
549
  messages: ChatMessage[];
@@ -576,6 +640,13 @@ interface ChatHost {
576
640
  * session via the public methods and re-renders in host.notify().
577
641
  */
578
642
 
643
+ /** A live poll registered in ChatSession.historyItemPolls. */
644
+ type PollHandle = {
645
+ /** 'bg' = background indexing, pausable. 'fg' = a reply the user is waiting on. */
646
+ kind: 'fg' | 'bg';
647
+ /** Absent on an older skapi-js that cannot stop an attached poll. */
648
+ stop?: () => void;
649
+ };
579
650
  declare class ChatSession {
580
651
  host: ChatHost;
581
652
  state: ChatState;
@@ -587,21 +658,73 @@ declare class ChatSession {
587
658
  endOfList: boolean;
588
659
  startKeyHistory: string[];
589
660
  }>;
590
- historyItemPolls: Map<string, boolean>;
661
+ historyItemPolls: Map<string, PollHandle>;
662
+ /** Non-empty while polling is paused; keyed by reason so overlapping causes
663
+ * (view detached AND tab hidden) do not resume each other prematurely. */
664
+ private _pauseReasons;
665
+ private _resuming;
591
666
  private _lidSeq;
592
667
  constructor(host: ChatHost);
668
+ /**
669
+ * Register a live poll so (a) a remount dedupes against it instead of stacking a
670
+ * SECOND poll on the same item, and (b) pausePolling can stop it.
671
+ *
672
+ * `stop` comes from the SDK and may be absent on an older skapi-js, in which case the
673
+ * poll simply cannot be stopped and is left running — see pausePolling.
674
+ */
675
+ private _trackPoll;
676
+ /** True while any pause reason is active. */
677
+ isPollingPaused(): boolean;
678
+ /**
679
+ * Stop BACKGROUND polling until resumePolling. Foreground polls (a reply the user is
680
+ * waiting on) keep running deliberately: their results must still land in the history
681
+ * cache so resumePendingRequest can render them on return, otherwise a user who sends
682
+ * a message then navigates away comes back to a permanently stuck "Thinking...".
683
+ *
684
+ * Server-side work is untouched; this only stops asking about it. That is safe for
685
+ * document indexing because the worker drives that loop itself.
686
+ */
687
+ pausePolling(reason: string): void;
688
+ /**
689
+ * Lift a pause reason WITHOUT running the reconcile. For a caller that is about to
690
+ * reload history anyway (a view remounting), letting resumePolling also reconcile
691
+ * would race that load and can double-attach.
692
+ */
693
+ clearPauseReason(reason: string): void;
694
+ /**
695
+ * Clear a pause reason and, once none remain, re-attach polling and reconcile.
696
+ * Deliberately does NOT touch gateRefreshToken: bumping it would silently discard
697
+ * the results of anything still in flight across the pause.
698
+ */
699
+ resumePolling(reason: string): Promise<void>;
593
700
  private _newLocalId;
594
701
  getHistoryCacheKey(): string;
595
702
  updateHistoryCache(): void;
703
+ /**
704
+ * Land a resolved reply in the history cache of a chat that is NOT currently
705
+ * visible, without touching state.messages. Mirrors the cache-only path in
706
+ * dispatchAgentRequest: REPLACE the trailing pending "Thinking..." bubble
707
+ * (append only when there is none), and settle the matching pending user
708
+ * bubble, so the cached copy never keeps a stuck "Thinking..." that a later
709
+ * cache-first load would re-render forever.
710
+ */
711
+ private _applyReplyToCache;
712
+ /**
713
+ * serviceId/owner are passed explicitly by every caller: a request can be
714
+ * dispatched after the user moved to another project, and re-reading the live
715
+ * identity here would silently send the turn to THAT project instead of the
716
+ * one it was composed for. Falls back to the live read only when a caller
717
+ * omits them.
718
+ */
596
719
  private _callProviderFor;
597
720
  dispatchAgentRequest(params: any): Promise<any>;
598
- dispatchComposedMessage(composed: string, useBgQueue?: boolean, composedForLlm?: string, extractContent?: any, fileUrls?: any): void;
721
+ dispatchComposedMessage(composed: string, useBgQueue?: boolean, composedForLlm?: string, extractContent?: any, fileUrls?: any, pinned?: PinnedDispatchContext): void;
599
722
  promoteNextBgQueuedToRunning(): void;
600
723
  promoteNextQueuedToRunning(): void;
601
724
  resolveQueuedUserBubble(serverId?: string): number | undefined;
602
725
  insertAtTarget(msg: ChatMessage, targetIdx: number): void;
603
- onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string): void;
604
- onQueuedSendError(_composed: string, err: any, serverId?: string): void;
726
+ onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string, ownerKey?: string): void;
727
+ onQueuedSendError(_composed: string, err: any, serverId?: string, ownerKey?: string): void;
605
728
  cancelQueuedMessage(msg: ChatMessage, idx: number): void;
606
729
  typewriteIntoIndex(idx: number, fullText: string, localId?: string): Promise<void>;
607
730
  private typewriterQueue;
@@ -629,4 +752,4 @@ declare class ChatSession {
629
752
  bumpGate(): void;
630
753
  }
631
754
 
632
- export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, 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, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, type ExtractDirective, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingSystemPromptParams, LINK_LABEL_MAX_DISPLAY_CHARS, MAX_HISTORY_MESSAGES, MAX_PARSED_CONTENT_CHARS, MCP_NAME, MIN_INPUT_TOKEN_BUDGET, type MapHistoryOptions, OUTPUT_TOKEN_RESERVE, type OpenAIMessage, POLL_INTERVAL, TOOL_AND_RESPONSE_BUFFER, buildBoundedChatMessages, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, clearAttachmentParsers, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, parseAttachmentContent, registerAttachmentParser, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };
755
+ export { type AttachmentFailureGroup, type AttachmentParser, type AttachmentSaveInfo, BG_INDEXING_QUEUE_SUFFIX, type BgTaskEntry, type BoundedChatOptions, 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, EXPIRED_ATTACHMENT_URL_HOST, EXPIRED_ATTACHMENT_URL_ORIGIN, type ExtractDirective, HISTORY_TOKEN_BUDGET, type IndexingAttachmentInfo, type IndexingSystemPromptParams, LINK_LABEL_MAX_DISPLAY_CHARS, 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, buildChatSystemPrompt, buildDisplayExpiredAttachmentHref, buildIndexingContinueMessage, buildIndexingRenderContinueTemplate, buildIndexingRenderMessage, buildIndexingSystemPrompt, buildIndexingUserMessage, buildIndexingWindowMessage, callClaudeWithMcp, callClaudeWithPublicMcp, callOpenAIWithPublicMcp, chatEngineConfig, clearAttachmentParsers, composeUserMessage, configureChatEngine, createInlineLinkRegex, encodePathSegments, estimateMessageTokens, estimateTextTokens, extractClaudeText, extractLastUserTextFromRequest, extractOpenAIText, extractRemotePathFromAttachmentHref, filterListByClearHorizon, findAttachmentParser, getAttachmentParsers, getChatHistory, getContextWindow, getErrorMessage, getExpiredAttachmentVisiblePath, groupAttachmentFailures, isAuthExpiredError, isBgIndexingQueue, isErrorResponseBody, isNonRetryableRequestError, isOfficeFile, isServerExtractable, isServiceDbAttachmentHref, listClaudeModels, listOpenAIModels, makeExtractPlaceholder, mapHistoryListToMessages, normalizeAttachmentPathCandidate, normalizeTextContent, notifyAgentSaveAttachment, parseAttachmentContent, registerAttachmentParser, safeDecodeURIComponent, sanitizeAttachmentLinksForHistory, stripFileBlocksFromHistory, transformContentWithImages, transformContentWithOpenAIImages, truncateLabelForDisplay };