bunnyquery 1.5.7 → 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,6 +255,51 @@ 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}}";
264
+ /**
265
+ * User message for a VISION file (PDF): its pages are delivered as RENDERED PAGE IMAGES that
266
+ * the proxy worker injects into THIS message at the `placeholder` token (tool-result images
267
+ * render on neither provider, so the pages must be image blocks in the message itself). Each
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.
274
+ *
275
+ * renderFrom === 0 is the FIRST pass (leads with "A new file has just been uploaded." so the
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.
279
+ */
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;
298
+ /**
299
+ * User message for a RESUME pass: a previous indexing pass could not finish this large
300
+ * file, so continue it from where the already-saved records leave off (never restart).
301
+ */
302
+ declare function buildIndexingContinueMessage(attachment: IndexingAttachmentInfo): string;
244
303
 
245
304
  /**
246
305
  * Error detection + message extraction (pure). Moved verbatim from the
@@ -387,6 +446,18 @@ type AttachmentSaveInfo = {
387
446
  * takes precedence over server-side office extraction / web_fetch.
388
447
  */
389
448
  parsedContent?: string;
449
+ /**
450
+ * True for a RESUME pass: a previous indexing pass could not finish this (large)
451
+ * file, so continue it - always via readFileContent paging, with a "continue"
452
+ * message telling the agent to resume from where the saved records leave off.
453
+ */
454
+ continueIndexing?: boolean;
455
+ /**
456
+ * For an image-vision file (PDF), the 0-based PAGE the render window should start at.
457
+ * The worker renders [renderFrom, renderFrom+RENDER_PAGES_PER_WINDOW) and injects them
458
+ * as image blocks; the resume loop advances this by a window each pass.
459
+ */
460
+ renderFrom?: number;
390
461
  };
391
462
  declare function notifyAgentSaveAttachment(info: AttachmentSaveInfo): Promise<any>;
392
463
  declare function extractClaudeText(response: any): any;
@@ -394,6 +465,16 @@ declare function extractOpenAIText(response: any): any;
394
465
  declare function listClaudeModels(service: string, owner: string): Promise<any>;
395
466
  declare function listOpenAIModels(service: string, owner: string): Promise<any>;
396
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;
397
478
  type BgTaskEntry = {
398
479
  serviceId: string;
399
480
  platform: 'claude' | 'openai';
@@ -407,6 +488,8 @@ type BgTaskEntry = {
407
488
  poll: ((opts: {
408
489
  latency: number;
409
490
  }) => Promise<any>) | undefined;
491
+ /** How many CONTINUE passes have already run for this file (resume-across-passes). */
492
+ resumePass?: number;
410
493
  };
411
494
  declare function getChatHistory(params: {
412
495
  service?: string;
@@ -433,6 +516,17 @@ interface ChatIdentity {
433
516
  serviceName?: string;
434
517
  serviceDescription?: string;
435
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
+ }
436
530
  interface ChatMessage {
437
531
  role: 'user' | 'assistant';
438
532
  content: string;
@@ -449,6 +543,7 @@ interface ChatMessage {
449
543
  _localId?: string;
450
544
  _cancelling?: boolean;
451
545
  _cancelError?: string;
546
+ _ownerKey?: string;
452
547
  }
453
548
  interface ChatState {
454
549
  messages: ChatMessage[];
@@ -545,6 +640,13 @@ interface ChatHost {
545
640
  * session via the public methods and re-renders in host.notify().
546
641
  */
547
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
+ };
548
650
  declare class ChatSession {
549
651
  host: ChatHost;
550
652
  state: ChatState;
@@ -556,21 +658,73 @@ declare class ChatSession {
556
658
  endOfList: boolean;
557
659
  startKeyHistory: string[];
558
660
  }>;
559
- 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;
560
666
  private _lidSeq;
561
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>;
562
700
  private _newLocalId;
563
701
  getHistoryCacheKey(): string;
564
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
+ */
565
719
  private _callProviderFor;
566
720
  dispatchAgentRequest(params: any): Promise<any>;
567
- 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;
568
722
  promoteNextBgQueuedToRunning(): void;
569
723
  promoteNextQueuedToRunning(): void;
570
724
  resolveQueuedUserBubble(serverId?: string): number | undefined;
571
725
  insertAtTarget(msg: ChatMessage, targetIdx: number): void;
572
- onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string): void;
573
- 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;
574
728
  cancelQueuedMessage(msg: ChatMessage, idx: number): void;
575
729
  typewriteIntoIndex(idx: number, fullText: string, localId?: string): Promise<void>;
576
730
  private typewriterQueue;
@@ -582,6 +736,7 @@ declare class ChatSession {
582
736
  handleHistoryItemResolution(itemId: string, response: any, platform: string): void;
583
737
  applyHistoryItemResolution(itemId: string, response: any, platform: string): void;
584
738
  drainBgTaskQueue(): void;
739
+ maybeResumeIndexing(entry: BgTaskEntry, response: any, platform: string): void;
585
740
  loadHistory(fetchMore?: boolean, token?: number): Promise<void>;
586
741
  uploadSingleAttachment(att: any): Promise<Array<{
587
742
  name: string;
@@ -597,4 +752,4 @@ declare class ChatSession {
597
752
  bumpGate(): void;
598
753
  }
599
754
 
600
- 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, 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,6 +255,51 @@ 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}}";
264
+ /**
265
+ * User message for a VISION file (PDF): its pages are delivered as RENDERED PAGE IMAGES that
266
+ * the proxy worker injects into THIS message at the `placeholder` token (tool-result images
267
+ * render on neither provider, so the pages must be image blocks in the message itself). Each
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.
274
+ *
275
+ * renderFrom === 0 is the FIRST pass (leads with "A new file has just been uploaded." so the
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.
279
+ */
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;
298
+ /**
299
+ * User message for a RESUME pass: a previous indexing pass could not finish this large
300
+ * file, so continue it from where the already-saved records leave off (never restart).
301
+ */
302
+ declare function buildIndexingContinueMessage(attachment: IndexingAttachmentInfo): string;
244
303
 
245
304
  /**
246
305
  * Error detection + message extraction (pure). Moved verbatim from the
@@ -387,6 +446,18 @@ type AttachmentSaveInfo = {
387
446
  * takes precedence over server-side office extraction / web_fetch.
388
447
  */
389
448
  parsedContent?: string;
449
+ /**
450
+ * True for a RESUME pass: a previous indexing pass could not finish this (large)
451
+ * file, so continue it - always via readFileContent paging, with a "continue"
452
+ * message telling the agent to resume from where the saved records leave off.
453
+ */
454
+ continueIndexing?: boolean;
455
+ /**
456
+ * For an image-vision file (PDF), the 0-based PAGE the render window should start at.
457
+ * The worker renders [renderFrom, renderFrom+RENDER_PAGES_PER_WINDOW) and injects them
458
+ * as image blocks; the resume loop advances this by a window each pass.
459
+ */
460
+ renderFrom?: number;
390
461
  };
391
462
  declare function notifyAgentSaveAttachment(info: AttachmentSaveInfo): Promise<any>;
392
463
  declare function extractClaudeText(response: any): any;
@@ -394,6 +465,16 @@ declare function extractOpenAIText(response: any): any;
394
465
  declare function listClaudeModels(service: string, owner: string): Promise<any>;
395
466
  declare function listOpenAIModels(service: string, owner: string): Promise<any>;
396
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;
397
478
  type BgTaskEntry = {
398
479
  serviceId: string;
399
480
  platform: 'claude' | 'openai';
@@ -407,6 +488,8 @@ type BgTaskEntry = {
407
488
  poll: ((opts: {
408
489
  latency: number;
409
490
  }) => Promise<any>) | undefined;
491
+ /** How many CONTINUE passes have already run for this file (resume-across-passes). */
492
+ resumePass?: number;
410
493
  };
411
494
  declare function getChatHistory(params: {
412
495
  service?: string;
@@ -433,6 +516,17 @@ interface ChatIdentity {
433
516
  serviceName?: string;
434
517
  serviceDescription?: string;
435
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
+ }
436
530
  interface ChatMessage {
437
531
  role: 'user' | 'assistant';
438
532
  content: string;
@@ -449,6 +543,7 @@ interface ChatMessage {
449
543
  _localId?: string;
450
544
  _cancelling?: boolean;
451
545
  _cancelError?: string;
546
+ _ownerKey?: string;
452
547
  }
453
548
  interface ChatState {
454
549
  messages: ChatMessage[];
@@ -545,6 +640,13 @@ interface ChatHost {
545
640
  * session via the public methods and re-renders in host.notify().
546
641
  */
547
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
+ };
548
650
  declare class ChatSession {
549
651
  host: ChatHost;
550
652
  state: ChatState;
@@ -556,21 +658,73 @@ declare class ChatSession {
556
658
  endOfList: boolean;
557
659
  startKeyHistory: string[];
558
660
  }>;
559
- 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;
560
666
  private _lidSeq;
561
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>;
562
700
  private _newLocalId;
563
701
  getHistoryCacheKey(): string;
564
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
+ */
565
719
  private _callProviderFor;
566
720
  dispatchAgentRequest(params: any): Promise<any>;
567
- 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;
568
722
  promoteNextBgQueuedToRunning(): void;
569
723
  promoteNextQueuedToRunning(): void;
570
724
  resolveQueuedUserBubble(serverId?: string): number | undefined;
571
725
  insertAtTarget(msg: ChatMessage, targetIdx: number): void;
572
- onQueuedSendResponse(_composed: string, response: any, platform: string, serverId?: string): void;
573
- 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;
574
728
  cancelQueuedMessage(msg: ChatMessage, idx: number): void;
575
729
  typewriteIntoIndex(idx: number, fullText: string, localId?: string): Promise<void>;
576
730
  private typewriterQueue;
@@ -582,6 +736,7 @@ declare class ChatSession {
582
736
  handleHistoryItemResolution(itemId: string, response: any, platform: string): void;
583
737
  applyHistoryItemResolution(itemId: string, response: any, platform: string): void;
584
738
  drainBgTaskQueue(): void;
739
+ maybeResumeIndexing(entry: BgTaskEntry, response: any, platform: string): void;
585
740
  loadHistory(fetchMore?: boolean, token?: number): Promise<void>;
586
741
  uploadSingleAttachment(att: any): Promise<Array<{
587
742
  name: string;
@@ -597,4 +752,4 @@ declare class ChatSession {
597
752
  bumpGate(): void;
598
753
  }
599
754
 
600
- 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, 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 };